如何回滚 MySQL 事务中的所有语句?

How to rollback all statements inside a MySQL Transaction?

我需要更新一个 table (bigtable) 的特定列,其中包含另一个 table 的 ID(FK 约束 oldsmalltable)以指向上的 ID另一个 table(FK 约束 newsmalltable)。基本上这就是我正在做的事情:

DELIMITER //

CREATE PROCEDURE updatebigtable ()
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING ROLLBACK;

    START TRANSACTION;
        ALTER TABLE bigtable DROP FOREIGN KEY bigtable_ibfk_1,
            MODIFY smalltable_id SMALLINT ;
        UPDATE bigtable SET smalltable_id=CASE smalltable_id
                WHEN 1 THEN 1592
                WHEN 2 THEN 1593
                WHEN 3 THEN 1602
                ...
                ELSE 0
            END;
        ALTER TABLE bigtable ADD CONSTRAINT bigtable_ibfk_1
            FOREIGN KEY(smalltable_id) REFERENCES newsmalltable(id);
    COMMIT;
END//
DELIMITER ;

CALL updatebigtable();
DROP PROCEDURE updatebigtable;

我需要确保如果由于某种原因新的外键约束失败(例如,对于不同类型的列,错误将发生在最后一个 alter table 语句),UPDATE 和第一个 ALTER TABLE 也应该回滚,即它们应该保持原样。

根据 MySQL documentation,通过使用 START TRANSACTION,该事务禁用了自动提交模式,这将不会允许:

that as soon as you execute a statement that updates (modifies) a table, MySQL stores the update on disk to make it permanent.

我只发现这个问题与我的关系不大:

How can I use transactions in my MySQL stored procedure?

如果我提到的错误发生在事务内部,则之前的语句已经执行并且更新是 "permanently done on disk"...

我也尝试在创建过程之前放置 SET autocommit=0; 但行为仍然相同...我是否遗漏了什么?或者这是 MySQL 事务回滚的预期行为?

如果有任何区别,我正在使用 MySQL v.5.6.17.

ALTER TABLE 语句总是导致隐式提交 (section 13.3.3 from MySQL docs, thanks ),这意味着即使它们在 START TRANSACTION; ... COMMIT; 块中,也有提交次数将与该块内完成的更改次数一样多。

锁定 table 也不是一个选项,因为 (from problems with ALTER TABLE):

If you use ALTER TABLE on a transactional table or if you are using Windows, ALTER TABLE unlocks the table if you had done a LOCK TABLE on it. This is done because InnoDB and these operating systems cannot drop a table that is in use.

在执行 alter 和 update 语句时避免不需要的 reads/writes 的唯一选择是模拟 ALTER TABLE:

的所有步骤
  1. Create a new table named A-xxx with the requested structural changes.
  2. Copy all rows from the original table to A-xxx.
  3. Rename the original table to B-xxx.
  4. Rename A-xxx to your original table name.
  5. Delete B-xxx.

这样可以在新的 table 中完成更新(在第 2 步之后),并且 bigtable 唯一不可用的时间是在执行第 3 步和第 4 步(重命名)时。

使用 TRY CATCH 块 BEGIN TRAN before BEGIN TRY and ROLLBACK TRAN inside CATCH block