一條查詢語句的執(zhí)行過程,一般是經(jīng)過 連接器、分析器、優(yōu)化器、執(zhí)行器,最后到達存儲引擎。
那么對于一條 更新的 MySQL 語句,執(zhí)行流程又是怎樣的呢? 首先可以肯定的是,以上查詢語句經(jīng)過的流程,更新語句也會走一遍。
但是與查詢流程不一樣的是,更新流程還涉及到兩個重要的日志模塊: redo log(重做日志) 和 binlog(歸檔日志)。
重做日志 redo log
InnoDB 使用到的記錄修改日志的技術是 WAL (Write-Ahead logging),其關鍵點就是先寫日志,再寫磁盤。
當有一條記錄需要更新時, InnDB 引擎會先把記錄寫入 redo log 中,并更新內(nèi)存。然后,在適當?shù)臅r候,將此操作記錄更新到磁盤中。

如上所示,為 InnoDB 的 redo log 示意圖。 redo log 的大小是固定的,會有兩個指針記錄關鍵的位置: write pos 是當前記錄位置,checkpoint 是當前要擦除的位置,也就是把記錄更新到磁盤。這兩個指針都是不斷向后推移并循環(huán)的。而 write pos 和 checkpoint 之間的位置就是可以用于記錄新的操作的空間。
有了 redo log, InnoDB 就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄不會丟失,并且可以根據(jù) redo log 恢復,此能力稱為 crash-safe。
歸檔日志 bin log
上面提到的 redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己特有的日志,稱為 binlog。
binlog 和 redo log 有以下幾點不同:
1、binlog 是 Server 層實現(xiàn)的, redo log 是 InnoDB 特有的。
2、redo log 是物理日志,記錄的是“在某個數(shù)據(jù)頁上做了什么修改”,而 binlog 是邏輯日志,一般記錄的是原修改 SQL 語句。
3、redo log 是循環(huán)寫的,空間有固定大小,并且可能會用完。binlog 是追加寫入的,寫到一定大小后可以切換下一個,并不會覆蓋之前的日志。

以上是 update 語句的流程圖,淺色框代表在 InnoDB 內(nèi)部執(zhí)行, 深色框是在執(zhí)行器中執(zhí)行。
可以注意到,最后三個步驟,將 redo log 的寫入拆成了兩步驟: prepare 和 commit ,這就是 “兩階段提交”。
兩階段提交
“兩階段提交”的機制,是為了讓兩份日志之間的邏輯一致。
可以考慮這么一個場景:當發(fā)生了誤刪表的操作,需要如何找回數(shù)據(jù):
1、找到最近一次的全量備份
2、從這個備份時間點開始,將備份的 binlog 依次取出,重新放到誤刪表之前的那個時刻。
3、然后此備份就和誤刪之前的線上庫一樣了。
那么,到底為什么需要“兩階段提交”呢? 這里用 反證法舉例說明,如果不用兩階段提交,會發(fā)生什么?
假設有這么個字段 c=0,現(xiàn)在要將其修改為 c=1
1、 先寫 redo log 再寫 binlog。如果寫完 redo log ,還沒來得及寫 binlog 時,MySQL 異常重啟。此時根據(jù) redo log 恢復的 c=1,但是 binlog 還未寫入,根據(jù) binlog 恢復的臨時庫,c=0,與原庫不一致。
2、先寫 binlog 再寫 redo log。如果在寫 redo log 之前,MySQL異常重啟,那么此事務無效,c的值應該是0。但是根據(jù) binlog 恢復的臨時庫,會多一次事務出來,導致與原庫值不相同。