測試腳本先START TRANSACTION,再插入一筆資料、加一個欄位,最後ROLLBACK。你以為全部會撤回,結果資料與欄位都還在。先查中間有沒有造成隱式提交的語句:MySQL的ALTER TABLE會打斷原本交易,不能只因腳本外面寫了交易,就把資料變更與結構變更視為一整包可回滾。
ROLLBACK只處理仍在交易中的變更
一般InnoDB資料操作可以在明確交易中由COMMIT提交或ROLLBACK回滾,但一些語句會自己形成提交邊界。MySQL官方文件列有ALTER TABLE等DDL語句;許多這類語句在執行前提交目前交易,並在執行後形成自己的提交。讀腳本時要查看整個語句序列,不能只找開頭與結尾。
這篇聚焦ALTER TABLE新增一般欄位的實測。不是所有SQL、所有DDL或所有資料庫引擎都用同一條規則;例如暫存表相關語句有文件列出的不同界線。碰到具體語句時,核對MySQL對應版本的隱式提交清單,不要只用「DDL都一樣」一句話跳過。
在自己的小表重現提交邊界
案例使用自有隔離MySQL 8.4.9,只有一張明確指定ENGINE=InnoDB的小表,沒有對外開放連接埠,也沒有讀寫正式網站資料。先在交易外建立原始表,再開始交易;這樣觀察點就是交易內的INSERT與ALTER,而不是把建立資料庫的影響混在一起。
CREATE TABLE records (
id INT PRIMARY KEY,
label VARCHAR(30)
) ENGINE=InnoDB;
START TRANSACTION;
INSERT INTO records VALUES (1,'before-ddl');
ALTER TABLE records
ADD COLUMN note VARCHAR(30) NULL;
ROLLBACK;
接著查看資料與欄位。本次ROLLBACK之後,id 1的before-ddl仍存在,note欄位也存在且值為NULL。這不是只用介面快取推測:測試直接執行SELECT和SHOW COLUMNS,保存伺服器回傳的結果。
SELECT * FROM records ORDER BY id;
SHOW COLUMNS FROM records;
-- 仍有:1 / before-ddl / NULL
-- 欄位仍有:id、label、note
在INSERT與ALTER之間沒有手動COMMIT。這正是案例要核對的地方:ALTER觸發提交邊界,讓先前那筆INSERT不再屬於最後ROLLBACK能撤回的未提交工作。若只說「新欄位沒被撤回」,還不足以指出資料操作也被提交;所以這裡同時檢查列與結構。
atomic DDL不等於你可手動回滾整段腳本
MySQL 8.4支援特定範圍的atomic DDL,官方用來描述DDL操作中資料字典、儲存引擎與二進位紀錄等更新的原子性。它讓受支援的DDL在完成或失敗時有一致的結果,但不把DDL變成能與先前DML放在同一使用者交易裡、最後一起ROLLBACK的語句。
兩個「原子」的對象不同:一個是在討論DDL這項操作如何完成,另一個是你希望整份腳本的多個步驟都一起撤回。這個小表沒有模擬當機或DDL執行中斷,也沒有驗證故障恢復;它只證明一般ALTER成功之後,最後ROLLBACK不會把這次結構與先前插入一起還原。
不要把這份結果寫成「InnoDB不支援交易」。表仍是InnoDB,問題是交易中插入了會提交的語句。也不要把SET autocommit=0當成通用解法;自動提交設定與特定語句的隱式提交規則是不同層次,關掉前者不會抹掉後者。
把資料修改與結構遷移分開規劃
如果工作只需修改既有資料,先使用明確交易包住需要一起成功的DML,再確認中間沒有隱式提交語句。需要加欄位、改索引或調整結構時,另立遷移步驟、驗收與恢復方式。不要把DDL藏在「若欄位不存在就加一下」的資料匯入程式裡,還對外保證整次匯入可以回滾。
對正式表的結構修改還牽涉資料量、鎖、執行算法與應用程式相容性。這篇一列表的ALTER很快,不代表正式大表不會阻塞,也沒有證明可以無停機部署。這些需要依實際ALTER、表與環境另外評估;本篇只處理交易邊界,不順手給出正式DDL命令。
遷移的回復方式也不一定是執行反向ALTER。新增欄位後如果已有程式寫入資料,直接刪除欄位可能丟失內容;修改型態後也未必能無損轉回。先保存必要備份、定義可接受的恢復點與相容期間,再決定回復設計,不能把「再改回去」等同交易回滾。
排查時重建真正的語句順序
先把測試的起始狀態也保存:原表只有id與label,沒有資料;結束時有一列與新增note。這樣能辨認變更究竟出自本次腳本,還是前一輪測試留下的內容。重跑時使用新測試表或新隔離資料庫,不要在已變更的表上再加同名欄位,然後把重複欄位錯誤混成原案例。
備份能協助恢復已提交變更,但它不是ROLLBACK的另一個名稱。恢復備份可能影響備份之後的其他合法資料,所以必須選定範圍與時間點。交易邊界寫清楚,才知道恢復方案需要處理哪些已經超出當前交易的工作。
先整理BEGIN或START TRANSACTION之後到COMMIT或ROLLBACK之前的每一條SQL,包含框架或工具自動送出的語句。應用程式裡有transaction函式,不代表函式內每個呼叫都遵守同一交易邊界;若中間執行ALTER,仍要按資料庫實際規則解讀。
保留相同連線中的版本、儲存引擎與最小重現結果。不同連線有各自的交易,不能在一個終端ROLLBACK後,到另一個應用程式畫面看到資料,就直接推論第一個交易沒有工作。這篇在同一個客戶端連線完成序列,再讀取結果,避免這種混淆。
另做一個只含INSERT而沒有ALTER的對照,會更容易辨認原本交易能做什麼。但不要把想像中的對照當成這次實測輸出:這份保存案例就是INSERT加ALTER後ROLLBACK,沒有宣稱已覆蓋每種DDL或各種框架遷移工具。自己延伸測試時,應保留兩份獨立表或乾淨起始狀態。
如果SQL客戶端遇錯仍繼續執行,也要把每條語句的錯誤與結果一起核對。某個ALTER失敗後,最後的ROLLBACK與列數可能呈現另一個結果;不能拿這篇成功ALTER的輸出,保證失敗語句前後所有情況。先確認實際成功的語句,再找已發生的提交邊界。
正式恢復前先停在只讀核對:確認哪些列已提交、結構現在是什麼,以及應用程式是否已經使用新欄位。不要因ROLLBACK沒達到預期,就立刻DELETE資料或DROP欄位來「補回滾」。那是新的資料變更,需要另一份有範圍的恢復方案。
本篇最後驗收只有兩個確切問題:ROLLBACK後id 1是否仍在,以及note欄位是否仍在。兩者都在,就對上這個ALTER的隱式提交行為。能分清已提交工作與仍可回滾的工作,才能準確描述失敗時會留下什麼,而不是只看程式最後有沒有呼叫rollback。

評論0