前言
由Inside君親授的 上海MySQL周末線下VIP培訓班 還有2周即將開始,目前僅剩2個名額。Inside君致力於打造最好的MySQL培訓班,咨詢請加Inside君個人公眾賬號:82946772。更多詳情請點擊:只做最好的MySQL數據庫培訓——3月上海周末線下班開始報名 。北京的同學們,也不要急,因為Inside君馬上要來北京了,本周確定培訓地點就會發布相關培訓信息(突然好想唱下五環之歌喲~~~)。
正文
MySQL 5.6版本開啟GTID模式,必須打開參數log_slave_updates,簡單來說就是必須在從機上再記錄一份二進制日志。這樣的無論對性能還是存儲的開銷,無疑會相應的增大。而MySQL 5.7版本開始無需在GTID模式下啟用參數log_slave_updates,其中最重要的原因在於5.7在mysql庫下引入了新的表gtid_executed,其表結構如下所示:
mysql> SHOW CREATE TABLE mysql.gtid_executed\G
*************************** 1. row ***************************
Table: gtid_executed
Create Table: CREATE TABLE `gtid_executed` (
`source_uuid` char(36) NOT NULL COMMENT 'uuid of the source where the transaction was originally executed.',
`interval_start` bigint(20) NOT NULL COMMENT 'First number of interval.',
`interval_end` bigint(20) NOT NULL COMMENT 'Last number of interval.',
PRIMARY KEY (`source_uuid`,`interval_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 STATS_PERSISTENT=0
1 row in set (0.00 sec)
簡單來說,該表會記錄當前執行的GTID。列source對應UUID,列interval_start/interval_end表示的是事務號。在MySQL 5.6中必須配置參數log_slave_updates的最重要原因在於當slave重啟后,無法得知當前slave已經運行到的GTID位置,因為變量gtid_executed是一個內存值:
mysql> select @@global.gtid_executed\G
*************************** 1. row ***************************
@@global.gtid_executed: 7af7d3ea-933b-11e5-9da7-fa163e30f9a2:1-72054
1 row in set (0.00 sec)
所以MySQL 5.6的處理方法就是啟動時掃描最后一個二進制日志,獲取當前執行到的GTID位置信息。當然,如果DBA不小心將二進制日志刪除了,那么這又會帶來災難性的問題。
因此,MySQL 5.7將gtid_executed這個值給持久化了。采用的技巧與MySQL 5.6處理SQL thread保存位置的方式一樣,即將GTID值持久化保存在一張InnoDB表中,並與用戶事務一起進行提交,從而實現數據的一致性:
START TRANSACTION;
# user statement
......
INSERT INTO mysql.gtid_executed VALUES (...)
END;
需要注意的是表mysql.gtid_executed是在主服務器和從服務器上有進行更新的,而表slave_relay_log_info僅在從服務器上更新。
MySQL 5.7對於表mysql.gtid_executed的更新策略也有些不同,如果沒有主服務器沒有開啟log_bin或者從服務器沒有開啟log_slave_updates,其會每一個事物更新表gtid_executed,這樣服務器重啟后可以快速知道當前服務器執行到的GTID位置。因此,用戶可能在服務器上看到類似如下的內容:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 1 | 4334294 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4334296 | 4352984 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352985 | 4352985 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352986 | 4352986 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352987 | 4352987 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4352988 | 4352988 |
......
那這樣豈不是表gtid_executed中的記錄會瘋狂增長。為此,MySQL 5.7又引入了新的線程,用來對此表進行壓縮,該線程如下所示:
mysql> select thread_id,thread_os_id,name,
-> processlist_command,processlist_state from threads
-> where name like '%compress%'\G
*************************** 1. row ***************************
thread_id: 39
thread_os_id: 23816
name: thread/sql/compress_gtid_table
processlist_command: Daemon
processlist_state: Suspending
1 row in set (0.01 sec)
參數gtid_executed_compression_period用來控制每執行多少個事務,對此表進行壓縮,默認值為1000。因此,過一段時間后,上述的表mysql.gtid_executed會壓縮成如下的內容:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 1 | 4334294 |
| 05e16691-bf69-11e5-97cf-fa163e30f9a2 | 4334296 | 4354329 |
......
若MySQL服務器啟用了二進制日志,則表mysql.gtid_executed的更新僅在二進制rotation時發生,因為發生重啟等情況依舊可以通過掃描二進制日志判斷得知當前運行的GTID位置。
開啟log_slave_updates后,主從服務器性能有多少的差異,這是一個蠻有意思的問題。不過IMG社區群中的西毒同學,已經發現了另一個有意思的問題,即主服務器將innodb_flush_log_at_trx_commit設置為0,依然能保證crash safe。有同學能想到原因是什么嗎?歡迎前來IMG微信群進行討論