淺談GTID及簡單測試
今天簡單介紹一下GTID,並有部分相關實驗。
GTID相信大家都不陌生,GTID的英文全稱為Global Transaction Identifier,在MySQL主從架構中應用廣泛。
GTID是由“UUID:事務號“組成的,GTID是基於事務的,在主從架構中,在主庫每提交一個事務都會對應生成一個GTID號,GTID支援語句和行格式的複製,而且在主庫提交的事務只會在從庫應用一次,保證了一致性。
一、下面說一下GTID的優缺點1、GTID優點:
①複製安全性高,搭建主從相比與傳統的(基於binlog和position號)複製要簡單;
②GTID在主庫是連續的,保證了資料的一致性;
③故障切換時間更短,降低服務故障時間等。
2、GTID缺點:
①主從表儲存引擎必須一致,不能一個innodb一個myisam,這樣會導致主備資料不一致,因為GTID是和事務之間一一對應的;
②不允許一個sql同時更新事務引擎表和非事務引擎表;
③在主從模式下,需要主庫從庫同時開啟和關閉gtid;
④不支援create table .... as select 語句,在主庫會直接報錯的;
⑤不支援create/drop temporary table語句;
⑥不支援傳統的複製模式,跳過錯誤的語句(sql_slave_skip_counter)。
二、GTID部分相關引數介紹
①gtid_mode:控制是否啟用gtid, on/on_permissiv/off_perissiv/off;
②enforce_gtid_consistency:用於保證GTID一致性的,有on/off/warn三個值
on:不允許任何事務違反gtid一致性;
off:允許所有事務違反gtid一致性;
warn:允許所有事務違反gtid一致性,但是這種情況下會生成告警,mysql5.7.6新增。
③gtid_executed:它是一個gtid的範圍,表示的是已經執行過的所有的gtid事務集或者是由於語句人為設定的gtid。
④gtid_purged:表示的是已經執行過但是已經被purge掉的gtid,也是一個範圍,它是executed的一個子集。在以下幾種情況下,gtid_purged會有值:
禁用二進位制日誌的情況下,提交事務的GTID;
寫入二進位制日誌的GTID已經被刪除了;
使用語句顯示的指定gtid purged:set @@global.gtid_purged=...。
⑤gtid_next:是會話級別的變數,對於提交的事務,會自動分配新的gtid,預設值為automatic,也可以顯示指定gtid_next,來指定下一個事務的GTID號。
⑥gtid_owned:該變數主要提供內部使用,儲存的是伺服器上當前正在使用的所有GTID列表,以及擁有它的執行緒的ID。
三、進行幾個GTID的測試
測試環境,已經開啟GTID複製模式,搭建過程省略。
1
1236報錯的情況
情況1:
主庫真正的purge了正在使用的binlog,或者執行了reset master;,這種情況不用過多解釋,從庫肯定不能找到主庫的binlog而報錯了。
情況2:
從庫gtid不連續,出現了空洞。
1) 如何模擬空洞的出現:
①在從庫引數檔案中指定slave_skip_errors=1050;
②在從庫test庫中建立一張表;
③在主庫test庫中建立同樣的一張表,此時如果沒有skip引數的話,從庫肯定會報錯;
④此時在主庫向該表中插入一條資料,此時從庫gtid就出現了空洞;
⑤檢視show slave status\G;
Executed_Gtid_Set: 9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14
對於gtid空洞的情況,假如在需要進行維護的時候,需要重新進行執行change master to語句,可能會出現1236報錯,模擬如下:
2)前期準備:
①切換和刪除binlog:
由於測試庫沒有什麼業務,binlog沒有切換,為了模擬生產上binlog的情況,我們手動進行binlog切換和刪除。
mysql> flush logs; //首先重新整理一個日誌,這樣從庫也就指向了新的binlog
Query OK, 0 rows affected (0.04 sec)
mysql> show binary logs; //檢視存在的binlog
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000022 | 2216 |
| mysql-bin.000023 | 194 |
+------------------+-----------+
2 rows in set (0.01 sec)
mysql> purge binary logs to 'mysql-bin.000023'; //將該binlog之前的binlog全部purge掉,這樣也就 模擬了主庫binlog超過了expire log days時間後對binlog進行的清理,這樣binlog裡面之前的gtid對應的操作都不存在了。
mysql> show binary logs; //再次檢視只留下了最新的binlog
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000023 | 194 |
+------------------+-----------+
1 row in set (0.00 sec)
3) 復現1236報錯:
此時主庫老的gtid對應的操作,在binlog中已經被purge了,而從庫由於skip引數已經出現了gtid空洞,此時簡單的stop slave;start slave;是不會有什麼問題的。
檢視gtid相關資訊:
mysql> show global variables like '%gtid%';
gtid_executed:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,
b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14 //這個值是和show slave status對應的。
gtid_purged :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1732,
b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-11 //由於從庫的binlog沒有purge,所以它的值沒有變化。
如果此時由於某種原因,我們需要重新執行change master to...操作,此時就會報錯1236:
mysql> stop slave;
mysql> change master to .....
mysql> start slave;
mysql> show slave status\G;
Last_IO_Errno: 1236
Last_IO_Error: Got fatal error 1236 from master when reading data from binary log: 'The slave is connecting using CHANGE MASTER TO MASTER_AUTO_POSITION = 1, but the master has purged binary logs containing GTIDs that the slave requires.
這是因為:當MASTER_AUTO_POSITION = 1時,在初始連線握手時也就是執行change的時候,從庫傳送一個GTID集,其中包含它已經接收或提交的事務,或同時包含這兩種事務(也就是executed的值)。主庫的響應方式是傳送所有記錄在二進位制日誌中的事務,這些事務的GTID不在副本傳送的GTID集合中。這種交換確保主庫只發送從庫還沒有記錄或提交的GTID的事務。
有了上面的理解就不難明白:從庫傳送的gtid集為:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1737:1739-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14,是不連續的,缺少9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1738,而主庫會發送到從庫的是不包含在從庫傳送出來的gtid的,所以從庫還會去主庫找9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1738這個gtid,而該gtid所在的binlog已經被purge掉了,所以會報1236的錯誤。
3) 修復:
我們需要在從庫執行purge,將gtid空洞補齊:
mysql> stop slave;
mysql> reset master;
mysql> set global gtid_purged='9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14';
mysql> reset slave all;
mysql> change master to ....;
mysql> start slave;
mysql> show slave status\G;
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
注意:在執行set global gtid_purged的時候,gtid_purged需要為空,我們需要執行reset master將從庫的gtid_purged清空,這樣gtid就會連續了,從庫傳送gtid集的時候就包含了空洞這部分gtid,所以主庫也就不會向從庫傳送該gtid,從庫也就不會去主庫找該gtid,也就會從最新的gtid去拉取,這樣就不會出現1236報錯了。
當然還有其他情況會出現1236的報錯,比如主庫中包含其他例項的gtid(可能是該主庫是從其他例項上摘下來的從庫),也就是說主庫的gtid集包含沒在該架構中的例項的gtid,而從庫不包含該例項的gtid,當從庫執行change的時候大概率也會有1236報錯。大概就是這一個意思,這裡就不做測試了,想要說明的就是從庫在執行change的時候它包含的gtid集要和主庫的一致,也就是說主庫gtid集中有的uuid,從庫中也得有,不管該gtid有沒有變化有沒有用到。
2
從庫gtid_purged值什麼時候會變化
情況1:
從庫手動執行set global gtid_purged=....;這個上面已經執行過了。
情況2:
禁用二進位制日誌的情況下或者關閉log_slave_updates引數,提交事務的GTID;這種情況說的是當從庫在禁用binlog的情況下,gtid purged該值是都在變化的,因為從庫沒有binlog了,也就沒有purge binlog這一說了。
情況3:
當從庫binlog被purge掉的時候。
此時的gtid executed和gtid purged分別為:
gtid_executed :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14
gtid_purged:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1740,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14
需要注意的是這兩個值平時是不一樣的,executed表示已經接收執行到的gtid,而purged表示已經被purge掉的gtid。
我們先flush一個binlog,然後再purge:
mysql> flush logs;
Query OK, 0 rows affected (0.09 sec)
mysql> show binary logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 2217 |
| mysql-bin.000002 | 194 |
+------------------+-----------+
2 rows in set (0.00 sec)
mysql> purge binary logs to 'mysql-bin.000002';
Query OK, 0 rows affected (0.11 sec)
mysql> show global variables like '%gtid%';
gtid_executed:9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14
gtid_purged :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1-1748,b30eac1b-7398-11ec-befa-fa7f4a6c5000:1-14
可以看到gtid_purged的值發生了變化,包含的是我們已經purge掉的gtid,因為測試庫沒有業務,所以executed和purged在這裡顯示出來是一樣的。
3
跳過gtid
當從庫出現主鍵衝突,或者其他報錯的時候,我們根據實際情況來進行手動跳過gtid,步驟如下:
mysql> stop slave;
mysql> set gtid_next=...; //該gtid號可以根據報錯提示去找。
mysql> begin;commit;
mysql> set gtid_next='automatic';
mysql> start slave;
這樣也就解釋了gtid_next可以顯示的指定,預設情況下是automatic的。
需要注意的是,在生產上從庫出現gtid問題的時候,不要一昧的去跳過,需要找到問題的原因,是因為從庫沒有開啟read only還是什麼原因,要搞清楚,否則可能造成主從資料不一致。
4
gtid_owned什麼時候顯示
在平時使用過程中該值是不容易被發現,被捕捉的。這裡我們模擬一個大事務來看看:
在主庫執行,模擬大事務:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update d set id=sleep(20) + 111 where id=1; //這裡d是一個測試表,可以自行建立。
Query OK, 1 row affected, 1 warning (20.09 sec)
Rows matched: 1 Changed: 1 Warnings: 1
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
在從庫檢視:
mysql> show global variables like '%gtid%';
gtid_owned :9c94ddb7-7397-11ec-a69f-fa37ddbb3200:1752#5
它是由gtid和執行緒組成,我們可以使用show processlist,檢視執行緒5:
mysql> show processlist;
| 5 | system user | | test | Connect | 34 | User sleep | update d set id=sleep(20) + 111 where id=1 |
正式主庫同步過來的操作。
5
gtid_executed表簡介
僅當gtid_mode設定為ON或ON_PERMISSIVE時,GTID才儲存在gtid_executed表中。如果從庫禁用了binlog或者log_slave_updates=0的時候,該表中儲存所有的gtid。
當啟用二進位制日誌的時候,該表並不儲存所有的gtid,而是通過show global variables查出來的gtid_executed儲存,重新整理二進位制日誌或者重啟時,將會把當前binlog中所有的gtid寫入到該表中。因此該表有時候可能並不是完整的gtid,所以通過show查看出來的才是完整的。
END
好了,就先說這麼多,說的比較淺顯,gtid還是很奇妙很有意思的,裡面其實還有好多東西需要學習,要帶著興趣帶著思考去學習去測試,這樣會事半功倍。