1. 程式人生 > >[MySQL原始碼]:2PC下的事務提交概述

[MySQL原始碼]:2PC下的事務提交概述

XA-2PC (two phase commit, 兩階段提交 ) 第一階段:為prepare階段,TM向RM發出prepare指令,RM進行操作,然後返回成功與否的資訊給TM; 第二階段:為事務提交或者回滾階段,如果TM收到所有RM的成功訊息,則TM向RM發出提交指令;不然則發出回滾指令;

MySQL通過兩階段提交很好地解決了binlog和redo log的一致性問題 第一階段:InnoDB prepare,持有prepare_commit_mutex,並且write/sync redo log; 將回滾段設定為Prepared狀態,binlog不作任何操作; 第二階段:包含兩步     1、write/sync Binlog;     2、InnoDB commit (寫入COMMIT標記後釋放prepare_commit_mutex);      以 binlog 的寫入與否作為事務提交成功與否的標誌,innodb commit標誌並不是事務成功與否的標誌。因為此時的事務崩潰恢復過程如下:     1、崩潰恢復時,掃描最後一個Binlog檔案,提取其中的xid;      2、InnoDB維持了狀態為Prepare的事務連結串列,將這些事務的xid和Binlog中記錄的xid做比較,如果在Binlog中存在,則提交,否則回滾事務。 通過這種方式,可以讓InnoDB和Binlog中的事務狀態保持一致。如果在寫入innodb commit標誌時崩潰,則恢復時,會重新對commit標誌進行寫入; 在prepare階段崩潰,則會回滾,在write/sync binlog階段崩潰,也會回滾。這種事務提交的實現是MySQL5.6之前的實現。

MySQL5.6中的binlog 組提交 將Binlog Group Commit的過程拆分成了三個階段     flush stage 將各個執行緒的binlog從cache寫到檔案中;      sync stage 對binlog做fsync操作(如果需要的話;最重要的就是這一步,對多個執行緒的binlog合併寫入磁碟);     commit stage 為各個執行緒做引擎層的事務commit(這裡不用寫redo log,在prepare階段已寫)。 每個stage同時只有一個執行緒在操作。(分成三個階段,每個階段的任務分配給一個專門的執行緒,這是典型的併發優化) 這種實現的優勢在於三個階段可以併發執行,從而提升效率。注意prepare階段沒有變,還是write/sync redo log.

MySQL5.7中的binlog組提交 從XA恢復的邏輯我們可以知道,只要保證InnoDB Prepare的redo日誌在寫Binlog前完成write/sync即可     InnoDB Prepare,記錄當前的LSN到thd中;     進入Group Commit的flush stage;Leader蒐集佇列,同時算出佇列中最大的LSN。      將InnoDB的redo log write/fsync到指定的LSN     寫Binlog並進行隨後的工作(sync Binlog, InnoDB commit , etc) 也就是將 redo log的write/sync延遲到了binlog group commit的 flush stage之後,sync binlog之前。 通過延遲寫redo log的方式,顯式的為redo log做了一次組寫入(redo log group write),並減少了(redo log) log_sys->mutex的競爭。

COMMIT命令為例MySQL提交程式碼框架如下

sql_parse.cc   mysql_execute_command
sql_parse.cc       SQLCOM_COMMIT
transaction.cc         trans_commit()
handler.cc                 ha_commit_trans()
handler.cc                     tc_log->prepare()
binlog.cc                          MYSQL_BIN_LOG::prepare()    //prepare 入口
handler.cc                     tc_log->commit()
binlog.cc                          MYSQL_BIN_LOG::commit()     //commit入口

tc_log會在sql/mysqld.cc中的init_server_components()中進行初始化,程式碼如下                                

if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log))
  {
    if (opt_bin_log)   //當binlog開啟的時候tc_log初始化為&mysql_bin_log
      tc_log= &mysql_bin_log;  //(binlog.h>>extern MYSQL_PLUGIN_IMPORT MYSQL_BIN_LOG mysql_bin_log;)
    else
      tc_log= &tc_log_mmap;
  }
  else
    tc_log= &tc_log_dummy;