最詳細的MySQL事務解析:ACID的原理及實現

提到MySQL的事務,我相信對MySQL有了解的同學都能聊上幾句,無論是面試求職,還是日常開發,MySQL的事務都跟我們息息相關。

而事務的ACID(即原子性Atomicity、一致性Consistency、隔離性Isolation、持久性Durability)可以說涵蓋了事務的全部知識點,所以,我們不僅要知道ACID是什麼,還要了解ACID背後的實現,只有這樣,無論在日常開發還是面試求職,都能無往而不利。

上一篇 主要圍繞“隔離性”展開,從基本概念,到隔離性的實現,最後以一個實戰案例進行融會貫通。本篇內容將介紹原子性、一致性、持久性相關實現,由於這部分內容可能很多人會相對陌生,因為日常業務開發可能不太會去接觸和深究,但是瞭解完後,你對MySQL會有更深刻的認識。

1.基本概念

  • 原子性。

整個事務是不可分割的最小單位,事務中任何一個語句執行失敗,所有已經執行成功的語句也要回滾,整個數據庫狀態要恢復到執行事務前到狀態。

  • 一致性。

事務將數據庫從一種狀態轉變為下一種一致的狀態。在事務的前後,數據庫的完整性約束沒有被破壞。(事務的acid不是完全正交的,尤其是一致性,可能跟原子性、隔離性都有一定關係,後面會看到)

  • 持久性。

事務一旦提交,那麼就是永久性的,不會因為宕機等故障導致數據丟失(外力影響不保證,比如磁盤損害)。持久性是保證了數據庫的高可靠性(High Reliability),而不是高可用性(Hign Availability)。高可用性並不能通過事務來保證。

2.持久性的實現

MySQL的innoDB存儲引擎,使用Redo log保證了事務的持久性。

當事務提交時,必須先將事務的所有日誌寫入日誌文件進行持久化,就是我們常說的WAL(write ahead log)機制(這個技術是保障持久性的關鍵技術,在HBase中也扮演重要角色,有興趣的同學可以參考我之前關於HBase的文章)。這樣才能保證斷電或宕機等情況發生後,已提交的事務不會丟失,這個能力稱為 crash-safe。

下面深入聊一聊redo log的機制,給大家更深刻的理解。

Redo log包括兩部分,重做日誌緩衝(redo log buffer)和重做日誌文件(redo log file),前者是易失的緩存,後者是持久化的文件。

舉一個事務的例子:

  • 步驟1:begin;
  • 步驟2:insert into t1 …r
  • 步驟3:insert into t2 …
  • 步驟4:commit;

這個事務的寫入過程實際拆解如下:

最詳細的MySQL事務解析:ACID的原理及實現

innodb緩衝池的概念本文就不展開說明了,以後有機會可以展開說一下。

重點關注在這個事務提交前,將 redo log 的寫入拆成了兩個步驟,prepare 和 commit,這就是"兩階段提交”。

為什麼要採用兩階段提交呢?

實際上,兩階段提交是分佈式系統常用的機制。MySQL使用了兩階段提交後,也是為了保證事務的持久性。Redo log 和bingo 有一個共同的數據字段,叫 XID,崩潰恢復的時候,會按順序掃描 redo log。

  • 假設在寫入binlog前系統崩潰,那麼數據庫恢復後順序掃描 redo log,碰到只有 parepare、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應的事務,而且binlog也沒寫入,所以事務就直接回滾了。
  • 假設在寫入binlog之後,事務提交前數據庫崩潰,那麼數據庫恢復後順序掃描 redo log,碰到既有 prepare、又有 commit 的 redo log,就直接提交,保證數據不丟失。

這個事務要往兩個表中插入記錄,插入數據的過程中,生成的日誌都得先寫入redo log buffer ,等到commit的時候,才真正把日誌寫到 redo log 文件。(當然,這裡不絕對,因為redo log buffer可能因為其他原因被迫刷新到redo log)。

而為了確保每次日誌都能寫入日誌文件,在每次將重做日誌緩衝 寫入 重做日誌文件 後,InnoDB存儲引擎都需要調用一次fsync操作,確保寫入了磁盤。

對於redo log的持久化,可以如下圖所示。

最詳細的MySQL事務解析:ACID的原理及實現

1)先寫入redo log buffer,在藍色區域。

2)寫入redo log file,但是還沒有fsync,這時候是處於黃色的位置,處於系統緩存。

3)調用fsync,真正寫入磁盤。

為了控制 redo log 的寫入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 參數,它有三種可能取值:

  • 設置為 0 的時候,表示每次事務提交時都只是把 redo log 留在 redo log buffer 中 ;
  • 設置為 1 的時候,表示每次事務提交時都將 redo log 直接持久化到磁盤;
  • 設置為 2 的時候,表示每次事務提交時都只是把 redo log 寫到 page cache。

binlog的寫入和redo log一樣,也是包括bingo cache和bingo file,同樣跟上面的三色層次類似(當然,binlog是server層的,不是存儲引擎層的),包括log buffer、文件系統page cache、hard disk。

寫入page cache 和 fsync到disk 的時機,是由參數 sync_binlog 控制的:

  • sync_binlog=0 的時候,表示每次提交事務都只 寫入文件系統的page cache,不 fsync;
  • sync_binlog=1 的時候,表示每次提交事務都會執行 fsync;
  • sync_binlog=N(N>1) 的時候,表示每次提交事務都寫入文件系統的page cache,但累積 N 個事務後才 fsync。(如果主機發生異常重啟,會丟失最近 N 個事務的 binlog 日誌)

通常我們說 MySQL 的“雙 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都設置成 1。也就是說,一個事務完整提交前,需要等待兩次刷盤,一次是 redo log(prepare 階段),一次是 binlog。

特別需要區分的是,redo log和binlog的不同。這也是經常在面試中可能會問到的兩種日誌的差異。

注意有這麼幾點:

  • 產生位置不同。

redo log是innodb的存儲引擎產生的,而binlog是數據庫的server層實現的。換句話說,如果你使用MySQL,換其他存儲引擎,那麼可能沒有redo log,但是還是會有binlog。

  • 日誌記錄的內容形式不同。

binlog是一種邏輯日誌,記錄對應的SQL語句,而redo log記錄了物理日誌,是針對每個數據頁的修改。

  • 日誌寫入磁盤時間不同。

binlog只有在事務提交後完成一次寫入,對於一個事物而言,在binlog中只有一條記錄。而redo log在事務進行中不斷被寫入,而且是併發寫入的,不是順序寫入的。

最詳細的MySQL事務解析:ACID的原理及實現

  • 保存方式不同。

redo log 是循環寫的,空間固定會用完;binlog 是可以追加寫入的。“追加寫”是指 binlog 文件寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌。

3.原子性的實現

Undo log保證了事務的原子性。

在對數據庫進行修改時,innoDB引擎除了會產生redo log,還會產生undo log。InnoDB實現回滾,靠的是undo log:當事務對數據庫進行修改時,InnoDB會生成對應的undo log;如果事務執行失敗導致事務需要回滾,就利用undo log中的信息將數據回滾到修改之前的樣子。

有人認為undo log是redo log的逆過程,其實是不對的。兩個日誌文件其實都能看作是一種對數據的恢復操作,redo log恢復事務導致的數據頁的修改,而undo log能夠恢復數據記錄到某個特定的版本。

所以redo log是一種物理日誌(數據頁的修改),而undo log是一種邏輯日誌(數據記錄)。

undo log還要另外一個重要作用,就是用於mvcc中,進行多版本控制,也就是實現事務隔離性的基礎,當用戶讀取一行記錄時,如果這個記錄已接被其他事務佔用,那麼當前事務就可以通過undo讀取之前的行版本信息,用來實現非鎖定讀取,就是“快照讀”。

4.一致性的實現

就像一開始在定義的時候介紹的,事務的ACID性質不是完全正交的,尤其是一致性,我們可以認為原子性、持久性和隔離性都是為了實現事務的一致性。

當然,這裡的一致性是指數據庫層面的事務一致性。

如果說你在應用層面做一個操作,給轉賬者扣錢,沒給接收者加錢,那麼這個不一致跟事務的不一致是沒有關係的,需要開發人員自己做業務邏輯一致性的保證。


分享到:


相關文章: