深入理解MySQL 5.7 GTID系列(二):GTID相關內部資料結構,mysqlgtid
作者:高鵬(重慶八怪)
原文地址:
https://www.jianshu.com/p/5649644fdc13
深入理解MySQL 5.7 GTID系列文章共十篇,本文為第二篇,
點擊查看第一篇文章:深入理解MySQL 5.7 GTID系列(一)
該系列文章將陸續不定期更新~
一、 GTID基本格式
e859a28b-b66d-11e7-8371-000c291f347d:1
前一部分是SERVER_UUID,後面一部分是執行事務的唯一標誌,通常是自增的。內部使用 GTID這種資料結構表示,後面會描述。
e859a28b-b66d-11e7-8371-000c291f347d:1-5
前一部分是SERVER_UUID,後面一部分是執行事務的唯一標誌集合,在內部使用GTID_SET中某個SIDNO對應的INTERVAL節點表示,後面會描述。
二、SERVER_UUID的產生
既然說到了SERVER_UUID這裡就開始討論SERVER_UUID的產生。
SERVER_UUID實際上是一個32位元組+1位元組(/0)的字串。MySQL啟動的時候會調用
INIT_SERVER_AUTO_OPTIONS()讀取
AUTO.CNF檔案。如果沒有讀取到則調用
GENERATE_SERVER_UUID()調用產生一個SERVER_ID。
實際上在這個函數裡面會看到SERVER_UUID至少和下面部分有關:
MySQL啟動時間
線程LWP有關
一個隨機的記憶體位址有關
請看程式碼片段:
const time_t save_server_start_time= server_start_time; //擷取MySQL
啟動時間 server_start_time+= ((ulonglong)current_pid << 48) + current_pid;//加入Lwp號運算 thd->status_var.bytes_sent= (ulonglong)thd;//這是一個記憶體指標 lex_start(thd); func_uuid= new (thd->mem_root) Item_func_uuid(); func_uuid->fixed= 1; func_uuid->val_str(&uuid); //這個函數裡面有具體的運算過程
獲得這些資訊後會進入Item_func_uuid::val_str做運算返回,有興趣的朋友可以深入看一下,最終會產生一個SERVER_UUID並且拷貝到實際的SERVER_UUID中如下:
strncpy(server_uuid, uuid.c_ptr(), UUID_LENGTH);
調用棧幀:
#0 init_server_auto_options () at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:3810#1 0x0000000000ec625e in mysqld_main (argc=97, argv=0x2e9af08) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/mysqld.cc:4962#2 0x0000000000ebd604 in main (argc=10, argv=0x7fffffffe458) at /root/mysql5.7.14/percona-server-5.7.14-7/sql/main.cc:25
三、SERVER_UUID的內部表示BINARY_LOG::UUID
BINARY_LOG::UUID是SERVER_UUID的內部表示實際上核心就是一個16位元組的記憶體空間,如下:
/** The number of bytes in the data of a Uuid. */ static const size_t BYTE_LENGTH= 16; /** The data for this Uuid. */ unsigned char bytes[BYTE_LENGTH];
SERVER_UUID和BINARY_LOG::UUID之間可以互相轉換,在SID_MAP中BINARY_LOG::UUID表示的SERVER_UUID實際上就是其SID。
四、類結構GTID
本結構是單個GTID的內部表示其核心元素包括:
/// SIDNO of this Gtid. rpl_sidno sidno; /// GNO of this Gtid. rpl_gno gno;
其中gno就是我們說的事務唯一標誌,而SIDNO其實是SERVER_UUID的內部表示BINARY_LOG::UUID(SID)通過HASH演算法得出的一個尋找表中的值。參考函數SID_MAP::ADD_SID本函數則根據BINARY_LOG::UUID(SID)返回一個SIDNO。
五、類結構SID_MAP
既然說到了HASH演算法那麼需要一個內部結構來儲存這種整個hash尋找表,在MySQL中使用SID_MAP來作為這樣一個結構,其中包含一個可變數組和一個HASH尋找表其作用用來已經在注釋裡面給出。全域只有一個SID_MAP。會在GTID模組初始化的時候分配記憶體。SID_MAP核心元素如下:
/// Read-write lock that protects updates to the number of SIDNOs. mutable Checkable_rwlock *sid_lock; /** Array that maps SIDNO to SID; the element at index N points to a Node with SIDNO N-1. */ Prealloced_array<Node*, 8, true>_sidno_to_sid; //因為sidno是一個連續的數值那麼更具sidno找到sid只需要簡單的做 //數組尋找即可這裡將node指標存入 /** Hash that maps SID to SIDNO. The keys in this array are of type rpl_sid. */ HASH _sid_to_sidno; //因為sid是一個資料結構其核心為bytes關KVStore for Redis了16位元組根據server_uuid //轉換而來的無規律記憶體空間,需要使用hash尋找錶快速定位 /** Array that maps numbers in the interval [0, get_max_sidno()-1] to SIDNOs, in order of increasing SID. @see Sid_map::get_sorted_sidno. */ Prealloced_array<rpl_sidno, 8, true> _sorted;//額外的一個關於sidno的數組,具體作用未知
這裡在看一下可變數組中的指標元素NODE的類型:
struct Node { rpl_sidno sidno; //sid hash no rpl_sid sid; //sid };
其實他就是一個SIDNO和SID的對應。
六、類結構GTID_SET
本結構是一個關於某種類型GTID總的集合,比如我們熟知的有EXECUTE_GTID集合,PURGE_GTID集合。我們知道在一個EXECUTE_GTID集合中可能包含多個資料庫的GTID也就是多個SIDNO同時存在的情況,並且可能存在某個資料庫的GTID出現區間的情況如下:
| gtid_executed | 3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,da267088-9c22-11e7-ab56-5254008768e3:1-34 |
這裡3558703b-de63-11e7-91c3-5254008768e3的GNO出現了多個區間:
那麼這種情況下內部表示應該是數組加區間鏈表的方式,當然MySQL內部正是這樣實現的。我們來看核心元素:
/** Array where the N'th element contains the head pointer to the intervals of SIDNO N+1. */ Prealloced_array<Interval*, 8, true> m_intervals;//每個sidno 包含一個Interval 單項鏈表,由next指標串連 這就完成了比如分割GTID的問題 /// Linked list of free intervals. Interval *free_intervals; //閒置interval串連在這個鏈表上 /// Linked list of chunks. Interval_chunk *chunks; //全域的一個Interval 鏈表 所有的Interval空間都串連在上面
7、GTID_SET的關聯類別結構INTERVAL
它正是前面說到的表示GTID區間如下:
他的內部類就是INTERVAL類結構我們看一下核心元素就懂了:
/// The first GNO of this interval. rpl_gno start; /// The first GNO after this interval. rpl_gno end; /// Pointer to next interval in list. Interval *next;
非常簡單起始GNO加一個NEXT指標,標示了一個區間。
8、類結構GTID_STATE
本結構也是在資料庫啟動的時候和SID_MAP一起進行初始化,也是一個全域的變數。
我們熟知的參數幾個參數如下:
GTID_EXECUTED
GTID_OWNED
GTID_PURGED
都來自於次,當然除了以上的我們常見的還包含了其他一些核心元素我們來具體看看:
/// The Sid_map used by this Gtid_state. mutable Sid_map *sid_map; //使用sid_map /** The set of GTIDs that existed in some previously purged binary log. This is always a subset of executed_gtids. */ Gtid_set lost_gtids; //對應gtid_purged參數,這個參數一般由Mysql自動維護除非手動設定了gtid_purged參數 /* The set of GTIDs that has been executed and stored into gtid_executed table. */ Gtid_set executed_gtids; //對應gtid_executed參數,這個參數一般由Mysql主動維護 /* The set of GTIDs that exists only in gtid_executed table, not in binlog files. */ Gtid_set gtids_only_in_table;//正常來講對於主庫這個集合始終為空白因為主庫不可能存在只在mysql.gtid_executed表而不再binlog中的gtid,但是從庫則必須開啟log_slave_updates和binlog才會達到這個效果,//否則binlog不包含relay的Gtid的只能包含在mysql.gtid_executed表中,那麼這種情況下Gtid_set gtids_only_in_table是始終存在的。具體後面還會解釋。 /* The previous GTIDs in the last binlog. */ Gtid_set previous_gtids_logged;//包含上一個binlog已經執行的所有的在binlog的Gtid /// The set of GTIDs that are owned by some thread. Owned_gtids owned_gtids;//當前所有線程擁有的全部Gtid集合 /// The SIDNO for this server. rpl_sidno server_sidno;//就是伺服器server_uuid對應sid hash出來的sidno
9、類結構 OWNED_GTIDS
這個結構包含當前線程所包含的所有正在持有的GTID集合,為事務分配GTID會先將這個GTID和線程號加入到給OWNED_GTIDS然後將線程的THD->OWNED_GTID指向這個GTID。
參考函數GTID_STATE::ACQUIRE_OWNERSHIP,在COMMIT的最後階段會將這個GTID從OWNED_GTIDS中移除參考函數OWNED_GTIDS::REMOVE_GTID並且將他加入到GTID_STATE::EXECUTED_GTIDS中。
這個過程會在後面進行描述。其核心元素包括:
/// Growable array of hashes. Prealloced_array<HASH*, 8, true> sidno_to_hash;
這樣一個每個SIDNO都會有HASH結構其HASH的內容則是:
struct Node { /// GNO of the group. rpl_gno gno; /// Owner of the group. my_thread_id owner; };
這樣一個結構體,我們看到其中包含了GNO和線程ID。
十、類結構GTID_TABLE_PERSISTOR
本結構主要是包含一些操作MySQL.GTID_EXECUTED表的函數介面
主要包含:
Insert the gtid into table.int save(THD *thd, const Gtid *gtid);
Insert the gtid set into table.int save(const Gtid_set *gtid_set);
Delete all rows from the table.int reset(THD *thd);
Fetch gtids from gtid_executed table and store them into
gtid_executed set.int fetch_gtids(Gtid_set *gtid_set);
Compress the gtid_executed table completely by employing one or more transactions.int compress(THD *thd);
Write a gtid interval into the gtid_executed table.
int write_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno gno_end);
Update a gtid interval in the gtid_executed table.
int update_row(TABLE *table, const char *sid,rpl_gno gno_start, rpl_gno new_gno_end);
Delete all rows in the gtid_executed table.int delete_all(TABLE *table);
這些方法也是確定什麼時候讀/寫MySQL.GTID.EXECUTED的斷點。
十一、內部結構圖示
為了能夠用圖的方式解釋這些類結構之間的關係,我修改MySQL.GTID_EXECUTED表和AUTO.CNF構造出了這種有區間的GTID案例,同時在源碼處增加列印代碼將啟動完成後的get_executed_gtids/get_lost_gtids/get_gtids_only_in_table/get_previous_gtids_logged輸出到了日誌。但是線上上情況下很難見到這種有區間的GTID。
假設某一時刻我們資料庫啟動後各種GTID如下():
2017-12-12T04:10:42.768153Z 0 [Note] gtid_state->get_executed_gtids:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-35,
da267088-9c22-11e7-ab56-5254008768e3:1-34
2017-12-12T04:10:42.768191Z 0 [Note] gtid_state->get_lost_gtids:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34
2017-12-12T04:10:42.768226Z 0 [Note] gtid_state->get_gtids_only_in_table:
Gtid have:3558703b-de63-11e7-91c3-5254008768e3:1-6:20-30,
da267088-9c22-11e7-ab56-5254008768e3:1-34
2017-12-12T04:10:42.768260Z 0 [Note] gtid_state->get_previous_gtids_logged:Gtid have:3558703b-de63-11e7-91c3-5254008768e3:7-19:31-35
啟動後我們馬上執行了一個事務,這個事務正處於ORDERED_COMMIT的FLUSH階段由GTID_STATE::ACQUIRE_OWNERSHIP獲得了一個GTID那麼它正在OWNED_GTIDS中,所以這個時候的圖如下:
十二、本文小結
學習完本節至少能夠學習到:
1、SERVER_UUID是什麼,如何產生,按照什麼規則產生
2、GTID內部是如何表示
3、SERVER_UUID和GTID內部表示之間的聯絡
4、 GTID_EXECUTED/GTID_OWNED/GTID_PURGED表示了什麼具體對應哪個記憶體結構,當然這些將在後面的文章中多次提到,也會加深對它的瞭解。
如果有源碼閱讀能力的朋友可以按照這個架構繼續深入學習。
對本文有任何疑問可掃碼添加原文作者
知數堂
葉金榮與吳炳錫聯合打造
領跑IT精英培訓
行業資深專家強強聯合,傾心定製
MySQL實戰/MySQL最佳化 / Python/ SQL最佳化
數門精品課程
緊隨技術發展趨勢,定期最佳化培訓教案
融入大量生產案例,貼合企業一線需求
社群陪伴學習,一次報名,可學3期
DBA、開發工程師必修課
上千位學員已華麗轉身,薪資翻番,職位提升
改變已悄然發生,你還在等什嗎?
掃碼下載知數堂精品課程試聽視頻
(MySQL 實戰/最佳化、Python開發,及SQL最佳化等課程)
密碼:hg3h