溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

MySQL層事務(wù)提交的流程

發(fā)布時(shí)間:2021-09-10 13:01:06 來(lái)源:億速云 閱讀:141 作者:chen 欄目:MySQL數(shù)據(jù)庫(kù)

本篇內(nèi)容主要講解“MySQL層事務(wù)提交的流程”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(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ù)制中l(wèi)ast commit和seq number的生成方式。

而sync_binlog參數(shù)則有兩個(gè)功能:

  • sync_binlog=0:binary log不sync刷盤(pán),依賴(lài)于OS刷盤(pán)機(jī)制。同時(shí)會(huì)在flush階段后通知DUMP線(xiàn)程發(fā)送Event。

  • sync_binlog=1:binary log每次sync隊(duì)列形成后都進(jìn)行sync刷盤(pán),約等于每次group commit進(jìn)行刷盤(pán)。同時(shí)會(huì)在sync階段后通知DUMP線(xiàn)程發(fā)送Event。注意sync_binlog非1的設(shè)置可能導(dǎo)致從庫(kù)比主庫(kù)多事務(wù)。

  • sync_binlog>1:binary log將在指定次sync隊(duì)列形成后進(jìn)行sync刷盤(pán),約等于指定次group commit后刷盤(pán)。同時(shí)會(huì)在flush階段后通知DUMP線(xiàn)程發(fā)送Event。

二、總體流程圖

這里我們先展示整個(gè)流程,如下(圖15-1,高清原圖包含在文末原圖中):

MySQL層事務(wù)提交的流程


三、步驟解析第一階段(圖中藍(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中??蓞⒖糱inlog_prepare函數(shù)。

(2.)  Innodb準(zhǔn)備。更改事務(wù)的狀態(tài)為準(zhǔn)備并且將事務(wù)的狀態(tài)和XID寫(xiě)入到Undo中??蓞⒖紅rx_prepare函數(shù)。

(3.)  XID_EVENT生成并且寫(xiě)到binlog cache中。在第10節(jié)中我們說(shuō)過(guò)實(shí)際上XID來(lái)自于query_id,早就生成了,這里只是生成Event而已??蓞⒖糓YSQL_BIN_LOG::commit函數(shù)。


四、步驟解析第二階段(圖中粉色部分)

(4.)  形成FLUSH隊(duì)列。這一步正在不斷的有事務(wù)加入到這個(gè)FLUSH隊(duì)列。第一個(gè)進(jìn)入FLUSH隊(duì)列的為本階段的leader,非leader線(xiàn)程將會(huì)堵塞,直到COMMIT階段后由leader線(xiàn)程的喚醒。

(5.)  獲取LOCK log 鎖。

(6.)  這一步就是將FLUSH階段的隊(duì)列取出來(lái)準(zhǔn)備進(jìn)行處理。也就是這個(gè)時(shí)候本FLUSH隊(duì)列就不能在更改了??蓞⒖約tage_manager.fetch_queue_for函數(shù)。

(7.)  這里事務(wù)會(huì)進(jìn)行Innodb層的redo持久化,并且會(huì)幫助其他事務(wù)進(jìn)行redo的持久化??梢詤⒖糓YSQL_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é)專(zhuān)門(mén)討論。

對(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è)值類(lèi)型都為L(zhǎng)ogical_clock,其中維護(hù)了一個(gè)叫做offsets偏移量的值,用來(lái)記錄每次binary log切換時(shí)sequence_number的相對(duì)偏移量。因此seq number和last commit在每個(gè)binary log總是重新計(jì)數(shù),下面是offset的源碼注釋?zhuān)?/p>

  /*
    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ě)入到磁盤(pán)介質(zhì)中。

重復(fù) 7 ~ 9步 把FLUSH隊(duì)列中所有的事務(wù)做同樣的處理。

注意:如果sync_binlog != 1 這里將會(huì)喚醒DUMP線(xiàn)程進(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??蓞⒖糓YSQL_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線(xiàn)程的喚醒。

(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)閘ast 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)閐elay參數(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)行磁盤(pán)刷盤(pá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è)置決定是否刷盤(pán)??梢詤⒖己瘮?shù)MYSQL_BIN_LOG::sync_binlog_file,邏輯也很簡(jiǎn)單。

到這里SYNC階段就結(jié)束了。

注意:如果sync_binlog = 1 這里將會(huì)喚醒DUMP線(xiàn)程進(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線(xiàn)程的喚醒。

(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ì)去更新它,如果大于則更改,小于則不變??蓞⒖糃ommit_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層的提交??蓞⒖糹nnobase_commit函數(shù)。

這一步Innodb層會(huì)做很多動(dòng)作,比如:

  • Readview的更新

  • Undo的狀態(tài)的更新

  • Innodb 鎖資源的釋放

完成這一步,實(shí)際上在Innodb層事務(wù)就可以見(jiàn)了。我曾經(jīng)遇到過(guò)一個(gè)由于leader線(xiàn)程喚醒本組其他線(xiàn)程出現(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線(xiàn)程會(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)銷(xiāo),但是下一節(jié)介紹的基于WRITESET的并行復(fù)制就有一定的開(kāi)銷(xiāo)了。

  • 我們需要明白的是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í)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI