http://dev.mysql.com/doc/refman/5.6/en/replication-gtids.html
在這篇文檔裏,咱們能夠知道全局事務 ID 的官方定義是:
GTID = source_id:transaction_id
在 MySQL 5.6 中,每個 GTID 表明一個數據庫事務。在上面的定義中,source_id 表示執行事務的主庫 uuid(server_uuid),transaction_id 是一個從 1 開始的自增計數,表示在這個主庫上執行的第 n 個事務。MySQL 會保證事務與 GTID 之間的 1 : 1 映射。
例如,下面就是一個 GTID:
3E11FA47-71CA-11E1-9E33-C80AA9429562:23
表示在以 "3E11FA47-71CA-11E1-9E33-C80AA9429562" 爲惟一標示的 MySQL 實例上執行的第 23 個數據庫事務。
很容易理解,MySQL 只要保證每臺數據庫的 server_uuid 全局惟一,以及每臺數據庫生成的 transaction_id 自身惟一,就能保證 GTID 的全局惟一性。
什麼又是 server_uuid?
MySQL 5.6 用 128 位的 server_uuid 代替了本來的 32 位 server_id 的大部分功能。緣由很簡單,server_id 依賴於 my.cnf 的手工配置,有可能產生衝突 —— 而自動產生 128 位 uuid 的算法能夠保證全部的 MySQL uuid 都不會衝突。
在首次啓動時 MySQL 會調用 generate_server_uuid() 自動生成一個 server_uuid,而且保存到 auto.cnf 文件 —— 這個文件目前存在的惟一目的就是保存 server_uuid。
在 MySQL 再次啓動時會讀取 auto.cnf 文件,繼續使用上次生成的 server_uuid。
使用 SHOW 命令能夠查看 MySQL 實例當前使用的 server_uuid:
SHOW GLOBAL VARIABLES LIKE 'server_uuid';
全局惟一的 server_uuid 的一個好處是:能夠解決由 server_id 配置衝突帶來的 MySQL 主備複製的異常終止(BUG
#33815):
在 MySQL 5.6,Slave 向 Master 申請 binlog 時,會首先發送本身的 server_uuid,Master 用 Slave 發送的 server_uuid 代替 server_id (MySQL 5.6 以前的方式)做爲 kill_zombie_dump_threads 的參數,終止衝突或者僵死的 BINLOG_DUMP 線程。
關於 GTID 的更多細節:
在 MySQL 5.6 源碼內部,GTID 的數據結構能夠用僞碼寫成:
(代碼路徑:mysql-5.6.9-rc\sql\rpl_gtid.h, 754 line)
Gtid := (sidno, gno)
其中 sidno 是表明 sid 的 32-bit 序號,sid 是 source_id 或者 server_uuid 的二進制表示 —— 這裏我先忽略 sidno 和 sid 的關聯(這須要解釋一些另外複雜的東西)。gno 是 64 位整數,等同於上面提到的 transaction_id。
在 MySQL 內部更常見的數據類型是 Gtid_set,表示一組 GTID 的集合,在官方文檔中一般寫成 GTIDs。例如,下面就是一個 GTIDs:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
表示在 MySQL 實例 "3E11FA47-71CA-11E1-9E33-C80AA9429562" 上執行的第 1 到第 5 個數據庫事務。
複雜一點的是:若是這組 GTIDs 來自不一樣的 source_id,各組 source_id 之間用逗號分隔;若是事務序號有多個範圍區間,各組範圍之間用冒號分隔,例如:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5:11-18,
2C256447-3F0D-431B-9A12-575BB20C1507:1-27
理解 GDITs 和 Gtid_set
在 MySQL 5.6 源碼裏,Gtid_set 是個較爲複雜的數據結構,基本結構能夠用僞碼錶示成:(代碼路徑:mysql-5.6.9-rc\sql\rpl_gtid.h, 842 line)
Gtid_set := array( sidno => link_list(Interval) )
Interval := (start, end)
用示意圖表示應該是這樣:
Gtid_set 的結構是一個以 sidno 爲序號的數組(一樣的,這裏容許我先忽略什麼是 sidno 這個撓頭的問題),每一個數組元素都指向一條 Interval 組成的鏈表,鏈表中的每一個 Interval 用來存放一組事務 ID(gno)的區間,例如 (1,5)。
假設上文中 uuid:3E11FA47-71CA-11E1-9E33-C80AA9429562 對應的 sidno 爲 1,那麼下面的 GTIDs:
3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
能夠用 Gtid_set 表示爲:
Gtid_set
| sidno: 1 | -> Interval(1, 5)
MySQL 5.6 引入 Gtid_set 數據結構的目的是爲了輕便的記錄大量的連續全局事務 ID,好比在 Slave 上執行過的全局事務 ID, 或者從 Master 上產生的全局事務 ID。這些集合每每包含海量的事務 ID。
當 transaction_id / gno 保持連續,Gtid_set 能夠在常數時間內判斷一個 GTID 是否包含在集合內。這很容易用來檢查一個 GTID 是否已經執行過,所以能夠用來防止 MySQL 事務在同一個 Slave 上重複執行這類狀況。
Gtid_set 支持一些常見的集合操做,好比檢查另外一個 Gtid_set 是否子集(is_subset),好比兩組 Gtid_set 作交集(intersection)。這些方法在內部操做 GTIDs 中常常用到。
有個值得一提的細節是,Gtid_set 爲了減小鏈表致使的內存碎片,全部的 Interval 對象都是用 chunk 方式分配的,chunk 大小爲 8 * sizeof(Interval)。
打斷,解釋一下 sidno
爲減小在索引裏保存 128 位 server_uuid 的消耗,Gtid_set 使用 int32 類型的 sidno 代替 server_uuid 做爲 Interval 鏈表的索引。所以,MySQL 須要另外一個數據結構在 128 位的 server_uuid 與 32 位的 sidno 之間創建映射,這個結構叫 Sid_map:
(代碼路徑:mysql-5.6.9-rc\sql\rpl_gtid.h, 467 line)
Sid_map := hash_map(sid => sidno)
它基本上就是一個 server_uuid 到 sidno 的 hash_map, 而且負責順序產生 sidno:第一個放入 Sid_map 的 sidno 爲 1,第二個 sidno 爲 2 ...... 注意,生成的 sidno 是臨時性的,在 Sid_map 被釋放或者 MySQL 實例重啓後又會從新分配。
在建立 Gtid_set 時,MySQL 會調用 ensure_sidno() 方法保證 array 內有足夠的空間容納 Sid_map 中全部分配的 sidno。
由於 Sid_map 是一個讀多寫少的數據結構(顯然,只有 MySQL 集羣增長或者更換實例時,server_uuid 纔會增長),MySQL 用一個讀寫鎖來保護 Sid_map,每當 Gtid_set 在查 Sid_map 時加讀鎖;每當 Gtid_set 找到新的 server_uuid 須要分配 sidno 時,加寫鎖。
基本上,MySQL 5.6 全部的 server_uuid 都經過一個全局變量 global_sid_map 來映射。相應的,也有一個全局鎖 global_sid_lock 在保護這個 Sid_map。這些代碼在 mysqld.cc 的 gtid_server_init 方法裏能夠找到。
(代碼路徑:mysql-5.6.9-rc\sql\mysqld.cc, 1719 line)
最後介紹一下 Gtid_state
如今,MySQL 5.6 有了記錄全局事務 ID 的數據結構 Gtid_set,又維持了一個全局 Sid_map 來映射 server_uuid 與 sidno,下面咱們能夠開始接觸 MySQL 全局事務 ID 的核心數據 Gtid_state 了:
Gtid_state := (logged_gtids, lost_gtids, owned_gtids)
全局事務狀態 Gtid_state 在 MySQL 5.6 內只有惟一一個實例,目的是存儲三組全局事務 ID 的集合,每一個集合的功能我會在下一篇博客闡述:
+---------------+-------------------------------------------------------------+
| 名稱 | 功能 |
+---------------+-------------------------------------------------------------+
| logged_gtids | 寫入到 binlog 的全局事務 ID 集合。 |
+---------------+-------------------------------------------------------------+
| lost_gtids | 已經從 binlog 刪除的全局事務 ID 集合。 |
+---------------+-------------------------------------------------------------+
| owned_gtids | 正在執行的全局事務 ID 與 MySQL 線程 ID 的集合 |
+---------------+-------------------------------------------------------------+
注:owned_gtids 變量的類型是 Owned_gtids, 它基本上能夠看做一個 Gtid (sidno, gno) 到 owner_thread_id 的 hash_map:
Owned_gtids := array(sidno => hash_map(Node))
Node := (gno, owner_thread_id)
其中 gno 是 Gtid 中的事務 ID。