在对该主题进行了一些讨论之后,我可以假设 MySQL InnoDB 有一个非常令人沮丧的事实:当涉及到 DML 时,它不支持(原子)事务。
如果您使用数据进行数据库迁移,那么有一个相当简单的解决方案可以使其完全失败或成功完成。
START TRANSACTION;
INSERT INTO orders(orderNumber,orderDate) VALUES (1,'2020-05-31');
INSERT INTO orders(orderNumber,orderDate) VALUES (1,'2020-05-31');
COMMIT;
事务是针对一个或多个数据库中的数据的数据库操作的原子单元。
不幸的是,以下情况并非如此:
START TRANSACTION;
CREATE TABLE Persons ( PersonID int, LastName varchar(255),FirstName varchar(255));
CREATE TABLE Ducks ( DuckID int, DuckName varchar(255));
CREATE INDEX duckname_index ON Ducks (DuckName varchar(255));
COMMIT;
每个语句都将创建一个隐式提交,因此如果在 MySQL 数据库损坏和迁移一半之间迁移失败。
从文档:
有些语句不能回滚。通常,这些包括数据定义语言 (DDL) 语句,例如创建或删除数据库的语句,创建、删除或更改表或存储例程的语句。您应该在设计事务时不包含此类语句。如果您在无法回滚的事务中早期发出语句,然后另一个语句稍后失败,则在这种情况下无法通过发出 ROLLBACK 语句来回滚事务的全部效果。
由于我们必须为某个软件实施自定义迁移系统,我们现在想知道如何解决这个问题?例如 Symfony ( https://symfony.com/ ) Doctrine ( https://www.doctrine-project.org/ ) 如何在内部解决这个问题?
想法:
如果出现错误,请在 CI/CD 级别解决并恢复旧数据库?缺点:听起来真的很笨拙。
仅允许仅包含一个 DML 语句的迁移,并严格分开 DML 和 DDL 迁移。缺点:每个生产部署将有 10 个或数百个迁移文件。
我仍然希望有更好的方法吗?该问题的最佳实际解决方案是什么 - 如果有的话?
我想你的意思是DDL。DML 语句就像 SELECT/INSERT/UPDATE/DELETE,我想不出导致隐式提交的 DML 语句之一。
如果您担心此类迁移中途发生崩溃,请在其自己的迁移步骤中应用每个 DDL 语句。通常,迁移框架为每个迁移分配一个“版本”ID,因此它们知道哪些迁移尚未应用。因此,如果一系列迁移中断,那么只需重新运行迁移工具,它就会找出中断的位置并运行后续迁移。
但老实说,大多数人并不介意。在将 DDL 语句提交到您的存储库之前,您应该仔细测试它们。所以语法错误或导致失败的东西的可能性应该接近于零。
如果任何人在没有通过迁移系统的情况下对该数据库实例进行操作,则可能会发生逻辑故障(例如,无法添加表,因为该名称的表已经存在)。没有任何自动化系统可以解释人类引入的所有可能的混乱。只要确保你的队友能够配合自动化。
中断多语句迁移的崩溃很少见。在这些情况下,您可能需要进行一些手动修复才能运行后续迁移。这很不方便,但这不是世界末日。
最后,迁移的另一种方法是声明性地描述表在系统的当前状态下应该是什么,并让工具确定要应用哪些迁移。例如,这就是像Skeema这样的工具的策略。