1. 程式人生 > >MySQL 5.6 GTID 原理以及使用

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找點兒了。

GTID的表示

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

        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中的橫線只是為了便於檢視,並沒有實際意義)。

1234567mysql> 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。

        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模式。

        剛才提到,同一個server_uuid下的transaction_id一般是遞增的,難道在某些情況下不是遞增的嗎?答案是肯定的。MySQL支援通過設定Session級別的變數gtid_next,來指定下一個事務的GTID,格式就是‘server_uuid:transaction_id'。之後還可以改回AUTOMATIC(預設值)

12345678mysql> 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的大小來判斷事務的順序。

        使用server_uuid:transaction_id共同組成一個GTID的好處是,由於server_uuid唯一,即使一個叢集內多個節點同時有寫入,也不會造成GTID衝突。

GTID的使用

        MySQL通過全域性變數gtid_mode控制開啟/關閉GTID模式。但是gtid_mode是隻讀的,可新增到配置檔案中,然後重啟mysqld來開啟GTID模式。相關配置項如下:

12345gtid-mode                = ONenforce_gtid_consistency = 1log-slave-updates        = 1log-bin                  = mysql-binlog-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。命令如下:

123456mysql> 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的執行情況

12345678910mysql> 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

123456789101112131415161718192021222324252627282930313233343536at 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-12000005at 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=0BEGIN/*!*/;at 370#151222  9:08:03 server id 1026872625  end_log_pos 480 CRC32 0x3c0e094f     Query   thread_id=18    exec_time=1 error_code=0use `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 = 6813913COMMIT/*!*/;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=0SET 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=0SET 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 = 6813916COMMIT/*!*/;

        這段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執行的冪等性。


業界經驗: