MySQL基於GTID主從複製的雜談
先來回顧一下SQL/">MySQL的二進位制知識點。基於Row格式的日誌可以避免MySQL主從複製中出現的主從不一致問題。在一個sql語句修改了1000條資料的情況下,基於段的日誌格式只會記錄這個sql語句。而基於row的日誌格式會有1000條記錄來記錄每一行的資料修改。
MySQL官方推薦基於row的日誌格式,優點如下:
1.使MySQL主從複製 更加安全。
2.對每一行資料的修改比基於段的複製更加高效。
有時候我們進行誤操作而修改了資料庫中的資料,同時又沒有備份可以恢復時,我們可以通過分析binlog,對日誌中記錄的資料修改操作做反向處理來達到恢復資料的目的。
基於row格式的日誌量會比較大,我們可以通過
配置 binlog_row_image=full|minimal|noblob
。full模式會記錄所有的列。minimal模式智慧記錄被修改的列。noblob模式在沒有對text、blob列進行修改時不會記錄text、blob列。
推薦日誌採用mixed格式,它具有以下優點:
1.根據sql語句,系統決定在基於段和基於行的日誌格式中進行選擇。
2.資料量的大小由所執行的sql語句決定。
由於日誌的格式原因,複製又分為:
基於sql語句的複製(SBR):二進位制日誌格式使用的是statement格式。
基於行的複製(RBR):二進位制日誌格式使用的是基於行的日誌格式。
混合模式:根據實際內容在以上兩者進行切換。
SBR的優點:
1.生成的日誌量較少,節約網路傳輸IO。
2.並不強制要求主從資料庫的表定義完全相同。
3.相比於基於row的複製模式更加靈活。
SBR的缺點:
1.對非確定性時間,無法保證主從複製資料的一致性,比如uuid()。從而導致主從資料一致性,造成複製鏈路中斷。
2.對於儲存過程,觸發器,自定義函式進行的修改也可能造成資料不一致。
3.相比基於行的複製方式在執行時需要更多的行鎖。
RBR的優點:
1.可以應用任何sql的複製包括非確定函式,儲存過程。
2.可以減少資料庫鎖的使用。
RBR的缺點:
1.要求主從資料庫的表結構相同,否則可能會發生複製中斷。
2.無法再slave上單獨執行觸發器。
- 基於sql段的日誌是slave上重新執行binlog記錄的sql。
- 基於row的日誌則是在slave上直接應用對資料庫的修改。
在slave配置中繼日誌的時候,如果不加 mysql
字首的話,預設生成的中繼日誌格式是 本機ip-mysql-replay-bin.000001
。最好還是指定字首。
relay_log=mysql-relay-bin
把中繼日誌的內容加入到slave的binlog中。也就是說slave的binlog會記錄master同步的操作日誌。
log-slave-updates=on
對資料庫進行備份
mysqldump -utest -ptest rap_test > rap_test.dump
master-data=1或者2,其實沒有什麼區別。只是master-data=2,把change master註釋掉了。指定single-transaction,備份時是不會加鎖的,也不會阻止任何的事務,保證備份時資料一致性。想要實現線上備份,可以用xtrabackup工具。
mysqldump --single-transaction --master-data --triggers --routines --all-databases -utest -ptest > db.dump

image.png
下面來說說基於日誌點複製和基於GTID複製的優缺點把。
基於日誌點複製的優點:
1.MySQL最早支援的複製技術,BUG相對較少。
2.對sql查詢沒有什麼限制。
3.故障處理比較容易。
基於日誌點複製的缺點:
1.故障轉移時重新獲取master的日誌點資訊比較困難。基於日誌點複製是從master的binlog的偏移量進行增量同步。如果指定錯誤會造成遺漏或者重複,造成主從不一致。
GTID是全域性事務ID,其保證為每個在master上提交的事務在複製叢集中可以生產一個唯一ID。GTID的生成策略是source_id(也就是server的uuid,在auto.conf檔案裡面可以看到):transaction_id(自增序列)。
[auto] server-uuid=67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026
在基於GTID的複製中,slave會告訴master,slave已經執行事務的 GTID,master也會告訴slave,slave未執行事務的GTID。同一個事務只會在指定的從庫執行一次。
基於GTID複製的優點是:
1.可以很方便的進行故障轉移,記錄master最後事務的GTID值。比如master:A,slave:B,C。當A掛了後,B執行了所有A傳過來的事務。當C連線到B後,在自己的binlog找到最後一次A傳過來的GTID。然後C將這個GTID傳送給B,B獲取到這個GTID,就開始從這個GTID的下一個GTID開始傳送事務給C。這種自我尋找複製位置的模式減少事務丟失的可能性以及故障恢復的時間。
2.slave不會丟失master的任何修改(開啟了log_slave_updates)
基於GTID複製的缺點:
1.不支援非事務引擎。
2.故障處理比較複雜,需要注入空事務。
3.不支援sql_slave_skip_counter(一般用這個來跳過基於binlog主從複製出現的問題。)
4.對執行的sql有一定的限制。
5.為了保證事務的安全性, create table ... select
無法使用。不能使用 create temporary table
建立臨時表。不能使用關聯更新事務表和非事務表。
廢話不BB了,直接進入正題。
這是我從網上copy的一張圖。一般主從複製有3個執行緒參與,binlog dump(master),IO Thread(slave),sql Thread(slave)

image.png
基於GTID主從複製的步驟:
1.master資料改變時,會在事務前產生一個GTID,通過binlog dump記錄到master的binlog中。
2.slave通過IO Thread將binlog中變更的資料,寫入到slave的relay log中(中繼日誌)。
3.slave通過sql Thread讀取relay log中的GTID,然後對比slave的binlog是否有此記錄。
4.如果slave的binlog存在該GTID的記錄,那麼不好意思,忽略掉了。
5.如果slave的binlog不存在該GTID的記錄,那麼就執行該GTID事務,並一同記錄到slave的binlog中。
slave配置
server-id=6 log-bin=mysql-bin binlog_format=mixed gtid_mode=on enforce-gtid-consistency=on log-slave-updates=on #read_only=on #innodb_read_only=on master_info_repository=TABLE relay_log_info_repository=TABLE relay_log=mysql-relay-bin binlog_row_image=minimal relay_log_recovery=1 slave-parallel-type=LOGICAL_CLOCK slave-parallel-workers=16
master配置
# Binary Logging. sync_binlog=1 log-bin=mysql-bin binlog_format=mixed binlog_row_image=minimal gtid_mode=on enforce-gtid-consistency=on log-slave-updates=on # Error Logging. log-error="cmazxiaoma-mysql.err" # Server Id. server-id=21
在slave配置master相關的資訊。
mysql> change master to -> master_host="192.168.10.21", -> master_user="gtid", -> master_password="gtid", -> master_auto_position=0\g Query OK, 0 rows affected, 2 warnings (0.13 sec)
mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Master_Host: 192.168.10.21 Master_User: gtid Master_Port: 3306 Connect_Retry: 60 Master_Log_File: Read_Master_Log_Pos: 4 Relay_Log_File: mysql-relay-bin.000001 Relay_Log_Pos: 4 Relay_Master_Log_File: Slave_IO_Running: No Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 0 Relay_Log_Space: 154 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 1236 Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replica' Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 21 Master_UUID: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026 Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: 181108 11:26:16 Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1-42135, 803785ec-db4e-11e8-987c-000c29c74079:1-6139 Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec)
之前我在master上執行了 reset master
的命令,清除了binlog。首先IO執行緒讀取主庫的binlog將master資料變化寫入到slave的relay log。然後sql執行緒從relay log獲取gtid,與slave的binlog對比,發現slave比master的gtid數量還要多,所以丟擲了1236錯誤。
我們在master執行 SHOW GLOBAL VARIABLES LIKE '%gtid%'
,發現 gtid_executed
和 gtid_purged
都為空。

image.png
我們在slave也同樣執行 SHOW GLOBAL VARIABLES LIKE '%gtid%'
,發現gtid_executed為 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1-42135, 803785ec-db4e-11e8-987c-000c29c74079:1-6139

image.png
我們在slave執行 reset master
,再來看看 gtid_executed
的值,發現它清空了。
mysql> show global variables like '%gtid%'\G *************************** 1. row *************************** Variable_name: binlog_gtid_simple_recovery Value: ON *************************** 2. row *************************** Variable_name: enforce_gtid_consistency Value: ON *************************** 3. row *************************** Variable_name: gtid_executed Value: *************************** 4. row *************************** Variable_name: gtid_executed_compression_period Value: 1000 *************************** 5. row *************************** Variable_name: gtid_mode Value: ON *************************** 6. row *************************** Variable_name: gtid_owned Value: *************************** 7. row *************************** Variable_name: gtid_purged Value: *************************** 8. row *************************** Variable_name: session_track_gtids Value: OFF 8 rows in set (0.00 sec)
執行slave,檢視slave的狀態,一切正常。
mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.10.21 Master_User: gtid Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 154 Relay_Log_File: mysql-relay-bin.000003 Relay_Log_Pos: 367 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 154 Relay_Log_Space: 751 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 21 Master_UUID: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026 Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec)
後來我不小心在master上執行了一段sql INSERT INTO groupon.date_demo VALUES(NULL,NOW(),NOW(),NOW(),NOW(),NOW())
,由於master和slave上的資料不一致(master上有groupon資料庫,slave沒有groupon資料庫),導致slave的SQL執行緒執行sql語句時,掛了。
mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.10.21 Master_User: gtid Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 537 Relay_Log_File: mysql-relay-bin.000003 Relay_Log_Pos: 367 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: No Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 1146 Last_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 0 failed executing transaction '67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1' at master log mysql-bin.000001, end_log_pos 506. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any. Skip_Counter: 0 Exec_Master_Log_Pos: 154 Relay_Log_Space: 1134 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 1146 Last_SQL_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 0 failed executing transaction '67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1' at master log mysql-bin.000001, end_log_pos 506. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any. Replicate_Ignore_Server_Ids: Master_Server_Id: 21 Master_UUID: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026 Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: 181108 11:57:50 Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1 Executed_Gtid_Set: Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec)
通過看slave的狀態,我們可以抓住幾條關鍵訊息,來定位錯誤。
Retrieved_Gtid_Set是slave收到的事務資訊,Executed_Gtid_Set是slave執行的事務訊息。我們發現收到的事務資訊是1,但是slave卻沒有執行任何事務訊息。所以我判斷是執行67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1事務訊息報錯。
Retrieved_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1 Executed_Gtid_Set
還有一條關鍵資訊是 Last_SQL_Error
,它顯示執行 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
事務訊息報錯。證明了我們上面的想法。如果Last_SQL_Error沒有顯示執行錯誤的事務資訊,那該怎麼辦。我們可以通過檢視log的方式,定位到事務資訊GTID。
Last_SQL_Error: Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 0 failed executing transaction '67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1' at master log mysql-bin.000001, end_log_pos 506. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any.
還有一條關鍵訊息是 Relay_Master_Log_File: mysql-bin.mysql-bin.000001
和 Exec_Master_Log_Pos: 154
。這是相對於主庫,是從庫的sql執行緒執行到的位置。也就是SQL執行緒讀取Relay_Master_Log_File檔案,執行完position為154的位置,發生異常。為了確認我的想法是對的,執行 mysqlbinlog --no-defaults -v -v --base64-output=decode-rows --stop-position=200 mysql-bin.000001
檢視binlog日誌,發現mysql-bin.000001檔案中end_log_pos:154的後面沒有訊息了。按理來說執行完end_log_pos:154訊息,再往下執行就會報錯,會輸出相關執行錯誤的事務資訊。那為什麼沒有相關錯誤的事務訊息呢?腦子裡面仔細回想一下GTID主從複製的流程:slave通過讀取relay log檔案,執行GTID的事務並記錄到slave的binlog中。由於執行GTID的事務失敗,那麼相關資訊肯定不會記錄到slave的binlog中。

image.png

image.png
在Relay_Master_Log_File沒有找到相關資訊,不要緊。我們從Relay_Log_File裡面找資訊。Relay_Log_File是中繼日誌,相對於從庫,記錄著從庫的sql執行緒執行到的位置。我們可以確定從庫的sql執行緒執完中繼日誌的pos為367的位置發生異常。
Relay_Log_File: mysql-relay-bin.000003 Relay_Log_Pos: 367
執行 mysqlbinlog --no-defaults -v -v --base64-output=decode-rows --stop-position=390 mysql-relay-bin.000003
,我們可以看到執行到end_log_pos為219的位置發生異常,異常事務訊息gtid為 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
。

image.png
我們從master檢視到binlog中pos為219的訊息,可以看到GTID為 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
,證明我的猜想是正確的。

image.png
那如何定位錯誤事務GTID已經說完了,接下來是怎麼解決錯誤了,我們可以注入空事務的方式跳過這個錯誤。
mysql> stop slave\G Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> set gtid_next='67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1'\G Query OK, 0 rows affected (0.00 sec) mysql> begin\G Query OK, 0 rows affected (0.00 sec) mysql> commit\G Query OK, 0 rows affected (0.00 sec) mysql> set gtid_next="automatic"\G Query OK, 0 rows affected (0.00 sec)
執行slave,檢視狀態,可以看到 Retrieved_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
和 Executed_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
資料一致,證明我們注入空事務成功,nice。
mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.10.21 Master_User: gtid Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 537 Relay_Log_File: mysql-relay-bin.000004 Relay_Log_Pos: 454 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 537 Relay_Log_Space: 1257 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 21 Master_UUID: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026 Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1 Executed_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1 Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec) mysql>
為了測試基於GTID的主從複製是否成功,
我們在master插入一條資料 INSERT INTO date_demo VALUES(NULL,NOW(),NOW(),NOW(),NOW(),NOW())
,然後看slave是否有此記錄。

master.png

slave.png
再看看slave的 Retrieved_Gtid_Set
和 Executed_Gtid_Set
發生了什麼變化。它們從 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1
轉變成了 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1-2
,說明slave成功執行了剛才插入sql的事務訊息。
Retrieved_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1-2 Executed_Gtid_Set: 67ccaaf1-e4b4-11e7-a07f-c8d3ffc0c026:1-2