1 兩階段提交
以update語句的具體執行過程為例:
具體更新一條記錄 UPDATE t_user SET name = ‘xiaolin’ WHERE id = 1;的流程如下:
1.執行器負責具體執行,會調用存儲引擎的接口,通過主鍵索引樹搜索獲取 id = 1 這一行記錄:
(1)如果 id=1 這一行所在的數據頁本來就在 buffer pool 中,就直接返回給執行器更新;
(2)如果記錄不在 buffer pool,將數據頁從磁盤讀入到 buffer pool,返回記錄給執行器。
2.執行器得到聚簇索引記錄后,會看一下更新前的記錄和更新后的記錄是否一樣:
(1)如果一樣的話就不進行后續更新流程;
(2)如果不一樣的話就把更新前的記錄和更新后的記錄都當作參數傳給 InnoDB 層,讓 InnoDB 真正的執行更新記錄的操作;
3.開啟事務, InnoDB 層更新記錄前,首先要記錄相應的 undo log,因為這是更新操作,需要把被更新的列的舊值記下來,也就是要生成一條 undo log,并被寫入undo log buffer
里
4.InnoDB 層開始更新記錄,會先更新buffer pool里相關的頁(并標記為臟頁)。刷新到磁盤,有相應的機制,刷新策略控制參數innodb_max_dirty_pages_pct,innodb_max_dirty_pages_pct_lwm
5.生成 redo log,寫入redo log buffer
里,此時到了redo log兩階段提交的第一階段 preparing階段
。刷新到磁盤,有相應的機制,由參數innodb_autoinc_lock_mode控制。
6.引擎層 在一條更新語句執行完成后,server層開始記錄該語句對應的 binlog,此時記錄的 binlog 會被保存到binlog cache。刷新到磁盤,有相應的機制,刷新策略控制參數sync_binlog;只要 bin log 寫磁盤成功,就算 redo log 的狀態還是 prepare 也沒有關系,一樣會被認為事務已經執行成功;binlog是二進制日志,有三種類型,由參數binlog_format【Mysql–基礎知識點–88–各種mysql日志中的2】控制
7.此時到了redo log兩階段提交的第二階段commit 階段
8.至此,一條更新語句執行完成。
2 為什么要兩階段提交呢?直接提交不行嗎?
我們可以假設不采用兩階段提交的方式,而是采用"單階段"進行提交,即要么先寫入redo log,后寫入binlog;要么先寫入binlog,后寫入redo log。這兩種方式的提交都會導致原先數據庫的狀態和被恢復后的數據庫的狀態不一致。
先寫入 redo log,后寫入 binlog:
在寫完redo log之后,數據此時具有crash-safe能力,因此系統崩潰,數據會恢復成事務開始之前的狀態。但是,若在redo log寫完時候,binlog寫入之前,系統發生了宕機。此時binlog沒有對上面的更新語句進行保存,導致當使用binlog進行數據庫的備份或者恢復時,就少了上述的更新語句。從而使得id=2這一行的數據沒有被更新。
先寫入 binlog,后寫入 redo log:
寫完 binlog 之后,所有的語句都被保存,所以通過 binlog 復制或恢復出來的數據庫中 id=2 這一行的數據會被更新為a=1。但是如果在redo log寫入之前,系統崩潰,那么redo log中記錄的這個事務會無效,導致實際數據庫中 id=2 這一行的數據并沒有更新。
簡單說,redo log和binlog都可以用于表示事務的提交狀態,而兩階段提交就是讓這兩個狀態保持邏輯上的一致。
3 崩潰恢復時的處理
若MySQL在提交過程中崩潰,重啟后會檢查redo log和binlog的一致性:
Case 1:redo log為prepare且binlog完整
說明binlog已寫入,但redo log未提交。
恢復操作:提交事務(將數據修改生效)。
Case 2:redo log為prepare但binlog不完整
說明第二階段未完成。
恢復操作:回滾事務(利用undo log撤銷修改)。
通過這種機制,確保redo log和binlog的數據嚴格一致。