共計(jì) 7509 個(gè)字符,預(yù)計(jì)需要花費(fèi) 19 分鐘才能閱讀完成。
本篇內(nèi)容主要講解“MySQL 層事務(wù)提交的流程”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓丸趣 TV 小編來(lái)帶大家學(xué)習(xí)“MySQL 層事務(wù)提交的流程”吧!
本節(jié)將來(lái)解釋一下 MySQL 層詳細(xì)的提交流程,但是由于能力有限,這里不可能包含全部的步驟,只是包含了一些重要的并且我學(xué)習(xí)過(guò)的步驟。我們首先需要來(lái)假設(shè)參數(shù)設(shè)置,因?yàn)槟承﹨?shù)的設(shè)置會(huì)直接影響到提交流程,我們也會(huì)逐一解釋這些參數(shù)的含義。本節(jié)介紹的大部分內(nèi)容都集中在函數(shù) MYSQL_BIN_LOG::prepare 和 MYSQL_BIN_LOG::ordered_commit 之中。
一、參數(shù)設(shè)置
本部分假定參數(shù)設(shè)置為:
binlog_group_commit_sync_delay:0
binlog_group_commit_sync_no_delay_count:0
binlog_order_commits:ON
sync_binlog:1
binlog_transaction_dependency_tracking:COMMIT_ORDER
關(guān)于參數(shù) binlog_transaction_dependency_tracking 需要重點(diǎn)說(shuō)明一下。我們知道 Innodb 的行鎖是在語(yǔ)句運(yùn)行期間就已經(jīng)獲取,因此如果多個(gè)事務(wù)同時(shí)進(jìn)入了提交流程(prepare 階段),在 Innodb 層提交釋放 Innodb 行鎖資源之前各個(gè)事務(wù)之間肯定是沒(méi)有行沖突的,因此可以在從庫(kù)端并行執(zhí)行。在基于 COMMIT_ORDER 的并行復(fù)制中,last commit 和 seq number 正是基于這種思想生成的,如果 last commit 相同則視為可以在從庫(kù)并行回放,在 19 節(jié)我們將解釋從庫(kù)判定并行回放的規(guī)則。而在基于 WRITESET 的并行復(fù)制中,last commit 將會(huì)在 WRITESET 的影響下繼續(xù)降低,來(lái)使從庫(kù)獲得更好的并行回放效果,但是它也是 COMMIT_ORDER 為基礎(chǔ)的,這個(gè)下一節(jié)將討論。我們這節(jié)只討論基于 COMMIT_ORDER 的并行復(fù)制中 last commit 和 seq number 的生成方式。
而 sync_binlog 參數(shù)則有兩個(gè)功能:
sync_binlog=0:binary log 不 sync 刷盤,依賴于 OS 刷盤機(jī)制。同時(shí)會(huì)在 flush 階段后通知 DUMP 線程發(fā)送 Event。
sync_binlog=1:binary log 每次 sync 隊(duì)列形成后都進(jìn)行 sync 刷盤,約等于每次 group commit 進(jìn)行刷盤。同時(shí)會(huì)在 sync 階段后通知 DUMP 線程發(fā)送 Event。注意 sync_binlog 非 1 的設(shè)置可能導(dǎo)致從庫(kù)比主庫(kù)多事務(wù)。
sync_binlog 1:binary log 將在指定次 sync 隊(duì)列形成后進(jìn)行 sync 刷盤,約等于指定次 group commit 后刷盤。同時(shí)會(huì)在 flush 階段后通知 DUMP 線程發(fā)送 Event。
二、總體流程圖
這里我們先展示整個(gè)流程,如下(圖 15-1,高清原圖包含在文末原圖中):
三、步驟解析第一階段(圖中藍(lán)色部分)
注意:在第 1 步之前會(huì)有一個(gè)獲取 MDL_key::COMMIT 鎖的操作,因此 FTWRL 將會(huì)堵塞‘commit’操作,堵塞狀態(tài)為‘Waiting for commit lock’,這個(gè)可以參考 FTWRL 調(diào)用的函數(shù) make_global_read_lock_block_commit。
(1.) binlog 準(zhǔn)備。將上一次 COMMIT 隊(duì)列中最大的 seq number 寫(xiě)入到本次事務(wù)的 last_commit 中。可參考 binlog_prepare 函數(shù)。
(2.) Innodb 準(zhǔn)備。更改事務(wù)的狀態(tài)為準(zhǔn)備并且將事務(wù)的狀態(tài)和 XID 寫(xiě)入到 Undo 中。可參考 trx_prepare 函數(shù)。
(3.) XID_EVENT 生成并且寫(xiě)到 binlog cache 中。在第 10 節(jié)中我們說(shuō)過(guò)實(shí)際上 XID 來(lái)自于 query_id,早就生成了,這里只是生成 Event 而已。可參考 MYSQL_BIN_LOG::commit 函數(shù)。
四、步驟解析第二階段(圖中粉色部分)
(4.) 形成 FLUSH 隊(duì)列。這一步正在不斷的有事務(wù)加入到這個(gè) FLUSH 隊(duì)列。第一個(gè)進(jìn)入 FLUSH 隊(duì)列的為本階段的 leader,非 leader 線程將會(huì)堵塞,直到 COMMIT 階段后由 leader 線程的喚醒。
(5.) 獲取 LOCK log 鎖。
(6.) 這一步就是將 FLUSH 階段的隊(duì)列取出來(lái)準(zhǔn)備進(jìn)行處理。也就是這個(gè)時(shí)候本 FLUSH 隊(duì)列就不能在更改了。可參考 stage_manager.fetch_queue_for 函數(shù)。
(7.) 這里事務(wù)會(huì)進(jìn)行 Innodb 層的 redo 持久化,并且會(huì)幫助其他事務(wù)進(jìn)行 redo 的持久化。可以參考 MYSQL_BIN_LOG::process_flush_stage_queue 函數(shù)。下面是注釋和一小段代碼:
/*
We flush prepared records of transactions to the log of storage
engine (for example, InnoDB redo log) in a group right before
flushing them to binary log.
*/
ha_flush_logs(NULL, true);// 做 innodb redo 持久化
(8.)生成 GTID 和 seq number,并且連同前面的 last commit 生成 GTID_EVENT,然后直接寫(xiě)入到 binary log 中。我們注意到這里直接寫(xiě)入到了 binary log 而沒(méi)有寫(xiě)入到 binlog cache,因此 GTID_EVENT 是事務(wù)的第一個(gè) Event。參考函數(shù) binlog_cache_data::flush 中下面一段:
trn_ctx- sequence_number= mysql_bin_log.m_dependency_tracker.step();
//int64 state +1
if (!error)
if ((error= mysql_bin_log.write_gtid(thd, this, writer)))
// 生成 GTID 寫(xiě)入 binary log 文件
thd- commit_error= THD::CE_FLUSH_ERROR;
if (!error)
error= mysql_bin_log.write_cache(thd, this, writer);
// 將其他 Event 寫(xiě)入到 binary log 文件
而對(duì)于 seq number 和 last commit 的取值來(lái)講,實(shí)際上在 MySQL 內(nèi)部維護(hù)著一個(gè)全局的結(jié)構(gòu) Transaction_dependency_tracker。其中包含三種可能取值方式,如下:
Commit_order_trx_dependency_tracker
Writeset_trx_dependency_tracker
Writeset_session_trx_dependency_tracker
到底使用哪一種取值方式,由參數(shù) binlog_transaction_dependency_tracking 來(lái)決定的。
這里我們先研究參數(shù)設(shè)置為 COMMIT_ORDER 的取值方式,對(duì)于 WRITESET 取值的方式下一節(jié)專門討論。
對(duì)于設(shè)置為 COMMIT_ORDER 會(huì)使用 Commit_order_trx_dependency_tracker 的取值方式,有如下特點(diǎn):
特點(diǎn)
每次事務(wù)提交 seq number 將會(huì)加 1。
last commit 在前面的 binlog 準(zhǔn)備階段就賦值給了每個(gè)事務(wù)。這個(gè)前面已經(jīng)描述了。
last commit 是前一個(gè) COMMIT 隊(duì)列的最大 seq number。這個(gè)我們后面能看到。
其次 seq number 和 last commit 這兩個(gè)值類型都為 Logical_clock,其中維護(hù)了一個(gè)叫做 offsets 偏移量的值,用來(lái)記錄每次 binary log 切換時(shí) sequence_number 的相對(duì)偏移量。因此 seq number 和 last commit 在每個(gè) binary log 總是重新計(jì)數(shù),下面是 offset 的源碼注釋:
/*
Offset is subtracted from the actual absolute time value at
logging a replication event. That is the event holds logical
timestamps in the relative format. They are meaningful only in
the context of the current binlog.
The member is updated (incremented) per binary log rotation.
*/
int64 offset;
下面是我們計(jì)算 seq number 的方式,可以參考 Commit_order_trx_dependency_tracker::get_dependency 函數(shù)。
sequence_number=
trn_ctx- sequence_number - m_max_committed_transaction.get_offset();
// 這里獲取 seq number
我們清楚的看到這里有一個(gè)減去 offset 的操作,這也是為什么我們的 seq number 和 last commit 在每個(gè) binary log 總是重新計(jì)數(shù)的原因。
(9.)這一步就會(huì)將我們的 binlog cache 里面的所有 Event 寫(xiě)入到我們的 binary log 中了。對(duì)于一個(gè)事務(wù)來(lái)講,我們這里應(yīng)該很清楚這里包含的 Event 有:
QUERY_EVENT
MAP_EVENT
DML EVENT
XID_EVENT
注意 GTID_EVENT 前面已經(jīng)寫(xiě)入到的 binary logfile。這里我說(shuō)的寫(xiě)入是調(diào)用的 Linux 的 write 函數(shù),正常情況下它會(huì)進(jìn)入圖中的 OS CACHE 中。實(shí)際上這個(gè)時(shí)候可能還沒(méi)有真正寫(xiě)入到磁盤介質(zhì)中。
重復(fù) 7 ~ 9 步 把 FLUSH 隊(duì)列中所有的事務(wù)做同樣的處理。
注意:如果 sync_binlog != 1 這里將會(huì)喚醒 DUMP 線程進(jìn)行 Event 的發(fā)送。
(10.)這一步還會(huì)判斷 binary log 是否需要切換,并且設(shè)置一個(gè)切換標(biāo)記。依據(jù)就是整個(gè)隊(duì)列每個(gè)事務(wù)寫(xiě)入的 Event 總量加上現(xiàn)有的 binary log 大小是否超過(guò)了 max_binlog_size。可參考 MYSQL_BIN_LOG::process_flush_stage_queue 函數(shù),如下部分:
if (total_bytes 0 my_b_tell( log_file) = (my_off_t) max_size)
*rotate_var= true; // 標(biāo)記需要切換
但是注意這里是先將所有的 Event 寫(xiě)入 binary log,然后才進(jìn)行的判斷。因此對(duì)于大事務(wù)來(lái)講其 Event 肯定都包含在同一個(gè) binary log 中。
到這里 FLUSH 階段就結(jié)束了。
五、步驟解析第三階段(圖中紫色部分)
(11.)FLUSH 隊(duì)列加入到 SYNC 隊(duì)列。第一個(gè)進(jìn)入的 FLUSH 隊(duì)列的 leader 為本階段的 leader。其他 FLUSH 隊(duì)列加入 SYNC 隊(duì)列,且其他 FLUSH 隊(duì)列的 leader 會(huì)被 LOCK sync 堵塞,直到 COMMIT 階段后由 leader 線程的喚醒。
(12.)釋放 LOCK log。
(13.)獲取 LOCK sync。
(14.)這里根據(jù)參數(shù) delay 的設(shè)置來(lái)決定是否等待一段時(shí)間。我們從圖中我們可以看出如果 delay 的時(shí)間越久那么加入 SYNC 隊(duì)列的時(shí)間就會(huì)越長(zhǎng),也就可能有更多的 FLUSH 隊(duì)列加入進(jìn)來(lái),那么這個(gè) SYNC 隊(duì)列的事務(wù)就越多。這不僅會(huì)提高 sync 效率,并且增大了 GROUP COMMIT 組成員的數(shù)量(因?yàn)?last commit 還沒(méi)有更改,時(shí)間拖得越長(zhǎng)那么一組事務(wù)中事務(wù)數(shù)量就越多),從而提高了從庫(kù) MTS 的并行效率。但是缺點(diǎn)也很明顯可能導(dǎo)致簡(jiǎn)單的 DML 語(yǔ)句時(shí)間拖長(zhǎng),因此不能設(shè)置過(guò)大,下面是我簡(jiǎn)書(shū)中的一個(gè)案列就是因?yàn)?delay 參數(shù)設(shè)置不當(dāng)引起的,如下:
https://www.jianshu.com/p/bfd4a88307f2
參數(shù) delay 一共包含兩個(gè)參數(shù)如下:
binlog_group_commit_sync_delay:通過(guò)人為的設(shè)置 delay 時(shí)長(zhǎng)來(lái)加大整個(gè) GROUP COMMIT 組中事務(wù)數(shù)量,并且減少進(jìn)行磁盤刷盤 sync 的次數(shù),但是受到 binlog_group_commit_sync_no_delay_count 的限制。單位為 1 /1000000 秒,最大值 1000000 也就是 1 秒。
binlog_group_commit_sync_no_delay_count:在 delay 的時(shí)間內(nèi)如果 GROUP COMMIT 中的事務(wù)數(shù)量達(dá)到了這個(gè)設(shè)置就直接跳出等待,而不需要等待 binlog_group_commit_sync_delay 的時(shí)長(zhǎng)。單位是事務(wù)的數(shù)量。
(15.)這一步就是將 SYNC 階段的隊(duì)列取出來(lái)準(zhǔn)備進(jìn)行處理。也就是這個(gè)時(shí)候 SYNC 隊(duì)列就不能再更改了。這個(gè)隊(duì)列和 FLUSH 隊(duì)列并不一樣,事務(wù)的順序一樣但是數(shù)量可能不一樣。
(16.)根據(jù) sync_binlog 的設(shè)置決定是否刷盤。可以參考函數(shù) MYSQL_BIN_LOG::sync_binlog_file,邏輯也很簡(jiǎn)單。
到這里 SYNC 階段就結(jié)束了。
注意:如果 sync_binlog = 1 這里將會(huì)喚醒 DUMP 線程進(jìn)行 Event 的發(fā)送。
六、步驟解析第四階段(圖中黃色部分)
(17.)SYNC 隊(duì)列加入到 COMMIT 隊(duì)列。第一個(gè)進(jìn)入的 SYNC 隊(duì)列的 leader 為本階段的 leader。其他 SYNC 隊(duì)列加入 COMMIT 隊(duì)列,且其他 SYNC 隊(duì)列的 leader 會(huì)被 LOCK commit 堵塞,直到 COMMIT 階段后由 leader 線程的喚醒。
(18.)釋放 LOCK sync。
(19.)獲取 LOCK commit。
(20.)根據(jù)參數(shù) binlog_order_commits 的設(shè)置來(lái)決定是否按照隊(duì)列的順序進(jìn)行 Innodb 層的提交,如果 binlog_order_commits=1 則按照隊(duì)列順序提交則事務(wù)的可見(jiàn)順序和提交順序一致。如果 binlog_order_commits=0 則下面 21 步到 23 步將不會(huì)進(jìn)行,也就是這里不會(huì)進(jìn)行 Innodb 層的提交。
(21.)這一步就是將 COMMIT 階段的隊(duì)列取出來(lái)準(zhǔn)備進(jìn)行處理。也就是這個(gè)時(shí)候 COMMIT 隊(duì)列就不能在更改了。這個(gè)隊(duì)列和 FLUSH 隊(duì)列和 SYNC 隊(duì)列并不一樣,事務(wù)的順序一樣,數(shù)量可能不一樣。
注意:如果 rpl_semi_sync_master_wait_point 參數(shù)設(shè)置為‘AFTER_SYNC’,這里將會(huì)進(jìn)行 ACK 確認(rèn),可以看到實(shí)際的 Innodb 層提交操作還沒(méi)有進(jìn)行,等待期間狀態(tài)為‘Waiting for semi-sync ACK from slave’。
(22.)在 Innodb 層提交之前必須要更改 last_commit 了。COMMIT 隊(duì)列中每個(gè)事務(wù)都會(huì)去更新它,如果大于則更改,小于則不變。可參考 Commit_order_trx_dependency_tracker::update_max_committed 函數(shù),下面是這一小段代碼:
{ m_max_committed_transaction.set_if_greater(sequence_number);
// 如果更大則更改
}
(23.)COMMIT 隊(duì)列中每個(gè)事務(wù)按照順序進(jìn)行 Innodb 層的提交。可參考 innobase_commit 函數(shù)。
這一步 Innodb 層會(huì)做很多動(dòng)作,比如:
Readview 的更新
Undo 的狀態(tài)的更新
Innodb 鎖資源的釋放
完成這一步,實(shí)際上在 Innodb 層事務(wù)就可以見(jiàn)了。我曾經(jīng)遇到過(guò)一個(gè)由于 leader 線程喚醒本組其他線程出現(xiàn)問(wèn)題而導(dǎo)致整個(gè) commit 操作 hang 住,但是在數(shù)據(jù)庫(kù)中這些事務(wù)的修改已經(jīng)可見(jiàn)的案例。
循環(huán) 22~23 直到 COMMIT 隊(duì)列處理完。
注意:如果 rpl_semi_sync_master_wait_point 參數(shù)設(shè)置為‘AFTER_COMMIT’,這里將會(huì)進(jìn)行 ACK 確認(rèn),可以看到實(shí)際的 Innodb 層提交操作已經(jīng)完成了,等待期間狀態(tài)為‘Waiting for semi-sync ACK from slave’。
(24.)釋放 LOCK commit。
到這里 COMMIT 階段就結(jié)束了。
七、步驟解析第五階段(圖中綠色部分)
(25.)這里 leader 線程會(huì)喚醒所有的組內(nèi)成員,各自進(jìn)行各自的操作了。
(26.)每個(gè)事務(wù)成員進(jìn)行 binlog cache 的重置,清空 cache 釋放臨時(shí)文件。
(27.)如果 binlog_order_commits 設(shè)置為 0,COMMIT 隊(duì)列中的每個(gè)事務(wù)就各自進(jìn)行 Innodb 層提交(不按照 binary log 中事務(wù)的的順序)。
(28.)根據(jù)前面第 10 步設(shè)置的切換標(biāo)記,決定是否進(jìn)行 binary log 切換。
(29.)如果切換了 binary log,則還需要根據(jù) expire_logs_days 的設(shè)置判斷是否進(jìn)行 binlog log 的清理。
八、總結(jié)
整個(gè)過(guò)程我們看到生成 last commit 和 seq number 的過(guò)程并沒(méi)有其它的開(kāi)銷,但是下一節(jié)介紹的基于 WRITESET 的并行復(fù)制就有一定的開(kāi)銷了。
我們需要明白的是 FLUSH/SYNC/COMMIT 每一個(gè)階段都有一個(gè)相應(yīng)的隊(duì)列,每個(gè)隊(duì)列并不一樣。但是其中的事務(wù)順序卻是一樣的,是否能夠在從庫(kù)進(jìn)行并行回放完全取決于準(zhǔn)備階段獲取的 last_commit,這個(gè)我們將在第 19 節(jié)詳細(xì)描述。
對(duì)于 FLUSH/SYNC/COMMIT 三個(gè)隊(duì)列事務(wù)的數(shù)量實(shí)際有這樣關(guān)系,即 COMMIT 隊(duì)列 =SYNC 隊(duì)列 =FLUSH 隊(duì)列。如果壓力不大它們?nèi)呖赡芟嗤叶贾话粋€(gè)事務(wù)。
從流程中可以看出基于 COMMIT_ORDER 的并行復(fù)制如果數(shù)據(jù)庫(kù)壓力不大的情況下可能出現(xiàn)每個(gè)隊(duì)列都只有一個(gè)事務(wù)的情況。這種情況就不能在從庫(kù)并行回放了,但是下一節(jié)我們講的基于 WRITESET 的并行復(fù)制卻可以改變這種情況。
這里我們也更加明顯的看到大事務(wù)的 Event 會(huì)在提交時(shí)刻一次性的寫(xiě)入到 binary log。如果 COMMIT 隊(duì)列中包含了大事務(wù),那么必然堵塞本隊(duì)列中的其它事務(wù)提交,后續(xù)的提交操作也不能完成。我認(rèn)為這也是 MySQL 不適合大事務(wù)的一個(gè)重要原因。
到此,相信大家對(duì)“MySQL 層事務(wù)提交的流程”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是丸趣 TV 網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!