MySQL 5.6 GTID 原理以及使用

轉自:http://hamilton.duapp.com/detail?articleId=47

 

簡介

       GTID是MySQL 5.6的新特性,其全稱是Global Transaction Identifier,可簡化MySQL的主從切換以及Failover。GTID用於在binlog中惟一標識一個事務。當事務提交時,MySQL Server在寫binlog的時候,會先寫一個特殊的Binlog Event,類型爲GTID_Event,指定下一個事務的GTID,而後再寫事務的Binlog。主從同步時GTID_Event和事務的Binlog都會傳遞到從庫,從庫在執行的時候也是用一樣的GTID寫binlog,這樣主從同步之後,就可經過GTID肯定從庫同步到的位置了。也就是說,不管是級聯狀況,仍是一主多從狀況,均可以經過GTID自動找點兒,而無需像以前那樣經過File_name和File_position找點兒了。html

 

GTID的表示

        MySQL 5.6使用server_uuid和transaction_id兩個共同組成一個GTID。即:GTID = server_uuid:transaction_idmysql

        server_uuid是MySQL Server的只讀變量,保存在數據目錄下的auto.cnf中,可直接經過cat命令查看。MySQL第一次啓動時候建立auto.cnf文件,並生成server_uuid(MySQL使用機器網卡,當前時間,隨機數等拼接成一個128bit的uuid,可認爲在全宇宙都是惟一的,在將來一百年,使用一樣的算法生成的uuid是不會衝突的)。以後MySQL再啓動時不會重複生成uuid,而是使用auto.cnf中的uuid。也能夠經過MySQL客戶端使用以下命令查看server_uuid,看到的其實是server_uuid的十六進制編碼,總共16字節(其中uuid中的橫線只是爲了便於查看,並無實際意義)。算法

1
2
3
4
5
6
7
mysql> show  global  variables  like  'server_uuid' ;
+ ---------------+--------------------------------------+
| Variable_name | Value                                |
+ ---------------+--------------------------------------+
| server_uuid   | b3485508-883f-11e5-85fb-e41f136aba3e |
+ ---------------+--------------------------------------+
1 row  in  set  (0.00 sec)

        在同一個集羣內,每一個MySQL實例的server_uuid必須惟一,不然同步時,會形成IO線程不停的中斷,重連。在經過備份恢復數據時,必定要將var目錄中的auto.cnf刪掉,讓MySQL啓動時本身生成uuid。sql

        GTID中還有一部分是transaction_id,同一個server_uuid下的transaction_id通常是遞增的。若是一個事務是經過用戶線程執行,那麼MySQL在生成的GTID時,會使用它本身的server_uuid,而後再遞增一個transaction_id做爲該事務的GTID。固然,若是事務是經過SQL線程回放relay-log時產生,那麼GTID就直接使用binlog裏的了。在MySQL 5.6中不用擔憂binlog裏沒有GTID,由於若是從庫開啓了GTID模式,主庫也必須開啓,不然IO線程在創建鏈接的時候就中斷了。5.6的GTID對MySQL的集羣環境要求是很是嚴格的,要麼主從所有開啓GTID模式,要麼所有關閉GTID模式。bash

        剛纔提到,同一個server_uuid下的transaction_id通常是遞增的,難道在某些狀況下不是遞增的嗎?答案是確定的。MySQL支持經過設置Session級別的變量gtid_next,來指定下一個事務的GTID,格式就是‘server_uuid:transaction_id'。以後還能夠改回AUTOMATIC(默認值)app

1
2
3
4
5
6
7
8
mysql>  set  gtid_next =  'b694c8b2-883f-11e5-85fb-e41f136aba3e:12000005' ;
Query OK, 0  rows  affected (0.00 sec)
mysql>  begin ;
Query OK, 0  rows  affected (0.00 sec)
mysql>  commit ;
Query OK, 0  rows  affected (0.00 sec)
mysql>  set  gtid_next = AUTOMATIC;
Query OK, 0  rows  affected (0.00 sec)

        通常設置gtid_next是加1,用於主從同步時跳過一個事務。可是若是設置gtid_next以後,致使當前server_uuid下的transaction_id不連續,那麼坑爹的地方也就出現了。在改回AUTOMATIC之後,再有事務執行時,MySQL生成transaction_id時,不是按當前最大的transaction_id繼續增加,而是補缺口(使用最小的缺失的那個transaction_id做爲下一個gtid)。這樣的話,即便是同一個server_uuid,也不能經過transaction_id的大小來判斷事務的順序。less

        使用server_uuid:transaction_id共同組成一個GTID的好處是,因爲server_uuid惟一,即便一個集羣內多個節點同時有寫入,也不會形成GTID衝突。post

GTID的使用

        MySQL經過全局變量gtid_mode控制開啓/關閉GTID模式。可是gtid_mode是隻讀的,可添加到配置文件中,而後重啓mysqld來開啓GTID模式。相關配置項以下:ui

1
2
3
4
5
gtid-mode                = ON
enforce_gtid_consistency = 1
log-slave-updates        = 1
log-bin                  = mysql-bin
log-bin-index            = mysql-bin.index

 

        配置方式爲gtid_mode=ON/OFF。讓人詫異的是gtid_mode的類型爲枚舉類型,枚舉值能夠爲ON和OFF,因此應該經過ON或者OFF來控制gtid_mode,不要把它配置成0或者1,不然結果可能不符合你的預期。開啓gtid_mode時,log-bin和log-slave-updates也必須開啓,不然MySQL Server拒絕啓動。除此之外,enforce-gtid-consistency也必須開啓,不然MySQL Server也拒絕啓動。enforce-gtid-consistency是由於開啓grid_mode之後,許多MySQL的SQL和GTID是不兼容的。好比開啓ROW 格式時,CREATE TABLE ... SELECT,在binlog中會造成2個不一樣的事務,GTID沒法惟一。另外在事務中更新MyISAM表也是不容許的。編碼

        剛纔已經提到,當開啓GTID模式時,集羣中的所有MySQL Server必須同時配置gtid_mod = ON,不然沒法同步。

        一旦使用GTID模式同步之後,主從切換就可使用GTID來自動找點兒了,使用方式是在CHANGE MASTER時指定MASTER_AUTO_POSITION=1。命令以下:

1
2
3
4
5
6
mysql> CHANGE MASTER  TO  \
     -> MASTER_HOST =  '' , \
     -> MASTER_PORT = 3306, \
     -> MASTER_USER =  'test' , \
     -> MASTER_PASSWORD =  '' , \
     -> MASTER_AUTO_POSITION = 1;

        經過SHOW SLAVE STATUS也能夠看到Auto_Position: 1,說明之後START SLAVE將使用GTID自動找點兒,開啓GTID以後原理上還支持使用FileName和FilePosition的方式找點兒,可是不建議使用。若是非要使用的話,在CHANGE MASTER的時候要指定MASTER_AUTO_POSITION=0

       MySQL經過若干變量能夠查看GTID的執行狀況

1
2
3
4
5
6
7
8
9
10
mysql> show  global  variables  like  'gtid_%' ;
+ ---------------+----------------------------------------------------------------------------------------------+
| Variable_name | Value                                                                                        |
+ ---------------+----------------------------------------------------------------------------------------------+
| gtid_executed | b694c8b2-883f-11e5-85fb-e41f136aba3e:1-10114525:12000000-12000005                            |
| gtid_mode     |  ON                                                                                            |
| gtid_owned    | b694c8b2-883f-11e5-85fb-e41f136aba3e:10114523#10:10114525#6:10114521#5:10114524#8:10114522#4 |
| gtid_purged   | b694c8b2-883f-11e5-85fb-e41f136aba3e:1-8993295                                               |
+ ---------------+----------------------------------------------------------------------------------------------+
rows  in  set  (0.00 sec)

        這裏有4個變量,其中gtid_mode已經介紹過了,其餘3個變量的含義以下

        gtid_executed:這既是一個Global級別的變量,又是一個Session級別的變量,是隻讀變量。Global級別的gtid_executed表示當前實例已經執行過的GTID集合。Session級別的gtid_executed通常狀況下是空的。

        gtid_owned:這既是一個Global級別的變量,又是一個Session級別的變量,是隻讀變量。Global級別的gtid_owned表示當前實例正在執行中的GTID,以及對應的線程id。Session級別的gtid_owned通常狀況下是空的。

        gtid_purged:這是一個Global級別的變量,可動態修改。咱們知道binlog能夠被purge掉,gtid_purged表示當前實例中已經被purge掉的GTID集合,很明顯gtid_purged是gtid_executed的子集。可是gtid_purged也不是能夠隨意修改的,必須在@@global.gtid_executed是空的狀況下,才能夠動態設置gtid_purged。

GTID相關Binlog

       經過前面的介紹能夠知道,GTID能夠在binlog中惟一標識一個事務,要了解GTID找點兒原理,就必須知道Binlog的格式,首先看一段Binlog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
at  120
#151222  9:07:58 server id 1026872634  end_log_pos 247 CRC32 0xedf993a8     Previous-GTIDs
# b3485508-883f-11e5-85fb-e41f136aba3e:1-14,
# b694c8b2-883f-11e5-85fb-e41f136aba3e:1-10115960:12000000-12000005
at  247
#151222  9:08:03 server id 1026872625  end_log_pos 295 CRC32 0xc3d3d8ee     GTID [ commit =yes]
SET  @@SESSION.GTID_NEXT=  'b694c8b2-883f-11e5-85fb-e41f136aba3e:10115961' /*!*/;
at  295
#151222  9:08:03 server id 1026872625  end_log_pos 370 CRC32 0x0a32d229     Query   thread_id=18    exec_time=1 error_code=0
BEGIN
/*!*/;
at  370
#151222  9:08:03 server id 1026872625  end_log_pos 480 CRC32 0x3c0e094f     Query   thread_id=18    exec_time=1 error_code=0
use `db`/*!*/;
SET  TIMESTAMP =1450746483/*!*/;
update  tb  set  val = val + 1  where  id = 1
/*!*/;
at  480
#151222  9:08:03 server id 1026872625  end_log_pos 511 CRC32 0x5772f16b     Xid = 6813913
COMMIT /*!*/;
at  511
#151222  9:10:19 server id 1026872625  end_log_pos 559 CRC32 0x3ac30191     GTID [ commit =yes]
SET  @@SESSION.GTID_NEXT=  'b694c8b2-883f-11e5-85fb-e41f136aba3e:10115962' /*!*/;
at  559
#151222  9:10:19 server id 1026872625  end_log_pos 634 CRC32 0x83a74912     Query   thread_id=18    exec_time=0 error_code=0
SET  TIMESTAMP =1450746619/*!*/;
BEGIN
/*!*/;
at  634
#151222  9:10:19 server id 1026872625  end_log_pos 744 CRC32 0x581f6031     Query   thread_id=18    exec_time=0 error_code=0
SET  TIMESTAMP =1450746619/*!*/;
update  tb  set  val = val + 1  where  id = 1
/*!*/;
at  744
#151222  9:10:19 server id 1026872625  end_log_pos 775 CRC32 0x793f8e34     Xid = 6813916
COMMIT /*!*/;

        這段Binlog從文件120偏移處(Format_description_log_event以後的第一個Binlog Event)開始截取。能夠看到,第一個Binlog Event的類型爲:Previous-GTIDs,它存在於每一個binlog文件中。當開啓GTID時,每一個binlog文件都有且只有一個Previous-GTIDs,位置都是在Format_description_log_event以後的第一個Binlog Event處。它的含義是在當前Binlog文件以前執行過的GTID集合,能夠充當索引用,使用這個Binlog Event,能夠便於快速判斷GTID是否位於當前binlog文件中。

        下面看看gtid_purged和gtid_executed是如何構造的。MySQL在啓動時打開最老的binlog文件,讀取其中的Previous-GTIDs,那麼就是@@global.gtid_purged。MySQL在啓動時打開最新的binlog文件,讀取其中的Previous-GTIDs,構造一個gtid_set,而後再遍歷這個最新的binlog文件,把遇到的每一個gtid都添加到gtid_set中,當文件遍歷完成時,這個gtid_set就是@@global.gtid_executed。

        前面說過只有在@@global.gtid_executed爲空的狀況下,才能夠動態設置@@global.gtid_purged。所以能夠經過RESET MASTER的方式來清空@@global.gtid_executed。這一點,相似Ares中的命令:set binlog_group_id=XXX, master_server_id=YYY with reset;(是會刪除binlog的)

        經過解析上面的binlog文件,咱們也能夠看到,每一個事務以前,都有一個GTID_log_event,用來指定GTID的值。整體來看,一個MySQL binlog的格式大體以下:

GTID找點兒原理

        咱們知道,在未開啓GTID模式的狀況下,從庫用(File_name和File_pos)二元組標識執行到的位置。START SLAVE時,從庫會先向主庫發送一個BINLOG_DUMP命令,在BINLOG_DUMP命令中指定File_name和File_pos,主庫就從這個位置開始發送binlog。

         在開啓GTID模式的狀況下,若是指定MASTER_AUTO_POSITION=1。START SLAVE時,從庫會計算Retrieved_Gtid_Set和Executed_Gtid_Set的並集(經過SHOW SLAVE STATUS能夠查看),而後把這個GTID並集發送給主庫。主庫會使用從庫請求的GTID集合和本身的gtid_executed比較,把從庫GTID集合裏缺失的事務全都發送給從庫。若是從庫缺失的GTID,已經被主庫pruge了呢?從庫報1236錯誤,IO線程中斷。

        經過GTID找到點兒的原理仍是比較奇怪的,它過於強調主從binlog中GTID集合的一致性,弱化了Binlog執行的順序性。

        考慮下面這種狀況,有個集羣已經在使用GTID模式同步,小明想給集羣增長一臺從庫,新作完一臺從庫,數據和主庫一致,可是沒有binlog,也就是說新從庫的@@global.gtid_executed是空的。可是CHANGE MASTER時能夠經過File_name和File_pos找到正確的同步點,而後START SLAVE,一切正常。過了一下子,小明以爲還能夠經過MASTER_AUTO_POSITION = 1的方式從新CHANGE MASTER,而後再START SLAVE。這種狀況下,主庫一看從庫GTID裏少了那麼多binlog,而後把所有缺失的binglog再給從庫發送一遍,那麼悲劇就發生了。

        爲了解決這個問題,小明新作完從庫之後,應該在從庫上執行reset master; set global gtid_purged = 'xxxxx',把缺失的GTID集合設置爲purged,而後就能夠直接使用MASTER_AUTO_POSITION=1自動找點兒了。

        因而可知,開啓GTID之後,Binlog和數據文件同樣重要,不只要求主從數據一致,還要求主從Binlog中GTID集合一致。

GTID注意事項

 

1)開啓GTID之後,沒法使用sql_slave_skip_counter跳過事務。前面介紹過了,使用GTID找點兒時,主庫會把從庫缺失的GTID,發送給從庫,因此skip是沒有用的。爲了提早發現問題,MySQL在gtid模式下,直接禁止使用set global sql_slave_skip_counter = x。正確的作法是,經過set grid_next= 'zzzz'('zzzz'爲待跳過的事務),而後執行BIGIN;COMMIT產生一個空事務,佔據這個GTID,再START SLAVE,會發現下一條事務的GTID已經執行過,就會跳過這個事務了

        2)若是一個GTID已經執行過,再遇到重複的GTID,從庫會直接跳過,可看做GTID執行的冪等性。

        3)使用限制:https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-restrictions.html


業界經驗:

1. https://code.facebook.com/posts/1542273532669494/lessons-from-deploying-mysql-gtid-at-scale/

2. https://www.percona.com/blog/2015/02/10/online-gtid-rollout-now-available-percona-server-5-6/

相關文章
相關標籤/搜索