簡(jiǎn)書(shū)地址:
http://www.jianshu.com/p/fc836446cde0
本節(jié)也是一個(gè)重頭戲,后面的故障案例也和本節(jié)有關(guān)。本節(jié)將詳細(xì)介紹Gtid模塊的初始化,以及什么時(shí)候讀取了我們前文提及的兩個(gè)Gtid持久化介質(zhì):
清水網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、響應(yīng)式網(wǎng)站設(shè)計(jì)等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)2013年至今到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。
此外也會(huì)描述他們的讀取方式。
同時(shí)分析這個(gè)步驟我也將在重點(diǎn)步驟分為兩種情況來(lái)分別討論:
因?yàn)檫@兩種使我們通常設(shè)置的方式,下面簡(jiǎn)稱主庫(kù)和從庫(kù)。
首先初始化Gtid 幾個(gè)Global 內(nèi)存空間包括 Gtid_state\Sid_map\gtid_table_persistor
這個(gè)調(diào)用由mysqld.cc調(diào)入gtid_server_init()。
if (init_server_components()) unireg_abort(MYSQLD_ABORT_EXIT);
其中init_server_components()會(huì)初始化很多模塊Gtid只是其中很小的一個(gè),Innodb就在這里初始化。
gtid_server_init()函數(shù)片段如下:
(!(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一個(gè)內(nèi)存Sid_map內(nèi)存空間出來(lái) !(gtid_state= new Gtid_state(global_sid_lock, global_sid_map))||//new一個(gè)內(nèi)存Gtid_state內(nèi)存空間出來(lái) !(gtid_table_persistor= new Gtid_table_persistor()));//new一個(gè)內(nèi)存Gtid_table_persistor內(nèi)存空間出來(lái)
這個(gè)初始化過(guò)程在前文提到了,無(wú)非就是通過(guò)my.cnf獲得server_uuid,如果沒(méi)有則重新生成,具體可以參考一下前文這里不再過(guò)多描述。
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); }
global_sid_lock->rdlock(); int gtid_ret= gtid_state->init();//將server_uuid對(duì)應(yīng)的sid(Uuid)和sidno加入到 Sid_map中。 global_sid_lock->unlock(); if (gtid_ret) unireg_abort(MYSQLD_ABORT_EXIT);
其實(shí)本步驟也是完成了sidno的加入Sid_map中,有興趣的可以參考int Gtid_state::init()函數(shù)邏輯非常簡(jiǎn)單。
這一步開(kāi)始讀取我們的第一個(gè)Gtid持久化介質(zhì)mysql.gtid_executed表,其最終調(diào)用為Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)其原理為一行一行的讀取mysql.gtid_executed表的內(nèi)容加入到Gtid_state.executed_gtids中,我們來(lái)看源碼:
// 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只是一層簡(jiǎn)單的封裝如下:
int Gtid_state::read_gtid_executed_from_table() { return gtid_table_persistor->fetch_gtids(&executed_gtids); }
接下來(lái)看看Gtid_table_persistor::fetch_gtids(Gtid_set *gtid_set)函數(shù)邏輯片段
if ((err= table->file->ha_rnd_init(true))) { ret= -1; goto end; } while(!(err= table->file->ha_rnd_next(table->record[0]))) //開(kāi)始一行一行讀取數(shù)據(jù) { /* 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區(qū)間加入到Gtid_state.executed_gtids中。 RETURN_STATUS_OK) { global_sid_lock->unlock(); break; } global_sid_lock->unlock(); }
完成本步驟過(guò)后Gtid_state.executed_gtids將設(shè)置,主庫(kù)和從庫(kù)的設(shè)置不同
本步驟是一個(gè)非關(guān)鍵步驟但是定義了一些中間變量而且定義了4個(gè)指針來(lái)分別獲得Gtid_state四個(gè)內(nèi)存變量的地址,方便操作。
if (opt_bin_log) //如果binlog開(kāi)啟 { /* 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_state->get_executed_gtids());//獲得Gtid_state.executed_gtids的指針 Gtid_set *lost_gtids= const_cast (gtid_state->get_lost_gtids());//獲得gtid_state.get_lost_gtids的指針 Gtid_set *gtids_only_in_table= const_cast (gtid_state->get_gtids_only_in_table());//獲得gtid_state.get_lost_gtids的指針 Gtid_set *previous_gtids_logged= const_cast (gtid_state->get_previous_gtids_logged());//獲得gtid_state.previous_gtids_logged的指針 Gtid_set purged_gtids_from_binlog(global_sid_map, global_sid_lock);//定義臨時(shí)變量用于存儲(chǔ)從binlog中掃描到已經(jīng)丟棄的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);//定義中間變量沒(méi)有存放在表中而在binlog中存在過(guò)的Gtid事物, //顯然主庫(kù)包含這樣一個(gè)集合,因?yàn)橹鲙?kù)的gtids_in_binlog>gtids_only_in_table,而從庫(kù)同樣也不包含這樣一個(gè)集合因?yàn)閺膸?kù)的全部Gtid事物都在表中。
本步驟將會(huì)讀取我們提及的第二個(gè)Gtid持久化介質(zhì)binlog,其讀取方式為先反向讀取獲得 gtids_in_binlog然后正向讀取獲得 purged_gtids_from_binlog,并且這里正向讀取purged_gtids_from_binlog將會(huì)受到binlog_gtid_simple_recovery參數(shù)的影響。同時(shí)我們前文所描述5.7 中Previous gtid Event會(huì)在沒(méi)有開(kāi)啟Gtid的binlog也包含這個(gè)event,將在這部體現(xiàn)出它的價(jià)值。
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*/))
我們發(fā)現(xiàn)他實(shí)際上就是調(diào)用bool MYSQL_BIN_LOG::init_gtid_sets()函數(shù)我們繼續(xù)看這個(gè)函數(shù)重要代碼片段:
listfilename_list; //定義一個(gè)string list來(lái)存儲(chǔ)文件名 LOG_INFO linfo; int error; list ::iterator it;//定義一個(gè)list的正向迭代器 list ::reverse_iterator rit;//定義一個(gè)list的反向迭代器 for (error= find_log_pos(&linfo, NULL, false/*need_lock_index=false*/); !error; //這部分實(shí)際上就是將文件名全部加入到這個(gè)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) //數(shù)據(jù)庫(kù)啟動(dòng)初始化的情況下all_gtids不會(huì)為NULL,但是如果是做purge binary logs命令等刪除binlog log all_gtid會(huì)傳入NULL { rit= filename_list.rbegin(); //反向迭代器指向list尾部 bool can_stop_reading= false; reached_first_file= (rit == filename_list.rend());//如果只有一個(gè)binlog則為true while (!can_stop_reading && !reached_first_file) //開(kāi)始反向循環(huán)掃描來(lái)獲得gtids_in_binlog(all_gtids)集合 { const char *filename= rit->c_str(); //獲取文件名 rit++; reached_first_file= (rit == filename_list.rend());//如果達(dá)到第一個(gè)文件則為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)) //通過(guò)函數(shù)read_gtids_from_binlog讀取這個(gè)binlog文件 { case ERROR: { error= 1; goto end; } case GOT_GTIDS: //如果掃描本binlog有PREVIOUS GTID EVENT和GTID EVENT 則break 跳出循環(huán)且設(shè)置can_stop_reading= true { can_stop_reading= true; break; } case GOT_PREVIOUS_GTIDS://如果掃描本binlog只有PREVIOUS GTID EVENT 則進(jìn)入邏輯判斷 { if (!is_relay_log)//我們只考慮binlog 不會(huì)是relaylog 那么 break 跳出循環(huán)且設(shè)置can_stop_reading= true, //注意這里并不受到binlog_gtid_simple_recovery參數(shù)的影響,我們知道5.7.5過(guò)后每一個(gè)binlog都 //包含了PREVIOUS GTID EVENT實(shí)際上即使沒(méi)有開(kāi)啟GTID這里也會(huì)跳出循環(huán),則只是掃描了最后一個(gè)binlog 文件 can_stop_reading= true; break; } case NO_GTIDS: //如果沒(méi)有找到PREVIOUS GTID EVENT和GTID EVENT 則做如下邏輯,實(shí)際上5.7過(guò)后不可能出現(xiàn)這種問(wèn)題,因?yàn)楸厝话薖REVIOUS GTID EVENT //即便是沒(méi)有開(kāi)啟GTID,所以反向查找一定會(huì)在掃描最后一個(gè)文件后跳出循環(huán) { if (binlog_gtid_simple_recovery && is_server_starting && !is_relay_log) //這里受到了binlog_gtid_simple_recovery參數(shù)的影響,但是我們知道這個(gè)分支是不會(huì)執(zhí)行的。除非這個(gè)數(shù)據(jù)庫(kù)是升級(jí)的并且沒(méi)有開(kāi)啟Gtid { DBUG_ASSERT(all_gtids->is_empty());//斷言all_gtids還是沒(méi)有找到 DBUG_ASSERT(lost_gtids->is_empty());//斷言lost_gtids還是沒(méi)有找到 goto end;//結(jié)束掃描,從這里我們發(fā)現(xiàn)如果mysql是升級(jí)而來(lái)的一定要注意這個(gè)問(wèn)題,設(shè)置binlog_gtid_simple_recovery可能拿不到正確的GTID,對(duì)于升級(jí) //最好使用master-slave 進(jìn)行升級(jí),可以規(guī)避這個(gè)風(fēng)險(xiǎn)。 } /*FALLTHROUGH*/ } case TRUNCATED: { break; } } } //中間還有一部分處理relaylog的占時(shí)沒(méi)有去研究接下來(lái)就是正向查找獲得purged_gtids_from_binlog(lost_gtids) if (lost_gtids != NULL && !reached_first_file)//如果前面的掃描沒(méi)有掃描完全部的binlog,這實(shí)際在5.7中是肯定的。 { for (it= filename_list.begin(); it != filename_list.end(); it++)//進(jìn)行正向查找 { /* 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 則跳出循環(huán)直達(dá)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。如果設(shè)置為ON,實(shí)際上在5.7過(guò)后 goto end; //PREVIOUS GTID EVENT是一定命中的,可以得到正確的結(jié)果,但是如果是5.6升級(jí)而來(lái) /*FALLTHROUGH*/ //則binlog不包含PREVIOUS GTID EVENT則purged_gtids_from_binlog(lost_gtids)獲取為空 //如果在5.7中關(guān)閉了GTID,這種情況這里雖然PREVIOUS GTID EVENT命中但是任然 //不會(huì)跳出循環(huán)goto end,繼續(xù)下一個(gè)文件掃描。 } case TRUNCATED: { break; } } }
到這里我們分析了反向查找和正向查找,我們代碼注釋上也說(shuō)明了binlog_gtid_simple_recovery作用,因?yàn)橛辛薖REVIOUS GTID EVENT的支持,5.7.6過(guò)后這個(gè)參數(shù)默認(rèn)都是設(shè)置為true,如果在Gtid關(guān)閉的情況下設(shè)置binlog_gtid_simple_recovery為flase可能需要掃描大量的binlog才會(huì)確定purged_gtids_from_binlog這個(gè)集合,這可能出現(xiàn)在兩個(gè)地方:
這里也是我后文描述的第二個(gè)案例出現(xiàn)的原因。
正常情況下到這里我們的gtids_in_binlog和purged_gtids_from_binlog已經(jīng)獲?。?/p>
如第四步描述主庫(kù)通過(guò)讀取mysql.gtid_executed表獲得的Gtid_state.executed_gtids并不是最新的,所以整理需要修正,代碼如下:
if (!gtids_in_binlog.is_empty() && //如果gtids_in_binlog不為空,從庫(kù)為空不走這個(gè)邏輯了,這里主要是主庫(kù)對(duì)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這個(gè)Gtid集合存儲(chǔ)到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中加入這個(gè)gtids_in_binlog_not_in_table,這個(gè)完成executed_gtids就是最新的Gtid_set了,完成了Gtid_state.executed_gtids的修正 }
這一步完全是主庫(kù)才會(huì)觸發(fā)的邏輯:
到這里Gtid_state.executed_gtids也就是我們的gtid_executed變量初始化已經(jīng)完成mysql.gtid_executed表已經(jīng)修正。
由于上一步已經(jīng)獲得了完整的的Gtid_state.executed_gtids 集合,這里獲得Gtid_state.gtids_only_in_table只需要簡(jiǎn)單的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
這一步主庫(kù)和從庫(kù)如下:
這一步開(kāi)始獲取Gtid_state.lost_gtids也就是我們的gtid_purged變量,這里只需要簡(jiǎn)單的用Gtid_state.gtids_only_in_table + purged_gtids_from_binlog;即可,他們都已經(jīng)獲取
/* 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這個(gè)集合加入lost_gtids lost_gtids->add_gtid_set(&purged_gtids_from_binlog) != //將purged_gtids_from_binlog加入到這個(gè)集合 RETURN_STATUS_OK) { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); }
這一步主庫(kù)和從庫(kù)如下:
到這里gtid_purged變量和gtid_executed變量以及mysql.gtid_executed表都已經(jīng)初始化完成。
這個(gè)值沒(méi)有變量能夠看到,它代表是直到上一個(gè)binlog所包含的全部的binlog Gtid。
/* Prepare previous_gtids_logged for next binlog */ if (previous_gtids_logged->add_gtid_set(>ids_in_binlog) !=//很明顯將掃描到的gtids_in_binlog的這個(gè)集合加入即可。 RETURN_STATUS_OK) { global_sid_lock->unlock(); unireg_abort(MYSQLD_ABORT_EXIT); }
很明顯因?yàn)閱?dòng)的時(shí)候binlog會(huì)切換所以簡(jiǎn)單的將掃描到gtids_in_binlog加入到集合即可。
這一步主庫(kù)和從庫(kù)如下:
通過(guò)讀取mysql.gtid_executed和binlog,然后經(jīng)過(guò)一系列的運(yùn)算后,我們的Gtid模塊初始化完成。4個(gè)內(nèi)存變量和mysql.gtid_executed都得到了初始化,總結(jié)如下:
注意本節(jié)第五步包含了binlog文件的讀取方法以及binlog_gtid_simple_recovery參數(shù)的作用
學(xué)習(xí)完本節(jié)至少能夠?qū)W習(xí)到:
作者微信: