Mysql 5.7 Gtid內部學習(六) Mysql啟動初始化Gtid模組
簡書地址:
本節也是一個重頭戲,後面的故障案例也和本節有關。本節將詳細介紹Gtid模組的初始化,以及什麼時候讀取了我們前文提及的兩個Gtid持久化介質:
- binlog檔案
- mysql.gtid_executed表
此外也會描述他們的讀取方式。
同時分析這個步驟我也將在重點步驟分為兩種情況來分別討論:
- 主庫開啟Gtid開啟binlog。
- 從庫開啟Gtid開啟binlog不開啟log_slave_updates引數。
因為這兩種使我們通常設定的方式,下面簡稱主庫和從庫。
一、初始化Gtid 模組全域性變數記憶體空間
首先初始化Gtid 幾個Global 記憶體空間包括 Gtid_state\Sid_map\gtid_table_persistor
這個呼叫由mysqld.cc調入gtid_server_init()。
if (init_server_components()) unireg_abort(MYSQLD_ABORT_EXIT);
其中init_server_components()會初始化很多模組Gtid只是其中很小的一個,Innodb就在這裡初始化。
gtid_server_init()函式片段如下:
(!(global_sid_lock= new Checkable_rwlock( #ifdef HAVE_PSI_INTERFACE key_rwlock_global_sid_lock #endif )) || !(gtid_mode_lock= new Checkable_rwlock( #ifdef HAVE_PSI_INTERFACE key_rwlock_gtid_mode_lock #endif )) || !(global_sid_map= new Sid_map(global_sid_lock)) || //new一個記憶體Sid_map記憶體空間出來 !(gtid_state= new Gtid_state(global_sid_lock, global_sid_map))||//new一個記憶體Gtid_state記憶體空間出來 !(gtid_table_persistor= new Gtid_table_persistor()));//new一個記憶體Gtid_table_persistor記憶體空間出來
二、初始化獲得本資料庫的server_uuid
這個初始化過程在前文提到了,無非就是透過my.cnf獲得server_uuid,如果沒有則重新生成,具體可以參考一下前文這裡不再過多描述。
if (init_server_auto_options()) { sql_print_error("Initialization of the server's UUID failed because it could" " not be read from the auto.cnf file. If this is a new" " server, the initialization failed because it was not" " possible to generate a new UUID."); unireg_abort(MYSQLD_ABORT_EXIT); }
三、初始化Gtid_state全域性結構
global_sid_lock->rdlock(); int gtid_ret= gtid_state->init();//將server_uuid對應的sid(Uuid)和sidno加入到 Sid_map中。 global_sid_lock->unlock(); if (gtid_ret) unireg_abort(MYSQLD_ABORT_EXIT);
其實本步驟也是完成了sidno的加入Sid_map中,有興趣的可以參考int Gtid_state::init()函式邏輯非常簡單。
四、讀取mysql.gtid_executed表
這一步開始讀取我們的第一個Gtid持久化介質mysql.gtid_executed表,其最終呼叫為Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)其原理為一行一行的讀取mysql.gtid_executed表的內容加入到Gtid_state.executed_gtids中,我們來看原始碼:
// Initialize executed_gtids from mysql.gtid_executed table. if (gtid_state->read_gtid_executed_from_table() == -1) unireg_abort(1);
Gtid_state::read_gtid_executed_from_table只是一層簡單的封裝如下:
int Gtid_state::read_gtid_executed_from_table() { return gtid_table_persistor->fetch_gtids(&executed_gtids); }
接下來看看Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)函式邏輯片段
if ((err= table->file->ha_rnd_init(true))) { ret= -1; goto end; } while(!(err= table->file->ha_rnd_next(table->record[0]))) //開始一行一行讀取資料 { /* Store the gtid into the gtid_set */ /** @todo: - take only global_sid_lock->rdlock(), and take gtid_state->sid_lock for each iteration. - Add wrapper around Gtid_set::add_gno_interval and call that instead. */ global_sid_lock->wrlock(); if (gtid_set->add_gtid_text(encode_gtid_text(table).c_str()) != //此處將讀取到的一行Gtid區間加入到Gtid_state.executed_gtids中。 RETURN_STATUS_OK) { global_sid_lock->unlock(); break; } global_sid_lock->unlock(); }
完成本步驟過後Gtid_state.executed_gtids將設定,主庫和從庫的設定不同
- 主庫因為mysql.gtid_executed只包含上一個binlog的全部Gtid所以,它不包含最新binlog的Gtid,所以在第七步還需要修正這個值。
- 從庫因為mysql.gtid_executed會實時更新,因此它包含了全部的Gtid。
五、定義中間變數和獲得指標
本步驟是一個非關鍵步驟但是定義了一些中間變數而且定義了4個指標來分別獲得Gtid_state四個記憶體變數的地址,方便操作。
if (opt_bin_log) //如果binlog開啟 { /* Initialize GLOBAL.GTID_EXECUTED and GLOBAL.GTID_PURGED from gtid_executed table and binlog files during server startup. */ Gtid_set *executed_gtids= const_cast<Gtid_set *>(gtid_state->get_executed_gtids());//獲得Gtid_state.executed_gtids的指標 Gtid_set *lost_gtids= const_cast<Gtid_set *>(gtid_state->get_lost_gtids());//獲得gtid_state.get_lost_gtids的指標 Gtid_set *gtids_only_in_table= const_cast<Gtid_set *>(gtid_state->get_gtids_only_in_table());//獲得gtid_state.get_lost_gtids的指標 Gtid_set *previous_gtids_logged= const_cast<Gtid_set *>(gtid_state->get_previous_gtids_logged());//獲得gtid_state.previous_gtids_logged的指標 Gtid_set purged_gtids_from_binlog(global_sid_map, global_sid_lock);//定義臨時變數用於儲存從binlog中掃描到已經丟棄的Gtid事物。 Gtid_set gtids_in_binlog(global_sid_map, global_sid_lock);//定義中間變數binlog中包含的所有Gtid事物包括丟棄的。 Gtid_set gtids_in_binlog_not_in_table(global_sid_map, global_sid_lock);//定義中間變數沒有存放在表中而在binlog中存在過的Gtid事物, //顯然主庫包含這樣一個集合,因為主庫的gtids_in_binlog>gtids_only_in_table,而從庫同樣也不包含這樣一個集合因為從庫的全部Gtid事物都在表中。
六、讀取binlog檔案
本步驟將會讀取我們提及的第二個Gtid持久化介質binlog,其讀取方式為先反向讀取獲得 gtids_in_binlog然後正向讀取獲得 purged_gtids_from_binlog,並且這裡正向讀取purged_gtids_from_binlog將會受到binlog_gtid_simple_recovery引數的影響。同時我們前文所描述5.7 中Previous gtid Event會在沒有開啟Gtid的binlog也包含這個event,將在這部體現出它的價值。
if (mysql_bin_log.init_gtid_sets(>ids_in_binlog, &purged_gtids_from_binlog, opt_master_verify_checksum, true/*true=need lock*/, NULL/*trx_parser*/, NULL/*gtid_partial_trx*/, true/*is_server_starting*/))
我們發現他實際上就是呼叫bool MYSQL_BIN_LOG::init_gtid_sets()函式我們繼續看這個函式重要程式碼片段:
list<string> filename_list; //定義一個string list來儲存檔名 LOG_INFO linfo; int error; list<string>::iterator it;//定義一個list的正向迭代器 list<string>::reverse_iterator rit;//定義一個list的反向迭代器 for (error= find_log_pos(&linfo, NULL, false/*need_lock_index=false*/); !error; //這部分實際上就是將檔名全部加入到這個list中 error= find_next_log(&linfo, false/*need_lock_index=false*/)) { DBUG_PRINT("info", ("read log filename '%s'", linfo.log_file_name)); filename_list.push_back(string(linfo.log_file_name)); } if (error != LOG_INFO_EOF) { DBUG_PRINT("error", ("Error reading %s index", is_relay_log ? "relaylog" : "binlog")); goto end; } if (all_gtids != NULL) //資料庫啟動初始化的情況下all_gtids不會為NULL,但是如果是做purge binary logs命令等刪除binlog log all_gtid會傳入NULL { rit= filename_list.rbegin(); //反向迭代器指向list尾部 bool can_stop_reading= false; reached_first_file= (rit == filename_list.rend());//如果只有一個binlog則為true while (!can_stop_reading && !reached_first_file) //開始反向迴圈掃描來獲得gtids_in_binlog(all_gtids)集合 { const char *filename= rit->c_str(); //獲取檔名 rit++; reached_first_file= (rit == filename_list.rend());//如果達到第一個檔案則為true表示掃描完成 switch (read_gtids_from_binlog(filename, all_gtids, reached_first_file ? lost_gtids : NULL, NULL/* first_gtid */, sid_map, verify_checksum, is_relay_log)) //透過函式read_gtids_from_binlog讀取這個binlog檔案 { case ERROR: { error= 1; goto end; } case GOT_GTIDS: //如果掃描本binlog有PREVIOUS GTID EVENT和GTID EVENT 則break 跳出迴圈且設定can_stop_reading= true { can_stop_reading= true; break; } case GOT_PREVIOUS_GTIDS://如果掃描本binlog只有PREVIOUS GTID EVENT 則進入邏輯判斷 { if (!is_relay_log)//我們只考慮binlog 不會是relaylog 那麼 break 跳出迴圈且設定can_stop_reading= true, //注意這裡並不受到binlog_gtid_simple_recovery引數的影響,我們知道5.7.5過後每一個binlog都 //包含了PREVIOUS GTID EVENT實際上即使沒有開啟GTID這裡也會跳出迴圈,則只是掃描了最後一個binlog 檔案 can_stop_reading= true; break; } case NO_GTIDS: //如果沒有找到PREVIOUS GTID EVENT和GTID EVENT 則做如下邏輯,實際上5.7過後不可能出現這種問題,因為必然包含了PREVIOUS GTID EVENT //即便是沒有開啟GTID,所以反向查詢一定會在掃描最後一個檔案後跳出迴圈 { if (binlog_gtid_simple_recovery && is_server_starting && !is_relay_log) //這裡受到了binlog_gtid_simple_recovery引數的影響,但是我們知道這個分支是不會執行的。除非這個資料庫是升級的並且沒有開啟Gtid { DBUG_ASSERT(all_gtids->is_empty());//斷言all_gtids還是沒有找到 DBUG_ASSERT(lost_gtids->is_empty());//斷言lost_gtids還是沒有找到 goto end;//結束掃描,從這裡我們發現如果mysql是升級而來的一定要注意這個問題,設定binlog_gtid_simple_recovery可能拿不到正確的GTID,對於升級 //最好使用master-slave 進行升級,可以規避這個風險。 } /*FALLTHROUGH*/ } case TRUNCATED: { break; } } } //中間還有一部分處理relaylog的佔時沒有去研究接下來就是正向查詢獲得purged_gtids_from_binlog(lost_gtids) if (lost_gtids != NULL && !reached_first_file)//如果前面的掃描沒有掃描完全部的binlog,這實際在5.7中是肯定的。 { for (it= filename_list.begin(); it != filename_list.end(); it++)//進行正向查詢 { /* We should pass a first_gtid to read_gtids_from_binlog when binlog_gtid_simple_recovery is disabled, or else it will return right after reading the PREVIOUS_GTIDS event to avoid stall on reading the whole binary log. */ Gtid first_gtid= {0, 0}; const char *filename= it->c_str();//獲得檔名指標 switch (read_gtids_from_binlog(filename, NULL, lost_gtids, binlog_gtid_simple_recovery ? NULL : &first_gtid, sid_map, verify_checksum, is_relay_log)) { case ERROR: { error= 1; /*FALLTHROUGH*/ } case GOT_GTIDS: //如果掃描本binlog有PREVIOUS GTID EVENT和GTID EVENT 則跳出迴圈直達end { goto end; } case NO_GTIDS: //這裡如果binlog不包含GTID EVENT和PREVIOUS GTID EVENT其處理邏輯一致 case GOT_PREVIOUS_GTIDS: { if (binlog_gtid_simple_recovery) //這裡受到了binlog_gtid_simple_recovery。如果設定為ON,實際上在5.7過後 goto end; //PREVIOUS GTID EVENT是一定命中的,可以得到正確的結果,但是如果是5.6升級而來 /*FALLTHROUGH*/ //則binlog不包含PREVIOUS GTID EVENT則purged_gtids_from_binlog(lost_gtids)獲取為空 //如果在5.7中關閉了GTID,這種情況這裡雖然PREVIOUS GTID EVENT命中但是任然 //不會跳出迴圈goto end,繼續下一個檔案掃描。 } case TRUNCATED: { break; } } }
到這裡我們分析了反向查詢和正向查詢,我們程式碼註釋上也說明了binlog_gtid_simple_recovery作用,因為有了PREVIOUS GTID EVENT的支援,5.7.6過後這個引數預設都是設定為true,如果在Gtid關閉的情況下設定binlog_gtid_simple_recovery為flase可能需要掃描大量的binlog才會確定purged_gtids_from_binlog這個集合,這可能出現在兩個地方:
- 如這裡討論的Mysql重啟的時候。
- 如前文所討論在purge binary logs to或者操作引數expire_logs_days設定的時間刪除binlog的時候。
這裡也是我後文描述的第二個案例出現的原因。
正常情況下到這裡我們的gtids_in_binlog和purged_gtids_from_binlog已經獲取:
- 主庫gtids_in_binlog包含了所有最新的Gtid事物(包含丟棄的)而purged_gtids_from_binlog包含了已經丟棄的Gtid事物。
- 從庫壓根沒有binlog因此gtids_in_binlog和purged_gtids_from_binlog為空集合。
七、對Gtid_state.executed_gtids和mysql.gtid_executed表的修正
如第四步描述主庫透過讀取mysql.gtid_executed表獲得的Gtid_state.executed_gtids並不是最新的,所以整理需要修正,程式碼如下:
if (!gtids_in_binlog.is_empty() && //如果gtids_in_binlog不為空,從庫為空不走這個邏輯了,這裡主要是主庫對Gtid_state.executed_gtids的修正 !gtids_in_binlog.is_subset(executed_gtids)) //並且executed_gtids是gtids_in_binlog的子集 { gtids_in_binlog_not_in_table.add_gtid_set(>ids_in_binlog); if (!executed_gtids->is_empty()) gtids_in_binlog_not_in_table.remove_gtid_set(executed_gtids); //將不在表中的GTID及gtids_in_binlog-executed_gtids 加入到gtids_in_binlog_not_in_table if (gtid_state->save(>ids_in_binlog_not_in_table) == -1)//這裡將gtids_in_binlog_not_in_table這個Gtid集合儲存到mysql.gtid_executed表中完成修正 { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); } executed_gtids->add_gtid_set(>ids_in_binlog_not_in_table);//最後在executed_gtids中加入這個gtids_in_binlog_not_in_table,這個完成executed_gtids就是最新的Gtid_set了,完成了Gtid_state.executed_gtids的修正 }
這一步完全是主庫才會觸發的邏輯:
- 主庫完成這一步Gtid_state.executed_gtids和mysql.gtid_executed表都將修正到最新的Gtid集合。
- 從庫不做這個邏輯,因為從庫的Gtid_state.executed_gtids和mysql.gtid_executed表本來就是最新的。
到這裡Gtid_state.executed_gtids也就是我們的gtid_executed變數初始化已經完成mysql.gtid_executed表已經修正。
八、初始化Gtid_state.gtids_only_in_table
由於上一步已經獲得了完整的的Gtid_state.executed_gtids 集合,這裡獲得Gtid_state.gtids_only_in_table只需要簡單的gtids_only_in_table= executed_gtids - gtids_in_binlog相減即可。
/* gtids_only_in_table= executed_gtids - gtids_in_binlog */ if (gtids_only_in_table->add_gtid_set(executed_gtids) != //這裡將executed_gtids加入到gtids_only_in_table RETURN_STATUS_OK) { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); } gtids_only_in_table->remove_gtid_set(>ids_in_binlog); //這裡將去掉gtids_in_binlog
這一步主庫和從庫如下:
- 主庫由於存在Gtid_state.executed_gtids是最新的同時gtids_in_binlog也是最新的所以Gtid_state. gtids_only_in_table是一個空集合。
- 從庫由於Gtid_state.executed_gtids是最新的但是gtids_in_binlog 是一個空集合,所以Gtid_state. gtids_only_in_table和Gtid_state.executed_gtids相等。
九、初始化Gtid_state.lost_gtids
這一步開始獲取Gtid_state.lost_gtids也就是我們的gtid_purged變數,這裡只需要簡單的用Gtid_state.gtids_only_in_table + purged_gtids_from_binlog;即可,他們都已經獲取
/* lost_gtids = executed_gtids - (gtids_in_binlog - purged_gtids_from_binlog) = gtids_only_in_table + purged_gtids_from_binlog; */ if (lost_gtids->add_gtid_set(gtids_only_in_table) != RETURN_STATUS_OK || //將gtids_only_in_table這個集合加入lost_gtids lost_gtids->add_gtid_set(&purged_gtids_from_binlog) != //將purged_gtids_from_binlog加入到這個集合 RETURN_STATUS_OK) { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); }
這一步主庫和從庫如下:
- 主庫由於Gtid_state.gtids_only_in_table為空集合,而purged_gtids_from_binlog則是獲取的第一個binlog Previous gtid Event的Gtid。所以正常情況下Gtid_state.lost_gtids就等於第一個binlog的binlog Previous gtid Event 的Gtid。
- 從庫由於Gtid_state.gtids_only_in_table和Gtid_state.executed_gtids相等而purged_gtids_from_binlog是空集合,所以正常情況下從庫的Gtid_state.lost_gtids就等於就等於Gtid_state.executed_gtids。也就是gtid_purged變數和gtid_executed變數相等。
到這裡gtid_purged變數和gtid_executed變數以及mysql.gtid_executed表都已經初始化完成。
十、初始化Gtid_state.previous_gtids_logged
這個值沒有變數能夠看到,它代表是直到上一個binlog所包含的全部的binlog Gtid。
/* Prepare previous_gtids_logged for next binlog */ if (previous_gtids_logged->add_gtid_set(>ids_in_binlog) !=//很明顯將掃描到的gtids_in_binlog的這個集合加入即可。 RETURN_STATUS_OK) { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); }
很明顯因為啟動的時候binlog會切換所以簡單的將掃描到gtids_in_binlog加入到集合即可。
這一步主庫和從庫如下:
- 主庫gtids_in_binlog包含全部Gtid事物。所以gtid_state.previous_gtids_logged就包含全部binlog中的Gtid事物。但是之後會做binlog切換。
- 從庫gtids_in_binlog為空,顯然gtid_state.previous_gtids_logged也為空。
十一、本節小結
透過讀取mysql.gtid_executed和binlog,然後經過一系列的運算後,我們的Gtid模組初始化完成。4個記憶體變數和mysql.gtid_executed都得到了初始化,總結如下:
- mysql.gtid_executed表
- 主庫在第四步讀取,在第七步的修正完成初始化,它包含了現有的全部的Gtid事物。
- 從庫在第四步讀取,因為從庫mysql.gtid_executed本來就是最新的不需要更改。
- Gtid_state.executed_gtids和它對應了變數gtid_executed
- 主庫透過第四步和第七步將Gtid_state.executed_gtids初始化,它包含了全部的Gtid事物。
- 從庫透過第四步就已經全部初始化完成,它包含了現有的全部的Gtid事物。
- Gtid_state.gtids_only_in_table
- 主庫透過第八步進行初始化,主庫Gtid_state. gtids_only_in_table是一個空集合。
- 從庫透過第八步進行初始化,從庫Gtid_state. gtids_only_in_table和Gtid_state.executed_gtids相等。
- Gtid_state.lost_gtids它對應了變數gtid_purged。
- 主庫透過第九步進行初始化,Gtid_state.lost_gtids就是第一個binlog Previous gtid Event的Gtid。
- 主庫透過第九步進行初始化,Gtid_state.lost_gtids等於Gtid_state.executed_gtids
- Gtid_state.previous_gtids_logged。
- 主庫透過第十步進行初始化,gtid_state.previous_gtids_logged就包含全部binlog中的Gtid事物
- 從庫透過第十步進行初始化,gtid_state.previous_gtids_logged為空集合。
注意本節第五步包含了binlog檔案的讀取方法以及binlog_gtid_simple_recovery引數的作用
學習完本節至少能夠學習到:
- 1、mysql.gtid_executed是如何以及何時讀取的。
- 2、binlog 檔案中的Gtid何時讀取。
- 3、整個Gtid模組的初始化流程及細節。
- 4、binlog_gtid_simple_recovery引數的作用。
作者微信:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2148839/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Mysql 5.7 Gtid內部學習(一) 導讀MySql
- Mysql 5.7 Gtid內部學習(四) mysql.gtid_executed表Previous gtid Event的改變MySql
- Mysql 5.7 Gtid內部學習(二) Gtid相關內部資料結構MySql資料結構
- Mysql 5.7 Gtid內部學習(八) Gtid帶來的運維改變MySql運維
- Mysql 5.7 Gtid內部學習(十) 實際案例(二)MySql
- Mysql 5.7 Gtid內部學習(九) 實際案例(一)MySql
- Mysql 5.7 Gtid內部學習(五) mysql.gtid_executed表及其他變數更改時機MySql變數
- Mysql 5.7 Gtid內部學習(三) Gtid和Last_commt/sequnce_number的生成時機MySqlAST
- Mysql 5.7 Gtid內部學習(七) 總結binlog_gtid_simple_recovery引數帶來的影響MySql
- mysql 5.7 GTID主從配置MySql
- MySQL5.7GTID淺析MySql
- MYSQL5.7-GTID概要翻譯MySql
- MYSQL5.7 MASTER-SLAVE 線上關閉和啟動GTIDMySqlAST
- GTID模組初始化
- MySQL5.7GTID運維實戰MySql運維
- MySQL 5.7 用mysqldump搭建gtid主從MySql
- MySQL 5.7 用xtrabackup搭建gtid主從MySql
- MySQL 5.7 基於GTID搭建主從複製MySql
- MySQL 5.7基於GTID的主從複製MySql
- MySQL 5.7 使用GTID方式搭建複製環境MySql
- MySQL 基礎知識梳理學習(四)—-GTIDMySql
- mysql replication之GTIDMySql
- MySQL GTID複製MySql
- MySQL 線上開啟&關閉GTID模式MySql模式
- MySQL GTID生命週期MySql
- MYSQL_GTID詳解MySql
- MySQL運維之binlog_gtid_simple_recovery(GTID)MySql運維
- MySQL 5.6 GTID 原理以及使用MySql
- MySQL 5.6 建立GTID主從複製 (GTID-based Replication)MySql
- 深入理解MySQL5.7GTID系列(七)binlog_gtid_simple_recovery引數的影響總結MySql
- MySQL運維實戰(7.1) 開啟GTID複製MySql運維
- 第3節:GTID模組初始化簡介和引數binlog_gtid_simple_recovery
- 基於GTID搭建主從MySQLMySql
- mysql GTID 主從複製概述MySql
- MySQL 5.7傳統複製到GTID線上切換(一主一從)MySql
- 【MySQL】UUID與GTID以及如何根據GTID找尋filename和positionMySqlUI
- Mysql基於GTID的複製模式MySql模式
- mysql從庫gtid間隙問題MySql