[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;