<b>bug 描述</b>
這是一個和 gtid 相關的bug,也就是說5.6才會有,并且出現這個 bug 需要滿足條件:
做修改性質的表管理操作,如 optimize/analyze/repair 可以,check 就不可以
操作對應的表不存在
gtid_next 被設定為一個固定的值,并且 binlog 開啟
在同時滿足這3種條件下,會發現記錄binlog時,對應的 gtid_log_event 中的uuid會記為 00000000-0000-0000-0000-000000000000,并且這個對應的 gtid 不會記入 executed_gtid_set。
<b>bug影響</b>
從 bug 描述可以看出,這個 bug 的表現特征就是 gtid_event 記錯了,是以單執行個體的話基本不受影響的,因為主備複制時才會用到 gtid,是以主備場景會受到這個bug的影響。下面我們看下主備場景下這個bug是如何影響的:
m<->s : m 和 s 互為主備,都是5.6,以 gtid 協定進行複制,m是主庫。
假設我們在主庫上執行了 optimize table non_exist_table,這時候 gtid_next = 'automatic',不是一個固定值,是以主庫的 gtid 記錄還是正常的,假設這時生成的 gtid_log_event 為 f3c1dd3e-395d-11e4-be45-4cb16c8f4abc:5,binlog 傳到備庫後,sql 線程在 apply 的時候,會先将 f3c1dd3e-395d-11e4-be45-4cb16c8f4abc:5 設定為 gtid_next,然後同樣做 optimize table non_exist_table,這個時候就觸發了bug,備庫的 gtid_log_event 記為00000000-0000-0000-0000-000000000000:5,并且不記入 executed_gtid_set。主庫繼續接收使用者的更新,同時會将備庫的 binlog 拉過去應用,當做到 00000000-0000-0000-0000-000000000000:5 時,發現這個不在 executed_gtid_set 中,就會執行,同樣觸發 bug, gtid_log_event 記為00000000-0000-0000-0000-000000000000:5,并且同樣不記入 executed_gtid_set。如此這樣循環往複,會發現 optimize table non_exist_table 對應的binlog 在主備之前循環,充斥在 binlog 和 relay log 中。
<b>bug 分析</b>
之是以出現這個bug,是因為表管理操作的特殊性,optimize/analyze/repair/check table 這些都統一調用 mysql_admin_table 函數進行管理操作,mysql_admin_table 執行失敗的時候,執行線程并不報錯,而是在 mysql_admin_table 函數結束前,清空線程中的error,将錯誤資訊封裝在結果集(result set)中發送給用戶端,是以 optimize/analyze/repair 雖然執行失敗了,但仍然會記 binlog 。 按照這個邏輯來看,出錯了仍然記binlog也是沒問題,隻要記對就行了,但是這裡有一個問題,就是 mysql_admin_table 會調用 open_and_lock_tables,因為表不存在,是以 open_and_lock_tables 打開表的時候就出錯,然後調用 trans_rollback_stmt ,之後會調到 gtid_rollback,最終調到 thd->variables.gtid_next.set_undefined()。
可以看到,如果是 type == gtid_group,就将 type 設定為 undefined_group。那麼什麼情況下gtid_next 的 type 會是 gtid_group,答案是為一個固定值的時候,即類似這種 f3c1dd3e-395d-11e4-be45-4cb16c8f4abc:5。
而在 gtid_log_event::gtid_log_event 有這段邏輯,
我們會發現,這個時候sid會被清掉,clear 操作就是置全0,是以最終寫入 binlog 的就是全0。
細心的同學會發現,當 gtid_next = automatic 的時候,也是會被 clear 的(automatic 對應的 group 是 automatic_group),其實如果 gtid_next = automatic 的話,隻有在 binlog commit 的時候才調用 gtid_before_write_cache 生成 gtid,是以前面的 gtid_rollback 是不會影響 automatic 的。
關于不記 executed_gtid_set 的問題,gtid_rollback 的時候,一方面通過 thd->variables.gtid_next.set_undefined() 把 gtid_next 的type設成undefined_group,另一方面用 thd->clear_owned_gtids(),把 thd->owned_gtid 的 sidno 設為0,導緻最終不會添加到 executed_gtid_set 中。
<b>bug修複</b>
官方已經修複了這個bug,具體可以參見這2個 revno
主要是第一個,第二個是post-fix。修複方法是在 thd 中加一個标志 skip_gtid_rollback,在進入 mysql_admin_table 時先根據上下文設定thd->skip_gtid_rollback ,在退出mysql_admin_table 前重置标志,gtid_rollback 在執行clear前會判斷下thd->skip_gtid_rollback。