您好,登錄后才能下訂單哦!
GTID是MySQL 5.6的新特性,其全稱是Global Transaction Identifier,可簡化MySQL的主從切換以及Failover。GTID用于在binlog中唯一標(biāo)識一個(gè)事務(wù)。當(dāng)事務(wù)提交時(shí),MySQL Server在寫binlog的時(shí)候,會先寫一個(gè)特殊的Binlog Event,類型為GTID_Event,指定下一個(gè)事務(wù)的GTID,然后再寫事務(wù)的Binlog。主從同步時(shí)GTID_Event和事務(wù)的Binlog都會傳遞到從庫,從庫在執(zhí)行的時(shí)候也是用同樣的GTID寫binlog,這樣主從同步以后,就可通過GTID確定從庫同步到的位置了。也就是說,無論是級聯(lián)情況,還是一主多從情況,都可以通過GTID自動找點(diǎn)兒,而無需像之前那樣通過File_name和File_position找點(diǎn)兒了。
MySQL 5.6使用server_uuid和transaction_id兩個(gè)共同組成一個(gè)GTID。即:GTID = server_uuid:transaction_id
server_uuid是MySQL Server的只讀變量,保存在數(shù)據(jù)目錄下的auto.cnf中,可直接通過cat命令查看。MySQL第一次啟動時(shí)候創(chuàng)建auto.cnf文件,并生成server_uuid(MySQL使用機(jī)器網(wǎng)卡,當(dāng)前時(shí)間,隨機(jī)數(shù)等拼接成一個(gè)128bit的uuid,可認(rèn)為在全宇宙都是唯一的,在未來一百年,使用同樣的算法生成的uuid是不會沖突的)。之后MySQL再啟動時(shí)不會重復(fù)生成uuid,而是使用auto.cnf中的uuid。也可以通過MySQL客戶端使用如下命令查看server_uuid,看到的實(shí)際上是server_uuid的十六進(jìn)制編碼,總共16字節(jié)(其中uuid中的橫線只是為了便于查看,并沒有實(shí)際意義)。
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) |
在同一個(gè)集群內(nèi),每個(gè)MySQL實(shí)例的server_uuid必須唯一,否則同步時(shí),會造成IO線程不停的中斷,重連。在通過備份恢復(fù)數(shù)據(jù)時(shí),一定要將var目錄中的auto.cnf刪掉,讓MySQL啟動時(shí)自己生成uuid。
GTID中還有一部分是transaction_id,同一個(gè)server_uuid下的transaction_id一般是遞增的。如果一個(gè)事務(wù)是通過用戶線程執(zhí)行,那么MySQL在生成的GTID時(shí),會使用它自己的server_uuid,然后再遞增一個(gè)transaction_id作為該事務(wù)的GTID。當(dāng)然,如果事務(wù)是通過SQL線程回放relay-log時(shí)產(chǎn)生,那么GTID就直接使用binlog里的了。在MySQL 5.6中不用擔(dān)心binlog里沒有GTID,因?yàn)槿绻麖膸扉_啟了GTID模式,主庫也必須開啟,否則IO線程在建立連接的時(shí)候就中斷了。5.6的GTID對MySQL的集群環(huán)境要求是非常嚴(yán)格的,要么主從全部開啟GTID模式,要么全部關(guān)閉GTID模式。
剛才提到,同一個(gè)server_uuid下的transaction_id一般是遞增的,難道在某些情況下不是遞增的嗎?答案是肯定的。MySQL支持通過設(shè)置Session級別的變量gtid_next,來指定下一個(gè)事務(wù)的GTID,格式就是‘server_uuid:transaction_id'。之后還可以改回AUTOMATIC(默認(rèn)值)
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) |
一般設(shè)置gtid_next是加1,用于主從同步時(shí)跳過一個(gè)事務(wù)。但是如果設(shè)置gtid_next之后,導(dǎo)致當(dāng)前server_uuid下的transaction_id不連續(xù),那么坑爹的地方也就出現(xiàn)了。在改回AUTOMATIC以后,再有事務(wù)執(zhí)行時(shí),MySQL生成transaction_id時(shí),不是按當(dāng)前最大的transaction_id繼續(xù)增長,而是補(bǔ)缺口(使用最小的缺失的那個(gè)transaction_id作為下一個(gè)gtid)。這樣的話,即使是同一個(gè)server_uuid,也不能通過transaction_id的大小來判斷事務(wù)的順序。
使用server_uuid:transaction_id共同組成一個(gè)GTID的好處是,由于server_uuid唯一,即使一個(gè)集群內(nèi)多個(gè)節(jié)點(diǎn)同時(shí)有寫入,也不會造成GTID沖突。
MySQL通過全局變量gtid_mode控制開啟/關(guān)閉GTID模式。但是gtid_mode是只讀的,可添加到配置文件中,然后重啟mysqld來開啟GTID模式。相關(guān)配置項(xiàng)如下:
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,所以應(yīng)該通過ON或者OFF來控制gtid_mode,不要把它配置成0或者1,否則結(jié)果可能不符合你的預(yù)期。開啟gtid_mode時(shí),log-bin和log-slave-updates也必須開啟,否則MySQL Server拒絕啟動。除此以外,enforce-gtid-consistency也必須開啟,否則MySQL Server也拒絕啟動。enforce-gtid-consistency是因?yàn)殚_啟grid_mode以后,許多MySQL的SQL和GTID是不兼容的。比如開啟ROW 格式時(shí),CREATE TABLE ... SELECT,在binlog中會形成2個(gè)不同的事務(wù),GTID無法唯一。另外在事務(wù)中更新MyISAM表也是不允許的。
剛才已經(jīng)提到,當(dāng)開啟GTID模式時(shí),集群中的全部MySQL Server必須同時(shí)配置gtid_mod = ON,否則無法同步。
一旦使用GTID模式同步以后,主從切換就可以使用GTID來自動找點(diǎn)兒了,使用方式是在CHANGE MASTER時(shí)指定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自動找點(diǎn)兒,開啟GTID之后原理上還支持使用FileName和FilePosition的方式找點(diǎn)兒,但是不建議使用。如果非要使用的話,在CHANGE MASTER的時(shí)候要指定MASTER_AUTO_POSITION=0
MySQL通過若干變量可以查看GTID的執(zhí)行情況
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 | +
---------------+----------------------------------------------------------------------------------------------+ 4
rows
in
set
(0.00 sec) |
這里有4個(gè)變量,其中g(shù)tid_mode已經(jīng)介紹過了,其他3個(gè)變量的含義如下
gtid_executed:這既是一個(gè)Global級別的變量,又是一個(gè)Session級別的變量,是只讀變量。Global級別的gtid_executed表示當(dāng)前實(shí)例已經(jīng)執(zhí)行過的GTID集合。Session級別的gtid_executed一般情況下是空的。
gtid_owned:這既是一個(gè)Global級別的變量,又是一個(gè)Session級別的變量,是只讀變量。Global級別的gtid_owned表示當(dāng)前實(shí)例正在執(zhí)行中的GTID,以及對應(yīng)的線程id。Session級別的gtid_owned一般情況下是空的。
gtid_purged:這是一個(gè)Global級別的變量,可動態(tài)修改。我們知道binlog可以被purge掉,gtid_purged表示當(dāng)前實(shí)例中已經(jīng)被purge掉的GTID集合,很明顯gtid_purged是gtid_executed的子集。但是gtid_purged也不是可以隨意修改的,必須在@@global.gtid_executed是空的情況下,才可以動態(tài)設(shè)置gtid_purged。
通過前面的介紹可以知道,GTID可以在binlog中唯一標(biāo)識一個(gè)事務(wù),要了解GTID找點(diǎn)兒原理,就必須知道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之后的第一個(gè)Binlog Event)開始截取??梢钥吹?,第一個(gè)Binlog Event的類型為:Previous-GTIDs,它存在于每個(gè)binlog文件中。當(dāng)開啟GTID時(shí),每個(gè)binlog文件都有且只有一個(gè)Previous-GTIDs,位置都是在Format_description_log_event之后的第一個(gè)Binlog Event處。它的含義是在當(dāng)前Binlog文件之前執(zhí)行過的GTID集合,可以充當(dāng)索引用,使用這個(gè)Binlog Event,可以便于快速判斷GTID是否位于當(dāng)前binlog文件中。
下面看看gtid_purged和gtid_executed是如何構(gòu)造的。MySQL在啟動時(shí)打開最老的binlog文件,讀取其中的Previous-GTIDs,那么就是@@global.gtid_purged。MySQL在啟動時(shí)打開最新的binlog文件,讀取其中的Previous-GTIDs,構(gòu)造一個(gè)gtid_set,然后再遍歷這個(gè)最新的binlog文件,把遇到的每個(gè)gtid都添加到gtid_set中,當(dāng)文件遍歷完成時(shí),這個(gè)gtid_set就是@@global.gtid_executed。
前面說過只有在@@global.gtid_executed為空的情況下,才可以動態(tài)設(shè)置@@global.gtid_purged。因此可以通過RESET MASTER的方式來清空@@global.gtid_executed。這一點(diǎn),類似Ares中的命令:set binlog_group_id=XXX, master_server_id=YYY with reset;(是會刪除binlog的)
通過解析上面的binlog文件,我們也可以看到,每個(gè)事務(wù)之前,都有一個(gè)GTID_log_event,用來指定GTID的值??傮w來看,一個(gè)MySQL binlog的格式大致如下:
我們知道,在未開啟GTID模式的情況下,從庫用(File_name和File_pos)二元組標(biāo)識執(zhí)行到的位置。START SLAVE時(shí),從庫會先向主庫發(fā)送一個(gè)BINLOG_DUMP命令,在BINLOG_DUMP命令中指定File_name和File_pos,主庫就從這個(gè)位置開始發(fā)送binlog。
在開啟GTID模式的情況下,如果指定MASTER_AUTO_POSITION=1。START SLAVE時(shí),從庫會計(jì)算Retrieved_Gtid_Set和Executed_Gtid_Set的并集(通過SHOW SLAVE STATUS可以查看),然后把這個(gè)GTID并集發(fā)送給主庫。主庫會使用從庫請求的GTID集合和自己的gtid_executed比較,把從庫GTID集合里缺失的事務(wù)全都發(fā)送給從庫。如果從庫缺失的GTID,已經(jīng)被主庫pruge了呢?從庫報(bào)1236錯(cuò)誤,IO線程中斷。
通過GTID找到點(diǎn)兒的原理還是比較奇怪的,它過于強(qiáng)調(diào)主從binlog中GTID集合的一致性,弱化了Binlog執(zhí)行的順序性。
考慮下面這種情況,有個(gè)集群已經(jīng)在使用GTID模式同步,小明想給集群增加一臺從庫,新做完一臺從庫,數(shù)據(jù)和主庫一致,但是沒有binlog,也就是說新從庫的@@global.gtid_executed是空的。但是CHANGE MASTER時(shí)可以通過File_name和File_pos找到正確的同步點(diǎn),然后START SLAVE,一切正常。過了一會兒,小明覺得還可以通過MASTER_AUTO_POSITION = 1的方式重新CHANGE MASTER,然后再START SLAVE。這種情況下,主庫一看從庫GTID里少了那么多binlog,然后把全部缺失的binglog再給從庫發(fā)送一遍,那么悲劇就發(fā)生了。
為了解決這個(gè)問題,小明新做完從庫以后,應(yīng)該在從庫上執(zhí)行reset master; set global gtid_purged = 'xxxxx',把缺失的GTID集合設(shè)置為purged,然后就可以直接使用MASTER_AUTO_POSITION=1自動找點(diǎn)兒了。
由此可見,開啟GTID以后,Binlog和數(shù)據(jù)文件一樣重要,不僅要求主從數(shù)據(jù)一致,還要求主從Binlog中GTID集合一致。
1)開啟GTID以后,無法使用sql_slave_skip_counter跳過事務(wù)。前面介紹過了,使用GTID找點(diǎn)兒時(shí),主庫會把從庫缺失的GTID,發(fā)送給從庫,所以skip是沒有用的。為了提前發(fā)現(xiàn)問題,MySQL在gtid模式下,直接禁止使用set global sql_slave_skip_counter = x。正確的做法是,通過set grid_next= 'zzzz'('zzzz'為待跳過的事務(wù)),然后執(zhí)行BIGIN;COMMIT產(chǎn)生一個(gè)空事務(wù),占據(jù)這個(gè)GTID,再START SLAVE,會發(fā)現(xiàn)下一條事務(wù)的GTID已經(jīng)執(zhí)行過,就會跳過這個(gè)事務(wù)了
2)如果一個(gè)GTID已經(jīng)執(zhí)行過,再遇到重復(fù)的GTID,從庫會直接跳過,可看作GTID執(zhí)行的冪等性。
3)使用限制: https://dev.mysql.com/doc/refman/5.6/en/replication-gtids-restrictions.html
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。