MySQL層事務提交的流程

本篇內(nèi)容主要講解“MySQL層事務提交的流程”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“MySQL層事務提交的流程”吧!

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供鐵鋒網(wǎng)站建設、鐵鋒做網(wǎng)站、鐵鋒網(wǎng)站設計、鐵鋒網(wǎng)站制作等企業(yè)網(wǎng)站建設、網(wǎng)頁設計與制作、鐵鋒企業(yè)網(wǎng)站模板建站服務,十年鐵鋒做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡服務。

本節(jié)將來解釋一下MySQL層詳細的提交流程,但是由于能力有限,這里不可能包含全部的步驟,只是包含了一些重要的并且我學習過的步驟。我們首先需要來假設參數(shù)設置,因為某些參數(shù)的設置會直接影響到提交流程,我們也會逐一解釋這些參數(shù)的含義。本節(jié)介紹的大部分內(nèi)容都集中在函數(shù)MYSQL_BIN_LOG::prepare和MYSQL_BIN_LOG::ordered_commit之中。

一、參數(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需要重點說明一下。我們知道Innodb的行鎖是在語句運行期間就已經(jīng)獲取,因此如果多個事務同時進入了提交流程(prepare階段),在Innodb層提交釋放Innodb行鎖資源之前各個事務之間肯定是沒有行沖突的,因此可以在從庫端并行執(zhí)行。在基于COMMIT_ORDER 的并行復制中,last commit和seq number正是基于這種思想生成的,如果last commit相同則視為可以在從庫并行回放,在19節(jié)我們將解釋從庫判定并行回放的規(guī)則。而在基于WRITESET的并行復制中,last commit將會在WRITESET的影響下繼續(xù)降低,來使從庫獲得更好的并行回放效果,但是它也是COMMIT_ORDER為基礎(chǔ)的,這個下一節(jié)將討論。我們這節(jié)只討論基于COMMIT_ORDER 的并行復制中l(wèi)ast commit和seq number的生成方式。

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

  • sync_binlog=0:binary log不sync刷盤,依賴于OS刷盤機制。同時會在flush階段后通知DUMP線程發(fā)送Event。

  • sync_binlog=1:binary log每次sync隊列形成后都進行sync刷盤,約等于每次group commit進行刷盤。同時會在sync階段后通知DUMP線程發(fā)送Event。注意sync_binlog非1的設置可能導致從庫比主庫多事務。

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

二、總體流程圖

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

MySQL層事務提交的流程


三、步驟解析第一階段(圖中藍色部分)

注意:在第1步之前會有一個獲取MDL_key::COMMIT鎖的操作,因此FTWRL將會堵塞‘commit’操作,堵塞狀態(tài)為‘Waiting for commit lock’,這個可以參考FTWRL調(diào)用的函數(shù)make_global_read_lock_block_commit。

(1.)  binlog準備。將上一次COMMIT隊列中最大的seq number寫入到本次事務的last_commit中??蓞⒖糱inlog_prepare函數(shù)。

(2.)  Innodb準備。更改事務的狀態(tài)為準備并且將事務的狀態(tài)和XID寫入到Undo中。可參考trx_prepare函數(shù)。

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


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

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

(5.)  獲取LOCK log 鎖。

(6.)  這一步就是將FLUSH階段的隊列取出來準備進行處理。也就是這個時候本FLUSH隊列就不能在更改了。可參考stage_manager.fetch_queue_for函數(shù)。

(7.)  這里事務會進行Innodb層的redo持久化,并且會幫助其他事務進行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,然后直接寫入到binary log中。我們注意到這里直接寫入到了binary log而沒有寫入到binlog cache,因此GTID_EVENT是事務的第一個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 寫入binary log文件
        thd->commit_error= THD::CE_FLUSH_ERROR;
    if (!error)
      error= mysql_bin_log.write_cache(thd, this, &writer);
//將其他Event寫入到binary log文件

而對于seq number和last commit的取值來講,實際上在MySQL內(nèi)部維護著一個全局的結(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來決定的。
這里我們先研究參數(shù)設置為COMMIT_ORDER 的取值方式,對于WRITESET取值的方式下一節(jié)專門討論。

對于設置為COMMIT_ORDER會使用Commit_order_trx_dependency_tracker的取值方式,有如下特點:

特點
每次事務提交seq number將會加1。
last commit在前面的binlog準備階段就賦值給了每個事務。這個前面已經(jīng)描述了。
last commit是前一個COMMIT隊列的最大seq number。這個我們后面能看到。

其次seq number和last commit這兩個值類型都為Logical_clock,其中維護了一個叫做offsets偏移量的值,用來記錄每次binary log切換時sequence_number的相對偏移量。因此seq number和last commit在每個binary log總是重新計數(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;

下面是我們計算seq number的方式,可以參考Commit_order_trx_dependency_tracker::get_dependency函數(shù)。

  sequence_number=
    trn_ctx->sequence_number - m_max_committed_transaction.get_offset(); 
//這里獲取seq number

我們清楚的看到這里有一個減去offset的操作,這也是為什么我們的seq number和last commit在每個binary log總是重新計數(shù)的原因。

(9.) 這一步就會將我們的binlog cache里面的所有Event寫入到我們的binary log中了。對于一個事務來講,我們這里應該很清楚這里包含的Event有:

  • QUERY_EVENT

  • MAP_EVENT

  • DML EVENT

  • XID_EVENT

注意GTID_EVENT前面已經(jīng)寫入到的binary logfile。這里我說的寫入是調(diào)用的Linux的write函數(shù),正常情況下它會進入圖中的OS CACHE中。實際上這個時候可能還沒有真正寫入到磁盤介質(zhì)中。

重復 7 ~ 9步 把FLUSH隊列中所有的事務做同樣的處理。

注意:如果sync_binlog != 1 這里將會喚醒DUMP線程進行Event的發(fā)送。

(10.) 這一步還會判斷binary log是否需要切換,并且設置一個切換標記。依據(jù)就是整個隊列每個事務寫入的Event總量加上現(xiàn)有的binary log大小是否超過了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; //標記需要切換

但是注意這里是先將所有的Event寫入binary log,然后才進行的判斷。因此對于大事務來講其Event肯定都包含在同一個binary log中。

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


五、步驟解析第三階段(圖中紫色部分)

(11.) FLUSH隊列加入到SYNC隊列。第一個進入的FLUSH隊列的leader為本階段的leader。其他FLUSH隊列加入SYNC隊列,且其他FLUSH隊列的leader會被LOCK sync堵塞,直到COMMIT階段后由leader線程的喚醒。

(12.) 釋放LOCK log。

(13.) 獲取LOCK sync。

(14.) 這里根據(jù)參數(shù)delay的設置來決定是否等待一段時間。我們從圖中我們可以看出如果delay的時間越久那么加入SYNC隊列的時間就會越長,也就可能有更多的FLUSH隊列加入進來,那么這個SYNC隊列的事務就越多。這不僅會提高sync效率,并且增大了GROUP COMMIT組成員的數(shù)量(因為last commit還沒有更改,時間拖得越長那么一組事務中事務數(shù)量就越多),從而提高了從庫MTS的并行效率。但是缺點也很明顯可能導致簡單的DML語句時間拖長,因此不能設置過大,下面是我簡書中的一個案列就是因為delay參數(shù)設置不當引起的,如下:
https://www.jianshu.com/p/bfd4a88307f2

參數(shù)delay一共包含兩個參數(shù)如下:

  • binlog_group_commit_sync_delay:通過人為的設置delay時長來加大整個GROUP COMMIT組中事務數(shù)量,并且減少進行磁盤刷盤sync的次數(shù),但是受到binlog_group_commit_sync_no_delay_count的限制。單位為1/1000000秒,最大值1000000也就是1秒。

  • binlog_group_commit_sync_no_delay_count:在delay的時間內(nèi)如果GROUP COMMIT中的事務數(shù)量達到了這個設置就直接跳出等待,而不需要等待binlog_group_commit_sync_delay的時長。單位是事務的數(shù)量。

(15.) 這一步就是將SYNC階段的隊列取出來準備進行處理。也就是這個時候SYNC隊列就不能再更改了。這個隊列和FLUSH隊列并不一樣,事務的順序一樣但是數(shù)量可能不一樣。

(16.) 根據(jù)sync_binlog的設置決定是否刷盤??梢詤⒖己瘮?shù)MYSQL_BIN_LOG::sync_binlog_file,邏輯也很簡單。

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

注意:如果sync_binlog = 1 這里將會喚醒DUMP線程進行Event的發(fā)送。


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

(17.) SYNC隊列加入到COMMIT隊列。第一個進入的SYNC隊列的leader為本階段的leader。其他SYNC隊列加入COMMIT隊列,且其他SYNC隊列的leader會被LOCK commit堵塞,直到COMMIT階段后由leader線程的喚醒。

(18.) 釋放LOCK sync。

(19.) 獲取LOCK commit。

(20.) 根據(jù)參數(shù)binlog_order_commits的設置來決定是否按照隊列的順序進行Innodb層的提交,如果binlog_order_commits=1 則按照隊列順序提交則事務的可見順序和提交順序一致。如果binlog_order_commits=0 則下面21步到23步將不會進行,也就是這里不會進行Innodb層的提交。

(21.) 這一步就是將COMMIT階段的隊列取出來準備進行處理。也就是這個時候COMMIT隊列就不能在更改了。這個隊列和FLUSH隊列和SYNC隊列并不一樣,事務的順序一樣,數(shù)量可能不一樣。

注意:如果rpl_semi_sync_master_wait_point參數(shù)設置為‘AFTER_SYNC’,這里將會進行ACK確認,可以看到實際的Innodb層提交操作還沒有進行,等待期間狀態(tài)為‘Waiting for semi-sync ACK from slave’。

(22.) 在Innodb層提交之前必須要更改last_commit了。COMMIT隊列中每個事務都會去更新它,如果大于則更改,小于則不變??蓞⒖糃ommit_order_trx_dependency_tracker::update_max_committed函數(shù),下面是這一小段代碼:

{
  m_max_committed_transaction.set_if_greater(sequence_number);
//如果更大則更改
}

(23.) COMMIT隊列中每個事務按照順序進行Innodb層的提交??蓞⒖糹nnobase_commit函數(shù)。

這一步Innodb層會做很多動作,比如:

  • Readview的更新

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

  • Innodb 鎖資源的釋放

完成這一步,實際上在Innodb層事務就可以見了。我曾經(jīng)遇到過一個由于leader線程喚醒本組其他線程出現(xiàn)問題而導致整個commit操作hang住,但是在數(shù)據(jù)庫中這些事務的修改已經(jīng)可見的案例。

循環(huán)22~23直到COMMIT隊列處理完。

注意:如果rpl_semi_sync_master_wait_point參數(shù)設置為‘AFTER_COMMIT’,這里將會進行ACK確認,可以看到實際的Innodb層提交操作已經(jīng)完成了,等待期間狀態(tài)為‘Waiting for semi-sync ACK from slave’。

(24.) 釋放LOCK commit。

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


七、步驟解析第五階段(圖中綠色部分)

(25.) 這里leader線程會喚醒所有的組內(nèi)成員,各自進行各自的操作了。

(26.) 每個事務成員進行binlog cache的重置,清空cache釋放臨時文件。

(27.) 如果binlog_order_commits設置為0,COMMIT隊列中的每個事務就各自進行Innodb層提交(不按照binary log中事務的的順序)。

(28.) 根據(jù)前面第10步設置的切換標記,決定是否進行binary log切換。

(29.) 如果切換了binary log,則還需要根據(jù)expire_logs_days的設置判斷是否進行binlog log的清理。


八、總結(jié)

  • 整個過程我們看到生成last commit和seq number的過程并沒有其它的開銷,但是下一節(jié)介紹的基于WRITESET的并行復制就有一定的開銷了。

  • 我們需要明白的是FLUSH/SYNC/COMMIT每一個階段都有一個相應的隊列,每個隊列并不一樣。但是其中的事務順序卻是一樣的,是否能夠在從庫進行并行回放完全取決于準備階段獲取的last_commit,這個我們將在第19節(jié)詳細描述。

  • 對于FLUSH/SYNC/COMMIT三個隊列事務的數(shù)量實際有這樣關(guān)系,即COMMIT隊列>=SYNC隊列>=FLUSH隊列。如果壓力不大它們?nèi)呖赡芟嗤叶贾话粋€事務。

  • 從流程中可以看出基于COMMIT_ORDER 的并行復制如果數(shù)據(jù)庫壓力不大的情況下可能出現(xiàn)每個隊列都只有一個事務的情況。這種情況就不能在從庫并行回放了,但是下一節(jié)我們講的基于WRITESET的并行復制卻可以改變這種情況。

  • 這里我們也更加明顯的看到大事務的Event會在提交時刻一次性的寫入到binary log。如果COMMIT隊列中包含了大事務,那么必然堵塞本隊列中的其它事務提交,后續(xù)的提交操作也不能完成。我認為這也是MySQL不適合大事務的一個重要原因。

到此,相信大家對“MySQL層事務提交的流程”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學習!

新聞標題:MySQL層事務提交的流程
分享地址:http://bm7419.com/article26/pphhjg.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供微信公眾號企業(yè)網(wǎng)站制作、響應式網(wǎng)站、網(wǎng)站導航、虛擬主機、用戶體驗

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作