MySQL 8 複製(四)——GTID與複製
MySQL 8 複製(四)——GTID與複製
目錄
一、GTID簡介
1. 什麼是GTID
2. GTID的格式與儲存
二、GTID生命週期
1. 典型事務的GTID生命週期
2. GTID分配
3. gtid_next系統變數
4. gtid_purged系統變數
三、GTID自動定位
MySQL複製中使用的事務型別有以下兩種:
GTID事務:在二進位制日誌中每個GTID事務始終都以Gtid_log_event開頭。可以使用GTID或使用檔名和位置來定位GTID事務。
匿名事務:MySQL 8 的二進位制日誌中的每個匿名事務都以Anonymous_gtid_log_event開頭,不分配GTID。匿名事務只能使用檔名和位置來定位。
GTID出現之前,在一主多從的複製拓撲中,如果主庫當機,需要從多個從庫選擇之一作為新主庫,這個過程比較複雜。沒有一種直接了當的方法找到其它從庫對應的新主庫二進位制日誌座標。通常的做法是先要尋找每個從庫複製原主庫的最後語句,然後找到新主庫中包含該語句的二進位制日誌檔案,其中該語句後的第一個事件位置即為連線新主庫的二進位制座標。主要難點在於不存在一個唯一標識指出“複製原主庫的最後語句”,於是後來的MySQL中就出現了GTID的概念。
一、GTID簡介
1. 什麼是GTID
全域性事務識別符號GTID的全稱為Global Transaction Identifier,是在整個複製環境中對一個事務的唯一標識。它是MySQL 5.6加入的一個強大特性,目的在於能夠實現主從自動定位和切換,而不像以前需要指定檔案和位置。使用GTID複製時,主庫上提交事務時建立事務對應的GTID,從庫在應用中繼日誌時用GTID識別和跟蹤每個事務。在啟動新從庫或因故障轉移到新主庫時可以使用GTID來標識複製的位置,極大地簡化了這些任務。由於GTID的複製完全基於事務,因此只要在主庫上提交的所有事務也在從庫上提交,兩者之間的一致性就得到保證。GTID支援基於語句或基於行的複製格式,但為了獲得最佳效果,MySQL建議使用基於行的格式。GTID始終保留在主庫和從庫上,這意味著可以通過檢查其二進位制日誌來確定應用於任何從庫的任何事務的來源。而且,一旦在給定庫上提交了具有給定GTID的事務,則該庫將忽略具有相同GTID的任何後續事務。因此,在主庫上提交的事務只會在從庫上應用一次,這也有助於保證一致性。
2. GTID的格式與儲存
(1)單個GTID
GTID與主庫上提交的每個事務相關聯。此識別符號不僅對發起事務的庫是唯一的,而且在給定複製拓撲中的所有庫中都是唯一的。GTID用冒號分隔的一對座標表示,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:23
前一部分是主庫的server_uuid,後面一部分是主庫上按提交事務的順序確定的序列號,提交的事務序號從1開始。上面顯式的GTID表示:具有8eed0f5b-6f9b-11e9-94a9-005056a57a4e的伺服器上提交的第23個事務具有此GTID。MySQL 5.6以後用128位的server_uuid代替了原本的32位server_id的大部分功能。原因很簡單,server_id依賴於my.cnf 的手工配置,很可能產生衝突。而自動產生128位UUID的演算法可以保證所有的MySQL UUID都不會衝突。資料目錄下的auto.cnf檔案用來儲存server_uuid。MySQL啟動的時候會讀取auto.cnf檔案,如果沒有讀取到則會生成一個server_id,並儲存到auto.cnf檔案中。
在主庫上提交客戶端事務時,如果事務已寫入二進位制日誌,則會為其分配新的GTID,保證為客戶事務生成單調遞增且沒有間隙的GTID。如果未將客戶端事務寫入二進位制日誌(例如,因為事務已被過濾掉,或者事務是隻讀的),則不會在源伺服器上為其分配GTID。從庫上覆制的事務保留與主庫上事務相同的GTID。即使從庫上未開啟二進位制日誌,GTID也會被儲存。MySQL系統表mysql.gtid_executed用於儲存MySQL伺服器上應用的所有事務的GTID,但儲存在當前活動二進位制日誌檔案中的事務除外。
GTID的自動跳過功能意味著一旦在給定伺服器上提交了具有給定GTID的事務,則該伺服器將忽略使用相同GTID執行的任何後續事務(這種情況是可能發生的,如手工設定了gtid_next時)。這有助於保證主從一致性,因為在主庫上提交的事務在從庫上應用不超過一次。如果具有給定GTID的事務已開始在伺服器上執行但尚未提交或回滾,則任何在該伺服器上啟動具有相同GTID的併發事務都將被阻止。伺服器既不執行併發事務也不將控制權返回給客戶端。一旦先前的事務提交或回滾,就可以繼續執行同一GTID上被阻塞的併發會話。如果是回滾,則一個併發會話繼續執行事務,並且在同一GTID上阻塞的任何其它併發會話仍然被阻止。如果是提交,則所有併發會話都將被阻止,並自動跳過事務的所有語句。mysqlbinlog的輸出中的GTID_NEXT包含事務的GTID,用於標識複製中的單個事務。
下面做三個簡單實驗驗證GTID的自動跳過功能。
實驗1:驗證自動跳過
(1)準備初始資料
use test;
create table t1(a int);
create table t2(a int);
insert into t1 values(1),(2);
insert into t2 values(1),(2);
commit;
(2)檢視當前GTID
mysql> show master status;
+---------------+----------+--------------+------------------+--------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+--------------------------------------------+
| binlog.000027 | 34614 | | | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-356 |
+---------------+----------+--------------+------------------+--------------------------------------------+
1 row in set (0.00 sec)
(3)將GDIT設定為已經執行過的值,再執行事務。
mysql> set gtid_next = '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:356';
Query OK, 0 rows affected (0.00 sec)
mysql> truncate table test.t1;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test.t1;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql> set gtid_next = automatic;
Query OK, 0 rows affected (0.00 sec)
mysql>
可以看到,伺服器已經執行了GTID為356的事務,後續相同GTID的事務都被自動跳過,雖然truncate語句沒有報錯,但並未執行,資料無變化。
實驗2:驗證兩個相同GTID事務,事務1提交,事務2被跳過。
(1)準備兩個SQL指令碼s1.sql、s1.sql,gtid_next是一個沒用過的新值
s1.sql內容如下:
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:357';
begin;
delete from test.t1 where a=1;
select sleep(10);
commit;
set gtid_next=automatic;
s2.sql內容如下:
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:357';
begin;
delete from test.t2 where a=1;
commit;
set gtid_next=automatic;
(2)在會話1執行s1.sql,並且在其sleep期間,在會話2執行s2.sql
-- 會話1
mysql -uroot -p123456 test < s1.sql
-- 會話2
mysql -uroot -p123456 test < s2.sql
(3)查詢資料
mysql> select * from t1;
+------+
| a |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
mysql> select * from t2;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql>
可以看到,事務1提交前,事務2被阻塞。事務1提交後,具有相同GTID的事務2被跳過。
實驗3:驗證兩個相同GTID事務,事務1回滾,事務2提交。
(1)準備兩個SQL指令碼s1.sql、s1.sql,gtid_next是一個沒用過的新值
s1.sql內容如下:
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:360';
begin;
delete from test.t1 where a=2;
select sleep(10);
rollback;
set gtid_next=automatic;
s2.sql內容如下:
set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:360';
begin;
delete from test.t2 where a=1;
commit;
set gtid_next=automatic;
(2)在會話1執行s1.sql,並且在其sleep期間,在會話2執行s2.sql
-- 會話1
mysql -uroot -p123456 test < s1.sql
-- 會話2
mysql -uroot -p123456 test < s2.sql
(3)查詢資料
mysql> select * from t1;
+------+
| a |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
mysql> select * from t2;
+------+
| a |
+------+
| 2 |
+------+
1 row in set (0.00 sec)
mysql>
可以看到,事務1回滾前,事務2被阻塞。事務1回滾後,具有相同GTID的事務2被提交。
(2)GTID集
GTID集是包括一個或多個單個GTID或GTID範圍的集合。源自同一伺服器的一系列GTID可以摺疊為單個表示式,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-321
上面的示例表示源自server_uuid為8eed0f5b-6f9b-11e9-94a9-005056a57a4e伺服器的第1到第321個事務。源自同一伺服器的多個單GTID或GTID範圍可以同時包含在由冒號分隔的單個表示式中,例如:
8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-3:11:47-49
GTID集可以包括單個GTID和GTID範圍的任意組合,甚至它可以包括源自不同伺服器的GTID。例如一個儲存在從庫gtid_executed系統變數中的GTID集可能如下:
565a6b0a-6f05-11e9-b95c-005056a5497f:1-20, 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-321
表示該從庫已從兩個主庫應用了事務(也有可能是在從庫執行的寫操作)。當從庫變數返回GTID集時,UUID按字母順序排列,並且數值間隔按升序合併。
MySQL伺服器中很多地方都用到GTID集,例如:gtid_executed和gtid_purged系統變數儲存的值是GTID集;START SLAVE的UNTIL SQL_BEFORE_GTIDS和UNTIL SQL_AFTER_GTIDS子句的值是GTID集;內建函式GTID_SUBSET()和GTID_SUBTRACT()需要GTID集作為輸入等。
(3)mysql.gtid_executed表
mysql.gtid_executed表結構如下:
mysql> desc mysql.gtid_executed;
+----------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------+------+-----+---------+-------+
| source_uuid | char(36) | NO | PRI | NULL | |
| interval_start | bigint(20) | NO | PRI | NULL | |
| interval_end | bigint(20) | NO | | NULL | |
+----------------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql.gtid_executed表記錄的是伺服器上已經執行事務的GTID。三個欄位分別表示發起事務的伺服器UUID、UUID集的起始和結束事務ID。對於單個GTID,後兩個欄位的值相同。
mysql.gtid_executed表供MySQL伺服器內部使用。當從庫禁用二進位制日誌時用該表記錄GTID,或者當二進位制日誌丟失時,可從該表查詢GTID狀態。RESET MASTER命令將重置mysql.gtid_executed表(清空表資料)。和所有系統表一樣,使用者不要修改該表。
僅當gtid_mode設定為ON或ON_PERMISSIVE時,GTID才儲存在mysql.gtid_executed表中。儲存的GTID值取決於是是否開啟二進位制日誌:
對於從庫,如果禁用了二進位制日誌記錄(skip-log-bin)或log_slave_updates,則伺服器將在該表中儲存每個事務的GTID。
如果啟用了二進位制日誌記錄,當重新整理二進位制日誌或重啟伺服器時,伺服器都會將當前二進位制日誌中所有事務的GTID寫入mysql.gtid_executed表。這種情況適用於主庫或啟用了二進位制日誌記錄的從庫。
啟用二進位制日誌記錄時,mysql.gtid_executed表並不儲存所有已執行事務的GTID的完整記錄,該資訊由gtid_executed全域性系統變數的值提供。如果伺服器意外停止,則當前二進位制日誌檔案中的GTID集不會儲存在mysql.gtid_executed表中。在MySQL例項恢復期間,這些GTID將從二進位制日誌檔案新增到表中。 即使伺服器處於只讀模式,MySQL伺服器也可以寫入mysql.gtid_executed表,這樣二進位制日誌檔案仍然可以在只讀模式下輪轉。如果無法訪問mysql.gtid_executed表時進行二進位制日誌檔案輪轉,則繼續使用二進位制日誌檔案儲存GTID,同時在伺服器上記錄警告資訊:
2019-06-03T09:37:07.777423Z 287633 [Warning] [MY-010015] [Repl] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
前面已經提到,mysql.gtid_executed表的記錄可能並不是完整的已執行GTID,而且有不可訪問的可能性(例如誤刪除此表),因此建議始終通過查詢@@global.gtid_executed(每次提交後更新)來確認MySQL伺服器的GTID狀態,而不是查詢mysql.gtid_executed表。 mysql.gtid_executed表可能隨著事務量的增多而快速膨脹,儲存了源自同一伺服器的大量不同的單個GTID,這些GTID構成一個範圍,例如:
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 1 | 329 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 330 | 330 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 331 | 331 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 332 | 332 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 333 | 333 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 334 | 334 |
+--------------------------------------+----------------+--------------+
為了節省空間,MySQL伺服器定期壓縮mysql.gtid_executed表,方法是將每個這樣的行集替換為跨越整個事務識別符號間隔的單行,如下所示:
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
|--------------------------------------+----------------+--------------|
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 1 | 334 |
...
通過設定gtid_executed_compression_period系統變數,可以控制壓縮表之前允許的事務數,從而控制壓縮率。此變數的預設值為1000,指的是在每1000次事務之後執行表的壓縮。將gtid_executed_compression_period設定為0將不執行壓縮。注意,啟用二進位制日誌時不使用gtid_executed_compression_period的值,並在每個二進位制日誌輪轉時壓縮mysql.gtid_executed表。mysql.gtid_executed表的壓縮由名為thread/sql/compress_gtid_table的專用前臺執行緒執行。此執行緒未在SHOW PROCESSLIST的輸出中列出,但可以從performance_schema.threads中查詢到:
mysql> select * from performance_schema.threads where name like '%gtid%'\G
*************************** 1. row ***************************
THREAD_ID: 44
NAME: thread/sql/compress_gtid_table
TYPE: FOREGROUND
PROCESSLIST_ID: 6
PROCESSLIST_USER: NULL
PROCESSLIST_HOST: NULL
PROCESSLIST_DB: NULL
PROCESSLIST_COMMAND: Daemon
PROCESSLIST_TIME: 438302
PROCESSLIST_STATE: Suspending
PROCESSLIST_INFO: NULL
PARENT_THREAD_ID: 1
ROLE: NULL
INSTRUMENTED: YES
HISTORY: YES
CONNECTION_TYPE: NULL
THREAD_OS_ID: 73199
RESOURCE_GROUP: SYS_default
1 row in set (0.00 sec)
mysql>
通常該執行緒都處於暫停狀態,只有當滿足條件時被喚醒,如達到gtid_executed_compression_period或發生了二進位制日誌輪轉(如flush logs等)時。
下面做個簡單實驗展示一下reset master的作用和影響。
(1)檢視從庫當前已經執行的GTID和二進位制日誌
show master status;
show variables like 'gtid%';
select * from mysql.gtid_executed;
show slave status\G
查詢結果如下:
mysql> show master status;
+---------------+----------+--------------+------------------+------------------------------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+------------------------------------------+
| binlog.000004 | 195 | | | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-6 |
+---------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)
mysql> show variables like 'gtid%';
+----------------------------------+------------------------------------------+
| Variable_name | Value |
+----------------------------------+------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-6 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
+----------------------------------+------------------------------------------+
6 rows in set (0.01 sec)
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 1 | 6 |
+--------------------------------------+----------------+--------------+
1 row in set (0.00 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.16.1.125
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: binlog.000001
Read_Master_Log_Pos: 1493
Relay_Log_File: hdp4-relay-bin.000005
Relay_Log_Pos: 315
Relay_Master_Log_File: binlog.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: 1493
Relay_Log_Space: 682
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: 1125
Master_UUID: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e
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: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-6
Executed_Gtid_Set: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-6
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
mysql>
所有查詢顯示的已經執行的GTID均為 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-6。
檢視當前的binlog結果如下:
[mysql@hdp4/usr/local/mysql/data]$more binlog.index
./binlog.000001
./binlog.000002
./binlog.000003
./binlog.000004
[mysql@hdp4/usr/local/mysql/data]$ls -lt binlog.*
-rw-r----- 1 mysql mysql 64 Jun 5 14:43 binlog.index
-rw-r----- 1 mysql mysql 195 Jun 5 14:43 binlog.000004
-rw-r----- 1 mysql mysql 239 Jun 5 14:43 binlog.000003
-rw-r----- 1 mysql mysql 239 Jun 5 14:43 binlog.000002
-rw-r----- 1 mysql mysql 1569 Jun 5 14:43 binlog.000001
[mysql@hdp4/usr/local/mysql/data]$
當前從庫有4個binlog檔案。
(2)在從庫執行reset master
(3)再次執行(1)的查詢,可以看到所有查詢的gtid_executed都置空,binlog檔案只有binlog.000001一個。說明reset master命令會清空gtid_executed變數和mysql.gtid_executed表,並會只保留一個初始的binlog檔案。
(4)在主庫上執行一些更新
use test;
create table t1(a int);
insert into t1 select 1;
(5)再次執行(1)的查詢,可以看到mysql.gtid_executed表中沒有記錄,其它查詢都已顯示出新執行GTID的值,複製正常。說明mysql.gtid_executed不記錄當前binlog中的GTID。
(6)在從庫上執行flush logs後,mysql.gtid_executed表中儲存了從reset master到flush logs之間binlog中的GTID。
從以上步驟看到,從庫上執行reset master只是清空從庫的gtid_executed,隨著複製的繼續,其gtid_executed的值也將隨之變化,對複製和主從資料一致性沒有影響。下面繼續實驗,看一下在主庫上執行reset master會產生哪些影響。
(7)在主庫上執行以下語句
use test;
delimiter //
create procedure p1(a int)
begin
declare i int default 1;
while i<=a do
insert into t1 values (i);
set i=i+1;
end while;
end;
//
delimiter ;
call p1(10000);
(8)在上一步執行期間,開一個新會話在主庫上執行reset master
(9)檢視從庫的複製狀態,從show slave status的輸出中可以看到複製的IO執行緒已停止,並報以下錯誤:
Last_IO_Errno: 13114
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'I/O error reading log event; the first event '' at 4, the last event read from './binlog.000001' at 201303, the last byte read from './binlog.000001' at 201303.'
由於主庫正在執行事務中間進行了reset master,從庫無法讀取主庫的二進位制日誌而報錯。更甚之,這些二進位制日誌的丟失是永久性的,結果很可能需要從頭重建複製。由此實驗得出的結論是,作為一條基本原則,不要隨意在主庫上執行reset master,這樣做極有可能導致複製停止或造成主從資料不一致等嚴重後果,而且不易恢復。
二、GTID生命週期
1. 典型事務的GTID生命週期
典型事務的GTID的生命週期包括以下步驟:
客戶端事務在主庫上執行並提交,此事務被分配一個GTID,該GTID由主伺服器的UUID和此伺服器上尚未使用的最小非零事務序列號組成。GTID作為Gtid_log_event緊接在事務本身之前,與事務本身一起被寫入主庫的二進位制日誌,這是一個原子操作。如果未將客戶端事務寫入二進位制日誌(例如,因為事務已被過濾掉,或者事務是隻讀的),則不會為其分配GTID。輪轉二進位制日誌或關閉MySQL例項時,都會將寫入之前二進位制日誌檔案的所有事務的GTID寫入mysql.gtid_executed表。
如果為事務分配了GTID,則將GTID新增到主庫gtid_executed系統變數(@@global.gtid_executed)的GTID集合中,這步將在事務提交後進行,並且與事務處理本身不是一個原子操作。gtid_executed系統變數包含所有已提交事務的GTID集,是應用事務的完整記錄,並在複製中用作表示伺服器狀態的標記。mysql.gtid_executed表不包含當前二進位制日誌檔案中的最新GTID記錄。
在將二進位制日誌資料傳輸到從庫並儲存在從庫的中繼日誌中之後,從庫讀取GTID並將其設定為gtid_next系統變數的值。這告訴從庫必須使用此GTID記錄下一個事務。
在處理事務本身之前,從庫首先讀取和檢查複製事務的GTID,不僅保證沒有先前事務具有此GTID,而且還保證沒有其它會話已經讀取此GTID但尚未提交相關事務。因此,如果多個客戶端同時提交同一GTID事務,則伺服器只允許其中一個執行。從庫的gtid_owned系統變數(@@global.gtid_owned)顯示當前正在使用的GTID以及擁有它的執行緒ID。如果已經使用了該GTID,通過自動跳過功能忽略具該事務,並且不會引發錯誤。
如果GTID尚未使用,則從庫應用複製的事務。gtid_next設定為主庫已分配的GTID,從庫不會為此事務生成新的GTID,而是使用儲存在gtid_next中的GTID。
如果在從庫上啟用了二進位制日誌記錄,則與主庫操作類似。GTID會在提交時作為Gtid_log_event原子寫入其二進位制日誌。當輪轉二進位制日誌或關閉MySQL例項時,都會將寫入之前二進位制日誌檔案的所有事務的GTID寫入mysql.gtid_executed表。
如果從庫禁用二進位制日誌記錄,則通過將GTID直接寫入mysql.gtid_executed表保留GTID。MySQL會在事務中附加一條語句,將GTID插入該表中。從MySQL 8.0開始,此操作對於DDL語句和DML語句都是原子操作。在這種情況下,mysql.gtid_executed表是從庫上應用事務的完整記錄。
從庫提交複製事務後,GTID將被新增到從庫gtid_executed系統變數(@@global.gtid_executed)的GTID集合中,這步將在事務應用後進行,並且與事務處理本身不是一個原子操作。
主庫上過濾掉的客戶端事務未分配GTID,因此它們不會新增到gtid_executed系統變數中的事務集中,也不會新增到mysql.gtid_executed表中。但是,在從庫上過濾掉的複製事務的GTID是持久化的。如果在從庫上啟用了二進位制日誌,則過濾掉的事務將作為Gtid_log_event寫入其二進位制日誌,後跟僅包含BEGIN和COMMIT語句的空事務。如果禁用二進位制日誌,則已過濾掉的事務的GTID將寫入mysql.gtid_executed表。為過濾掉的事務保留GTID可確保可以將mysti.gtid_executed表和gtid_executed系統變數中的GTID用GTID集表示。它還確保如果從庫重新連線到主庫,不會再次檢索過濾掉的事務。
在主庫或單執行緒複製的從庫上,GTID從1開始單向遞增且沒有間隙。但在多執行緒複製的從庫(slave_parallel_workers> 0)上,可以並行應用事務,因此複製的事務可能無序提交(除非設定了slave_preserve_commit_order = 1)。發生這種情況時,gtid_executed系統變數中的GTID集合將包含多個GTID範圍,它們之間存在間隙。多執行緒複製從庫上的間隙僅發生在最近應用的事務中,並在複製過程中填充。當使用STOP SLAVE語句乾淨地停止複製執行緒時,將應用正在進行的事務以填補空白。如果發生異常關閉,例如伺服器故障或使用KILL語句停止複製執行緒,則可能依然存在間隙。
下面實驗中將演示GTID存在間隙的情況。
(1)在從庫開啟多執行緒複製。
set global slave_parallel_workers=8;
stop slave;
start slave;
show processlist;
在最後的輸出中可以看到8個複製執行緒:
+------+-----------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+-----------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
| 4 | event_scheduler | localhost | NULL | Daemon | 1657 | Waiting on empty queue | NULL |
| 11 | wxy | localhost | NULL | Query | 0 | starting | show processlist |
| 1122 | system user | | NULL | Connect | 2 | Waiting for master to send event | NULL |
| 1123 | system user | | NULL | Query | 2 | Slave has read all relay log; waiting for more updates | NULL |
| 1124 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1125 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1126 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1127 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1128 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1129 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1130 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
| 1131 | system user | | NULL | Connect | 2 | Waiting for an event from Coordinator | NULL |
+------+-----------------+-----------+------+---------+------+--------------------------------------------------------+------------------+
12 rows in set (0.00 sec)
(2)在主庫上執行一個可以並行複製的長操作
因為並行複製預設是按資料庫分配執行緒的,所以建立多個庫表:
create database db1;
create database db2;
create database db3;
create database db4;
create database db5;
create database db6;
create database db7;
create database db8;
create table db1.t1(a int);
create table db2.t1(a int);
create table db3.t1(a int);
create table db4.t1(a int);
create table db5.t1(a int);
create table db6.t1(a int);
create table db7.t1(a int);
create table db8.t1(a int);
use test;
delimiter //
create procedure p1(a int)
begin
declare i int default 1;
while i<=a do
insert into db1.t1 values (i);
insert into db2.t1 values (i);
insert into db3.t1 values (i);
insert into db4.t1 values (i);
insert into db5.t1 values (i);
insert into db6.t1 values (i);
insert into db7.t1 values (i);
insert into db8.t1 values (i);
set i=i+1;
end while;
end;
//
delimiter ;
call p1(5000);
(3)在上一步正在執行過程當中殺掉從庫的mysqld程式,模擬異常當機。
ps -ef | grep mysqld | grep -v grep | awk {'print $2'} | xargs kill -9
(4)啟動從庫,不自動啟動複製。
mysqld_safe --defaults-file=/etc/my.cnf --skip-slave-start --slave_parallel_workers=8 &
(5)檢視從庫的GTID間隙
mysql> show variables like 'gtid_executed'\G
*************************** 1. row ***************************
Variable_name: gtid_executed
Value: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-42171:42173-42179:42181-42187:42189-42195:42197-42203:42205-42211:42213-42219:42221-42227:42229-42235:42237-42243:42245-42251:42253-42259:42261-42267:42269-42275:42277-42283:42285-42291:42293-42299:42301-42307:42309-42315:42317-42323:42325-42331:42333-42339:42341-42347:42349-42355:42357-42363:42365-42371:42373-42379:42381-42387:42389-42395:42397-42403:42405-42411:42413-42419:42421-42427:42429-42435:42437-42443:42445-42451:42453-42459:42461-42467:42469-42475:42477-42483:42485-42491:42493-42499:42501-42507:42509-42515:42517-42523:42525-42531:42533-42539:42541-42547:42549-42555:42557-42563:42565-42571:42573-42579:42581-42587:42589-42595:42597-42603:42605-42611:42613:42615-42619:42621:42623-42627:42629:42631-42635:42637:42639-42643
1 row in set (0.01 sec)
mysql>
GTID範圍的輸出是排序的,可以看到42172、42180、42188、42196 ... 這些GTID沒有出現在gtid_executed變數中,這些就是GTID間隙。查詢各個庫的記錄數(已經執行的事務)也是各不相同。
(6)啟動從庫的複製,檢查複製情況
start slave;
當所有事務都執行完後,再次檢視gtid_executed系統變數,已經合併為一個GTID範圍,所有間隙都已經被填充:
mysql> show variables like 'gtid_executed'\G
*************************** 1. row ***************************
Variable_name: gtid_executed
Value: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50021
1 row in set (0.00 sec)
mysql>
從show slave status的輸出和各個庫表的記錄數,也能確認複製正常。從這個簡單的實驗可以看到,啟用並行複製的從庫,在複製期間從庫例項異常終止會產生GTID間隙,但在例項重啟後複製會自動填充GTID間隙,最終達到主從資料一致。
2. GTID分配
典型情況是伺服器為已提交的事務生成新的GTID。寫入二進位制日誌的每個資料庫更改(DDL或DML)都會分配一個GTID。這包括自動提交的更改以及使用BEGIN和COMMIT或START TRANSACTION語句提交的更改。當資料庫,以及非表資料庫物件,例如過程、函式、觸發器、事件、檢視、使用者、角色在建立、更改或刪除時會分配GTID。授權語句和非事務表的更新也會分配GTID。
當二進位制日誌中的生成語句自動刪除表時,會為該語句分配GTID。例如,當具有開啟臨時表的使用者會話斷開連線時,將自動刪除臨時表,或者使用MEMORY儲存引擎的表在伺服器啟動後第一次訪問時會自動刪除。
未寫入二進位制日誌事務不會分配GTID。這包括回滾的事務,或在禁用二進位制日誌時執行的事務,或指定 sql_log_bin=0 時執行的事務,或空事務(begin;commit;)等。
XA事務為事務的XA PREPARE階段和事務的XA COMMIT或XA ROLLBACK階段分配了單獨的GTID。XA事務的準備階段是持久化的,以便使用者可以在發生故障時將其提交或回滾。因此,事務的兩個部分是分開復制的,因此兩個階段必須有自己單獨的GTID。
在以下特殊情況下,單個語句可以生成多個事務,因此會分配多個GTID:
呼叫儲存過程時,為過程提交的每個更新事務生成一個GTID。
多表DROP TABLE語句中包含任何不支援原子DDL儲存引擎的表(如myisam)或臨時表,會生成多個GTID。
注意,觸發器內的語句和觸發它的語句是在一個事務中,因此不會單獨分配GTID。MySQL不支援類似Oracle自治事務的功能。
3. gtid_next系統變數
gtid_next是會話系統變數。預設情況下,對於在使用者會話中提交的新事務,伺服器會自動生成並分配新的GTID。在從庫上應用事務時,將保留來自原始伺服器的GTID。可以通過設定gtid_next系統變數的會話值來更改此行為:
當gtid_next設定為AUTOMATIC(預設值),並且事務已提交併寫入二進位制日誌時,伺服器會自動生成並分配新的GTID。如果由於其它原因而回滾事務或未將事務寫入二進位制日誌,則伺服器不會生成和分配GTID。
如果將gtid_next設定為有效的單個GTID(由UUID和事務序列號組成,用冒號分隔),伺服器會將該GTID分配給下一個事務。只要事務提交,就會將此GTID分配並新增到gtid_executed。
在將gtid_next設定為特定GTID並且已提交或回滾事務之後,必須在任何其它語句之前發出顯式SET @@SESSION.gtid_next語句。如果不想分配更多GTID,可以將此選項值的值設定回AUTOMATIC。
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50057 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
+----------------------------------+----------------------------------------------+
6 rows in set (0.00 sec)
mysql> set gtid_next='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50058';
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50057 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50058 |
| gtid_owned | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50058 |
| gtid_purged | |
+----------------------------------+----------------------------------------------+
6 rows in set (0.00 sec)
mysql> begin;commit;
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50058 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50058 |
| gtid_owned | |
| gtid_purged | |
+----------------------------------+----------------------------------------------+
6 rows in set (0.01 sec)
mysql> create table t1(a int);
ERROR 1837 (HY000): When @@SESSION.GTID_NEXT is set to a GTID, you must explicitly set it to a different value after a COMMIT or ROLLBACK. Please check GTID_NEXT variable manual page for detailed explanation. Current @@SESSION.GTID_NEXT is '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50058'.
mysql> set gtid_next=automatic;
Query OK, 0 rows affected (0.00 sec)
mysql> create table t1(a int);
Query OK, 0 rows affected (0.01 sec)
mysql>
前面已經提到,從庫的SQL執行緒應用複製事務時使用此技術,將@@SESSION.gtid_next顯式設定為在源伺服器上分配給事務的GTID。這意味著保留來自原始伺服器的GTID,而不是由從庫生成和分配的新GTID。即使從庫禁用log_bin或log_slave_updates,或者事務是空操作或在從庫上過濾掉時,GTID也會新增到從庫上的gtid_executed。
客戶端可以通過在執行事務之前將@@SESSION.gtid_next設定為特定GTID來模擬複製的事務。mysqlbinlog使用此技術生成二進位制日誌的轉儲,客戶端可以重放該轉儲以保留GTID。通過客戶端提交的模擬複製事務完全等同於通過複製應用程式執行緒提交的複製事務,並且事後無法區分它們。
[mysql@hdp2/usr/local/mysql/data]$mysqlbinlog --base64-output=decode-rows binlog.000001 | tail -15
/*!80001 SET @@session.original_commit_timestamp=1559800983100268*//*!*/;
/*!80014 SET @@session.original_server_version=80016*//*!*/;
/*!80014 SET @@session.immediate_server_version=80016*//*!*/;
SET @@SESSION.GTID_NEXT= '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50059'/*!*/;
# at 13622355
#190606 14:03:03 server id 1125 end_log_pos 13622465 CRC32 0xbf6bf581 Query thread_id=184 exec_time=0 error_code=0 Xid = 601312
SET TIMESTAMP=1559800983/*!*/;
/*!80013 SET @@session.sql_require_primary_key=0*//*!*/;
create table t1(a int)
/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[mysql@hdp2/usr/local/mysql/data]$
4. gtid_purged系統變數
gtid_purged是全域性系統變數。@@GLOBAL.gtid_purged中的GTID集包含已在伺服器上提交但在伺服器上的任何二進位制日誌檔案中不存在的所有事務的GTID。gtid_purged是gtid_executed的子集。以下類別的GTID位於gtid_purged中:
在從庫上禁用二進位制日誌記錄時提交的複製事務的GTID。
已清除的二進位制日誌檔案中事務的GTID。
通過語句SET @@GLOBAL.gtid_purged明確新增到集合中的GTID。
第一種情況:
[mysql@hdp4~]$mysqladmin -uroot -p123456 shutdown
mysqladmin: [Warning] Using a password on the command line interface can be insecure.
[mysql@hdp4~]$mysqld_safe --defaults-file=/etc/my.cnf --skip-log-bin &
[1] 97160
[mysql@hdp4~]$2019-06-06T06:25:56.483366Z mysqld_safe Logging to '/usr/local/mysql/data/hdp4.err'.
2019-06-06T06:25:56.544557Z mysqld_safe Starting mysqld daemon with databases from /usr/local/mysql/data
[mysql@hdp4~]$mysql -uroot -p123456 -e "show variables like 'gtid_purged'"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_purged | |
+---------------+-------+
... 主庫執行更新 ...
[mysql@hdp4~]$mysql -uroot -p123456 -e "show variables like 'gtid_purged'"
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+--------------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------------+
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50060 |
+---------------+--------------------------------------------+
[mysql@hdp4~]$
第二種情況:
mysql> show binary logs;
+---------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+---------------+-----------+-----------+
| binlog.000001 | 13683049 | No |
+---------------+-----------+-----------+
1 row in set (0.00 sec)
mysql> show variables like 'gtid_purged';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_purged | |
+---------------+-------+
1 row in set (0.00 sec)
mysql> flush logs;
Query OK, 0 rows affected (0.01 sec)
mysql> purge master logs to 'binlog.000002';
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like 'gtid_purged';
+---------------+----------------------------------------------+
| Variable_name | Value |
+---------------+----------------------------------------------+
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50060 |
+---------------+----------------------------------------------+
1 row in set (0.01 sec)
mysql>
第三種情況:
mysql> show variables like 'gtid%';
+----------------------------------+--------------------------------------------------+
| Variable_name | Value |
+----------------------------------+--------------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50060-50061 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50060 |
+----------------------------------+--------------------------------------------------+
6 rows in set (0.01 sec)
mysql> set gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50060-50061';
ERROR 1229 (HY000): Variable 'gtid_purged' is a GLOBAL variable and should be set with SET GLOBAL
mysql> set global gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50060-50061';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the added gtid set must not overlap with @@GLOBAL.GTID_EXECUTED
mysql> set global gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:50061';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the new value must be a superset of the old value
mysql> set global gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50059';
ERROR 3546 (HY000): @@GLOBAL.GTID_PURGED cannot be changed: the new value must be a superset of the old value
mysql> set global gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50059:50060';
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50061 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-50060 |
+----------------------------------+----------------------------------------------+
6 rows in set (0.01 sec)
mysql>
可以更改gtid_purged的值,以便在伺服器上記錄已應用某個GTID集中的事務,儘管它們不存在於伺服器上的任何二進位制日誌中。將GTID新增到gtid_purged時,它們也會新增到gtid_executed中。來看一個相對極端的例子。
(1)從庫清除二進位制日誌和gtid_executed資訊
reset master;
stop slave;
reset slave all;
show variables like 'gtid%';
發出RESET MASTER會導致gtid_purged和gtid_executed的全域性值重置為空字串。最後的輸出為:
mysql> show variables like 'gtid%';
+----------------------------------+-----------+
| Variable_name | Value |
+----------------------------------+-----------+
| gtid_executed | |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | |
+----------------------------------+-----------+
6 rows in set (0.00 sec)
(2)重置複製
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
show slave status\G
最後的輸出為:
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 172.16.1.125
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: binlog.000001
Read_Master_Log_Pos: 2731306
Relay_Log_File: hdp4-relay-bin.000002
Relay_Log_Pos: 363
Relay_Master_Log_File: binlog.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: 1007
Last_Error: Error 'Can't create database 'test'; database exists' on query. Default database: 'test'. Query: 'create database test'
Skip_Counter: 0
Exec_Master_Log_Pos: 155
Relay_Log_Space: 2731721
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: 1007
Last_SQL_Error: Error 'Can't create database 'test'; database exists' on query. Default database: 'test'. Query: 'create database test'
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1125
Master_UUID: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e
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: 190606 15:52:51
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-10005
Executed_Gtid_Set:
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)
可以看到,從主庫讀到的GTID已經到了10005,但沒有已經執行的GTID。實際上這些事務都已經在從庫應用了,只是由於reset master而沒有留下執行的痕跡,所以要從1開始執行,而重複執行事務造成了錯誤。
(3)將所有已讀的GTID都標記為已執行,然後重啟複製
set global gtid_purged='8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-10005';
stop slave;
start slave;
show slave status\G
show variables like 'gtid%';
從show slave status的輸出中可以看到複製已恢復正常,最後的輸出為:
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-10005 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-10005 |
+----------------------------------+----------------------------------------------+
6 rows in set (0.00 sec)
伺服器啟動時,將初始化gtid_executed和gtid_purged系統變數中的GTID集。每個二進位制日誌檔案都以事件Previous_gtids_log_event開頭,該事件包含所有先前二進位制日誌檔案中的GTID集(由前一個檔案的Previous_gtids_log_event中的GTID和前一個檔案本身中每個Gtid_log_event的GTID組成)。最舊和最新的二進位制日誌檔案中的Previous_gtids_log_event的內容用於計算伺服器啟動時的gtid_executed和gtid_purged的GTID集:
gtid_executed是最新二進位制日誌檔案中Previous_gtids_log_event中的GTID、該二進位制日誌檔案中的事務的GTID、儲存在mysql.gtid_executed表中的GTID,三者的並集。此GTID集包含伺服器上已使用(或顯式新增到gtid_purged)的所有GTID,無論它們當前是否位於伺服器上的二進位制日誌檔案中。它不包括當前正在伺服器上正在處理事務的GTID(@@GLOBAL.gtid_owned)。
gtid_purged的計算方法是首先新增最新二進位制日誌檔案Previous_gtids_log_event中的GTID,再新增該二進位制日誌檔案中事務的GTID。此步驟提供當前或曾經記錄在伺服器上的二進位制日誌中的GTID集(gtids_in_binlog)。然後從gtids_in_binlog中減去最舊的二進位制日誌檔案中的Previous_gtids_log_event中的GTID。此步驟提供當前記錄在伺服器上的二進位制日誌中的GTID集(gtids_in_binlog_not_purged)。最後,從gtid_executed中減去gtids_in_binlog_not_purged。結果是伺服器上已經執行,但當前未記錄在伺服器上的二進位制日誌檔案中的GTID集,此結果用於初始化gtid_purged。
下面用一個的例子說明gtid_executed和gtid_purged的計算過程。
mysql> show variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name | Value |
+----------------------------------+----------------------------------------------+
| gtid_executed | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-11006 |
| gtid_executed_compression_period | 1000 |
| gtid_mode | ON |
| gtid_next | AUTOMATIC |
| gtid_owned | |
| gtid_purged | 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-10005 |
+----------------------------------+----------------------------------------------+
6 rows in set (0.01 sec)
伺服器重啟重啟後gtid_executed的值為1-11006,gtid_purged值為1-10005,下面倒推這些數是怎麼得來的。
當前有三個二進位制日誌檔案,最老的是binlog.000001,最新的是binlog.000003:
[mysql@hdp4/usr/local/mysql/data]$ls -lt binlog.*
-rw-r----- 1 mysql mysql 195 Jun 6 16:02 binlog.000003
-rw-r----- 1 mysql mysql 48 Jun 6 16:02 binlog.index
-rw-r----- 1 mysql mysql 275358 Jun 6 16:01 binlog.000002
-rw-r----- 1 mysql mysql 199 Jun 6 16:00 binlog.000001
[mysql@hdp4/usr/local/mysql/data]$
binlog.000001的Previous-GTIDs為空,檔案本身也沒有GTID:
[mysql@hdp4/usr/local/mysql/data]$mysqlbinlog --base64-output=decode-rows binlog.000001
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#190606 15:52:18 server id 1127 end_log_pos 124 CRC32 0x57a0d989 Start: binlog v 4, server v 8.0.16 created 190606 15:52:18 at startup
ROLLBACK/*!*/;
# at 124
#190606 15:52:18 server id 1127 end_log_pos 155 CRC32 0x69663b35 Previous-GTIDs
# [empty]
# at 155
#190606 16:00:15 server id 1127 end_log_pos 199 CRC32 0x08490198 Rotate to binlog.000002 pos: 4
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[mysql@hdp4/usr/local/mysql/data]$
binlog.000002的Previous-GTIDs由binlog.000001的Previous-GTIDs和binlog.000001本身的GTID組成,由於兩者都為空,所以binlog.000002的Previous-GTIDs也為空:
[mysql@hdp4/usr/local/mysql/data]$mysqlbinlog --base64-output=decode-rows binlog.000002 | head -10
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#190606 16:00:15 server id 1127 end_log_pos 124 CRC32 0x66692a6f Start: binlog v 4, server v 8.0.16 created 190606 16:00:15
# at 124
#190606 16:00:15 server id 1127 end_log_pos 155 CRC32 0x2c439049 Previous-GTIDs
# [empty]
# at 155
#190606 16:00:35 server id 1125 end_log_pos 239 CRC32 0x4bace6c6 GTID last_committed=0 sequence_number=1 rbr_only=no original_committed_timestamp=1559808035672038 immediate_commit_timestamp=1559808035637464 transaction_length=180
[mysql@hdp4/usr/local/mysql/data]$
binlog.000002本身的GTID為10006-11006:
[mysql@hdp4/usr/local/mysql/data]$mysqlbinlog --base64-output=decode-rows binlog.000002 > binlog.000002.txt
[mysql@hdp4/usr/local/mysql/data]$grep @@SESSION.GTID_NEXT binlog.000002.txt | head -1
SET @@SESSION.GTID_NEXT= '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:10006'/*!*/;
[mysql@hdp4/usr/local/mysql/data]$grep @@SESSION.GTID_NEXT binlog.000002.txt | tail -2
SET @@SESSION.GTID_NEXT= '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:11006'/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
[mysql@hdp4/usr/local/mysql/data]$
binlog.000003的Previous-GTIDs由binlog.000002的Previous-GTIDs和binlog.000002本身的GTID組成,所以binlog.000003的Previous-GTIDs為10006-11006:
[mysql@hdp4/usr/local/mysql/data]$mysqlbinlog --base64-output=decode-rows binlog.000003
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#190606 16:02:02 server id 1127 end_log_pos 124 CRC32 0x9443c747 Start: binlog v 4, server v 8.0.16 created 190606 16:02:02 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
# at 124
#190606 16:02:02 server id 1127 end_log_pos 195 CRC32 0xbcd9d46f Previous-GTIDs
# 8eed0f5b-6f9b-11e9-94a9-005056a57a4e:10006-11006
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[mysql@hdp4/usr/local/mysql/data]$
binlog.000003本身沒有GTID。
mysql.gtid_executed的記錄為:
mysql> select * from mysql.gtid_executed;
+--------------------------------------+----------------+--------------+
| source_uuid | interval_start | interval_end |
+--------------------------------------+----------------+--------------+
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 1 | 10005 |
| 8eed0f5b-6f9b-11e9-94a9-005056a57a4e | 10006 | 11006 |
+--------------------------------------+----------------+--------------+
2 rows in set (0.00 sec)
按照gtid_executed的計算方法,gtid_executed為10006-11006和1-11006的並集,於是得出1-11006。
gtid_purged的計算過程如下:
gtids_in_binlog_not_purged = gtids_in_binlog - binlog.000001的Previous-GTIDs = gtids_in_binlog
gtids_in_binlog = binlog.000003的Previous-GTIDs + binlog.000003本身的GTID = binlog.000003的Previous-GTIDs = 10006-11006
gtid_purged = gtid_executed - gtids_in_binlog_not_purged = 1-11006 - 10006-11006 = 1-10005
三、GTID自動定位
GTID是用來代替傳統複製的方法,GTID複製與普通複製模式的最大不同在於,啟動和恢復複製時能夠自動定位,而不需要指定二進位制日誌檔名和位置。配置非GTID複製時,需要在CHANGE MASTER TO語句中包含MASTER_LOG_FILE或MASTER_LOG_POS選項,用於指示從主庫複製的開始點。但對於GTID,從庫不需要此非本地資料,其與主庫同步的所有資訊都直接從複製資料流中獲取,因此不需要指定這些選項。要使用基於GTID的複製啟動從庫,推薦啟用MASTER_AUTO_POSITION選項。
預設情況下禁用MASTER_AUTO_POSITION選項。如果在從庫上啟用了多源複製,則需要為每個適用的複製通道設定該選項。設定MASTER_AUTO_POSITION=0會使從庫恢復為基於檔案的複製,這時必須指定MASTER_LOG_FILE或MASTER_LOG_POS選項。當從庫啟用GTID(GTID_MODE = ON、ON_PERMISSIVE或OFF_PERMISSIVE)並使用MASTER_AUTO_POSITION選項時,將啟用自動定位以連線到主庫。主庫必須設定GTID_MODE = ON才能使連線成功。
在初始握手中,從庫向主庫傳送一個GTID集,其中包含已經收到、已提交或兩者都已完成的事務。此GTID集等於@@GLOBAL.gtid_executed系統變數與select received_transaction_set from performance_schema.replication_connection_status查詢結果的並集。主庫會比較其二進位制日誌中記錄的所有事務和從庫發來的GTID集合,並將不包括在從庫傳送的GTID集中的事務全部傳送給從庫。自動跳過功能可確保同一事務不會應用兩次。如果從庫缺失的GTID已經被主庫清除(purge),則複製中斷,主庫將錯誤ER_MASTER_HAS_PURGED_REQUIRED_GTIDS傳送給從庫。主庫錯誤日誌的ER_FOUND_MISSING_GTIDS警告訊息中將列出丟失事務的GTID。從庫無法自動解決此問題,嘗試在不啟用MASTER_AUTO_POSITION選項的情況下重新連線主庫只會導致已清除事務在從庫上的丟失。可以考慮修改主庫上的binlog_expire_logs_seconds系統引數值(預設為2592000秒,即30天),以確保不再發生二進位制日誌還需要時已經被提前清除的情況。下面模擬一下這個場景。
-- 從庫停止複製
stop slave;
-- 主庫做更新
truncate table t1;
-- 主庫修改binlog檔名,模擬事務丟失
mysql -uroot -p123456 -e "show master status;"
mv binlog.000001 binlog.000001.bak
-- 從庫啟動複製
start slave;
show slave status\G
會看到1236錯誤:
會看到1236錯誤:
Last_IO_Errno: 13114
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'Cannot replicate because the master purged required binary logs. Replicate the missing transactions from elsewhere, or provision a new slave from backup. Consider increasing the master's binary log expiration period. To find the missing transactions, see the master's error log or the manual for GTID_SUBTRACT.'
主庫的錯誤日誌中會顯示如下資訊:
2019-06-11T00:18:14.500248Z 207 [ERROR] [MY-010958] [Server] Could not open log file.
2019-06-11T00:18:14.500299Z 207 [Warning] [MY-011809] [Server] Cannot replicate to server with server_uuid='565a6b0a-6f05-11e9-b95c-005056a5497f' because the present server has purged required binary logs. The connecting server needs to replicate the missing transactions from elsewhere, or be replaced by a new server created from a more recent backup. To prevent this error in the future, consider increasing the binary log expiration period on the present server. The missing transactions are '8eed0f5b-6f9b-11e9-94a9-005056a57a4e:1-11007'.
主庫二進位制日誌修改成正確的檔名,重啟從庫複製後恢復正常:
-- 主庫
mv binlog.000001.bak binlog.000001
-- 從庫
stop slave;
start slave;
show slave status\G
如果在事務交換期間發現從庫已經在GTID中接收或提交了事務,但主庫本身沒有它們的記錄,則複製停止,主庫將錯誤ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER傳送給從庫。當沒有配置sync_binlog=1的主庫遇到電源故障或作業系統崩潰,導致尚未同步到二進位制日誌檔案的已提交事務已被從庫接收,則會發生這種情況。如果主庫重新提交事務,可能導致主庫和從庫對不同的事務使用相同的GTID,這時只能根據需要對各個事務手動解決衝突(例如手工設定gtid_next)。如果問題僅在於主庫缺少事務,則可以主從切換,允許它跟上覆制拓撲中的其它伺服器,然後在需要時再次將其設定為主庫。可見sync_binlog=1對於主從資料一致至關重要,這也是MySQL 8的預設配置值。下面模擬一下這個場景。
-- 主庫reset master
reset master;
-- 從庫重啟複製
stop slave;
start slave;
show slave status\G
會看到以下錯誤:
Last_IO_Errno: 13114
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'
重新配置從庫以恢復複製:
reset master;
stop slave;
reset slave all;
change master to
master_host = '172.16.1.125',
master_port = 3306,
master_user = 'repl',
master_password = '123456',
master_auto_position = 1;
start slave;
show slave status\G
————————————————
版權宣告:本文為CSDN博主「wzy0623」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/wzy0623/article/details/91047395
About Me
........................................................................................................................ ● 本文作者:小麥苗,部分內容整理自網路,若有侵權請聯絡小麥苗刪除 ● 本文在itpub、部落格園、CSDN和個人微 信公眾號( xiaomaimiaolhr)上有同步更新 ● 本文itpub地址: http://blog.itpub.net/26736162 ● 本文部落格園地址: http://www.cnblogs.com/lhrbest ● 本文CSDN地址: https://blog.csdn.net/lihuarongaini ● 本文pdf版、個人簡介及小麥苗雲盤地址: http://blog.itpub.net/26736162/viewspace-1624453/ ● 資料庫筆試面試題庫及解答: http://blog.itpub.net/26736162/viewspace-2134706/ ● DBA寶典今日頭條號地址: http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826 ........................................................................................................................ ● QQ群號: 230161599 、618766405 ● 微 信群:可加我微 信,我拉大家進群,非誠勿擾 ● 聯絡我請加QQ好友 ( 646634621 ),註明新增緣由 ● 於 2020-02-01 06:00 ~ 2020-02-31 24:00 在西安完成 ● 最新修改時間:2020-02-01 06:00 ~ 2020-02-31 24:00 ● 文章內容來源於小麥苗的學習筆記,部分整理自網路,若有侵權或不當之處還請諒解 ● 版權所有,歡迎分享本文,轉載請保留出處 ........................................................................................................................ ● 小麥苗的微店: https://weidian.com/s/793741433?wfr=c&ifr=shopdetail ● 小麥苗出版的資料庫類叢書: http://blog.itpub.net/26736162/viewspace-2142121/ ● 小麥苗OCP、OCM、高可用網路班: http://blog.itpub.net/26736162/viewspace-2148098/ ● 小麥苗騰訊課堂主頁: https://lhr.ke.qq.com/ ........................................................................................................................ 使用 微 信客戶端掃描下面的二維碼來關注小麥苗的微 信公眾號( xiaomaimiaolhr)及QQ群(DBA寶典)、新增小麥苗微 信, 學習最實用的資料庫技術。
........................................................................................................................ |
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26736162/viewspace-2675273/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL 8 複製(五)——配置GTID複製MySql
- MySQL主從複製之GTID複製MySql
- MySQL 8 複製(三)——延遲複製與部分複製MySql
- MySQL 傳統複製與 GTID 複製原理及操作詳解MySql
- MySQL 8 複製(十)——組複製效能與限制MySql
- MySQL 8 複製(一)——非同步複製MySql非同步
- MySQL 8 複製(二)——半同步複製MySql
- Mysql基於GTID的複製模式MySql模式
- Mysql 基於GTID主從複製MySql
- MySQL GTID複製錯誤修復演示MySql
- MySQL 8 複製(九)——組複製聯機配置MySql
- MySQL 8 複製(八)——組複製安裝部署MySql
- 16.1.3 使用GTID 配置複製
- MySQL 5.7基於GTID的主從複製MySql
- MySQL 5.7 基於GTID搭建主從複製MySql
- MySQL GTID複製中斷修復過程MySql
- MySQL8.0輕鬆搞定GTID組複製MySql
- MySQL 8 複製(七)——組複製基本原理MySql
- MySQL 複製全解析 Part10 基於GTID的MySQL複製的一些限制MySql
- MySQL 8 複製(六)——拓撲與效能MySql
- Mysql 8.4.0 結合 Docker 搭建GTID主從複製,以及傳統主從複製MySqlDocker
- MySQL 複製全解析 Part 9 一步步搭建基於GTID的MySQL複製MySql
- MySQL運維實戰(7.1) 開啟GTID複製MySql運維
- 專案02(Mysql gtid複製故障處理01)MySql
- MySQL8.0輕鬆搞定GTID主從複製MySql
- MySQL8.0輕鬆搞定GTID主主複製MySql
- 線上將傳統模式複製改為GTID複製模式模式
- mysql複製--主從複製配置MySql
- 淺複製與深複製
- 淺複製和深複製的概念與值複製和指標複製(引用複製)有關 淺複製 “指標複製 深複製 值複製指標
- MySQL複製MySql
- MySQL 8 複製效能的增強MySql
- MySQL主從複製之半同步複製MySql
- MySQL主從複製之非同步複製MySql非同步
- mysql 複製原理與實踐MySql
- MySQL 複製全解析 Part 11 使用xtrabackup建立MySQL複製MySql
- mysql5.7主從複製,主主複製MySql
- MySQL 主從複製之多執行緒複製MySql執行緒