MySQL·捉蟲動態·DROPDATABASE外來鍵約束的GTIDBUG

db匠發表於2016-05-23

背景

MySQL的DDL沒有被設計成事務操作,因此DDL操作是無法回滾的(像PgSQL把DDL也設計成事務操作,DDL就可以在執行成功後被回滾操作取消)。這就會導致如果某個DDL語句內部被拆分為多個原子的DDL呼叫,那麼這個DDL語句就不具備中途執行失敗後回滾整個DDL語句的能力,也就是說,即使語句邏輯內的某個原子DDL呼叫失敗了,也無法回滾已經完成的那些原子DDL呼叫。


問題描述

DROP DATABASE 就是一個例子,對於MySQL而言,DROP DATABASE 並非是一個原子DDL操作,因為它是一個個刪除DB下的每張表,而 DROP TABLE 操作本身是會做預檢查的,無法刪除就會取消刪表操作返回失敗,所以 DROP TABLE 才能認為是原子的DDL呼叫。 這就會引起一個問題,如果一個DB中的某張表DROP失敗了,實際上 DROP DATABASE 作為一個整體是執行失敗的,但是DB中已經有一些表被刪除了,因此Binlog中會記錄成多個 DROP TABLE 操作,而不是一個 DROP DATABASE 語句。 如果被刪除的表的表名都不長,還是會記錄成一個刪除多張表的 DROP TABLE 語句(DROP TABLE tbl1, tbl2, …),但是如果表名總長度太長,MySQL會拆分為多個 DROP TABLE 語句來記錄。 沒有GTID的時候這似乎也不是什麼大問題,但是引入GTID之後就有一個問題:每個語句只分配一個GTID。如果一個 DROP DATABASE 語句被拆分為多個 DROP TABLE 語句,Binlog中就會出現多個 DROP TABLE 事件共用一個GTID的情況!

舉個例子:


這裡因為 db2.t3 表引用了 db1.t1 的欄位作為外來鍵約束,所以當 db1 做 DROP DATABASE 刪除到 t1 表時就報錯了,但此時很多表已經被刪除了。我們看Binlog中記錄的內容:


3個 DROP TABLE 語句都是同一個GTID:340d95b8-a699-11e4-868d-a0d3c1f20ae4:61

這就導致備庫複製報錯:



解決方案

怎麼解決這個問題呢?

1. 讓MySQL支援DDL事務

2. 對DROP DATABASE操作進行預檢查


第一種方案對MySQL改動太大了,完全不現實。因此我們採用了第二種方案,也間接實現了 DROP DATABASE 這個操作的原子性。 DROP DATABASE 之所以出現上面的狀況,就是因為沒有先檢查表是否可以刪除,而是走一步看一步,一個個刪的時候才看能不能刪除。我們對MySQL做了修正,對於DB中的每張表,在 DROP DATABASE 執行之前,都先預檢查所有可能導致刪除表失敗的條件,如果一旦發現某張表會無法刪除,就放棄整個 DROP DATABASE 操作,提示使用者刪除錯誤,讓使用者先自行解決問題後,再重新執行 DROP DATABASE。

例如上面例子中的情況,本來 DROP DATABASE 執行到有外來鍵約束的表時會報錯:


但此時其他表已經刪除了,而我們修正以後,同樣的操作會報一個Error和一個Warning,並且沒有真的刪任何表:


這裡提示了使用者有表存在問題無法刪除,讓使用者先處理掉之後,再來執行 DROP DATABASE。此時庫下面所有的表都還在,一定要預檢查通過才會真的刪除。


相關文章