ACID的實現原理

Virtuals發表於2021-09-21

引言

ACID是事務的特點也是必須的要求,只有保證ACID事務的執行才不會出錯,分別是原子性、一致性、隔離性和永續性。我們知道典型的MySQL事務是這樣執行的:

  • start transaction 開啟事務
  • commit 提交事務
  • rollback 回滾事務

注意兩個預設機制:

  • 如果沒有顯示開啟事務,每條SQL都是單獨的事務
  • 自動提交機制

下面我們就來分析一下ACID是如何實現的?以及它和鎖機制、隔離級別的關係。

實現原理

1、原子性(Atomicity)

原子性就是說事務是一個不可分割的基本單位,其中的操作要麼全部執行,要麼都不執行,其實就是rollback的實現機制,原子性實現的原理是通過undo log。

undo log是邏輯日誌,它記錄的是每條sql。當事務對資料庫進行修改時,InnoDB 會生成對應的 undo log,如果事務執行失敗呼叫了rollback,便可以利用 undo log 中的資訊將資料回滾到修改之前的樣子。

對於每個 insert,回滾時會執行 delete。

對於每個 delete,回滾時會執行 insert。

對於每個 update,回滾時會執行一個相反的 update,把資料改回去。

2、永續性(Durability)

永續性是指事務一旦提交,它對資料庫的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。重點就是如何保證資料庫當機資料不受影響。實現原理是通過redo log

要想深入瞭解redolog,需要事先了解MySQL的儲存引擎是怎麼從磁碟讀取資料,又是如何把資料刷回磁碟的?這裡以InnoDB為例,由於磁碟IO速度很慢,因此InnoDB不直接與磁碟打交道,而是通過Buffer Pool緩衝池,以此來加速讀和加速寫。

加速讀指的是讀取時會優先從Buffer Pool讀取,如果 Buffer Pool 中沒有,則從磁碟讀取後放入 Buffer Pool。

加讀寫指的是當向資料庫寫入資料時,會首先寫入 Buffer Pool,Buffer Pool 中修改的資料會定期重新整理到磁碟中,這一過程稱為刷髒

但是如果buffer中儲存的資料還沒重新整理到磁碟資料庫就當機了,會造成資料永久丟失。於是引入redo log解決這個問題,原理是WAL,先寫log,再寫buffer,並且每次事務提交都會把redo log重新整理到磁碟。這個時候如果資料庫當機,也可以通過redo log的記錄恢復所有資料。

你可能會有的兩個疑問:

  1. 為什麼buffer pool中的資料要定期刷髒,如果每次事務提交都重新整理到磁碟,就不需要redo log

    因為每次修改的資料隨機,buffer pool刷髒過程是隨機IO,速度很慢,而redo log是追加操作,資料都是連續的,屬於順序IO。第二個原因是刷髒都是以資料頁為單位的,一個小修改都要整頁寫入,而redo log中只包含真正修改的部分,無效IO減少。

  2. redo log 和 bin log的關係,bin log是否與永續性有關?

    完全無關,兩者的層次和維度都不相同。bin log是server端的,用於備份和恢復資料、主從複製等,而redo log是innodb特有的,用於保證異常情況下資料安全。當然redo log的二階段提交也是必要的,用來保證redo log和bin log的一致性。

3、隔離性(Isolation)

1. 引言

這是個重頭戲,涉及到很多方面的內容

隔離性指的是事物內部的操作與其他事務是隔離的,併發執行的事務之間不能互相干擾。不同的隔離級別事務併發程度也不相同,能解決的問題也不同。一般來說,隔離級別越高併發程度越低,因為要加不同的鎖。

MySQL隔離級別 -- 可能產生的問題:

  • 讀未提交 -- 髒讀、不可重複讀、幻讀
  • 讀已提交 -- 不可重複讀、幻讀
  • 可重複讀 -- 幻讀
  • 序列化 -- 無

先對各個級別加鎖情況做個介紹,讓你有個基本概念:

  1. 讀未提交級別:不需要加任何鎖,因此它的併發程度最高,但同時也會引發各種併發問題
  2. 讀已提交級別:讀不需要加鎖,但是寫需要加排它鎖/MVCC
  3. 可重複讀級別:有兩種不同的實現方式,一是悲觀鎖即讀加共享鎖,寫加排它鎖,這種方式併發程度低;二是樂觀鎖即MVCC,它的優勢是不加鎖,使用undo log和檢視的概念實現,併發程度高
  4. 序列化:讀加共享鎖,寫加排他鎖,讀寫互斥

可以看到,隨著隔離級別的提高,假的鎖也更多,併發程度自然更低。實際應用時要根據業務需求,選擇最合適的隔離級別。

其實隔離性本質上就是解決兩個問題:

  • (一個事務)寫操作對(另一個事務)寫操作的影響:只能通過鎖機制(當前讀,讀取最新資料)

  • (一個事務)寫操作對(另一個事務)讀操作的影響:目標就是不通過加鎖也能解決,目前最優解是MVCC(快照讀,不需要最新的資料)

2. 鎖的分類

接下來簡單介紹一下資料庫的鎖,可以從兩個維度進行分析:

一、從鎖範圍分,可以把鎖分為:全域性鎖、表級鎖、行級鎖,鎖的精度逐漸增加,鎖精度越高,需要同時鎖住的資料越少,併發程度越高

二、從鎖的作用分,可以把鎖分為:共享鎖(其他鎖只能讀不能寫)、排他鎖(其他鎖不能讀也不能寫),很明顯,共享鎖的併發程度更高

3. MVCC的實現原理

主要依靠資料的隱藏列(也可以稱之為標記位)和 undo log。其中資料的隱藏列包括了該行資料的版本號、刪除時間、指向 undo log 的指標等等。當讀取資料時,MySQL 可以通過隱藏列判斷是否需要回滾並找到回滾需要的 undo log,從而實現 MVCC。

在InnoDB中,會在每行資料後新增兩個額外的隱藏的值來實現MVCC,這兩個值一個記錄這行資料何時被建立,另外一個記錄這行資料何時過期(或者被刪除)。 在實際操作中,儲存的並不是時間,而是事務的版本號,每開啟一個新事務,事務的版本號就會遞增。 在可重讀Repeatable reads事務隔離級別下:

  • SELECT時,讀取建立版本號<=當前事務版本號,刪除版本號為空或>當前事務版本號。

    • 1、InnoDB 只查詢版本早於當前事務版本的資料行(也就是資料行的版本必須小於等於事務的版本),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務建立或修改的行
    • 2、行的刪除操作的版本一定是未定義的或者大於當前事務的版本號,確定了當前事務開始之前,行沒有被刪除

    符合了以上兩點則返回查詢結果。

  • INSERT時,儲存當前事務版本號為行的建立版本號

    • InnoDB 為每個新增行記錄當前系統版本號作為建立 ID。
  • DELETE時,儲存當前事務版本號為行的刪除版本號

    • InnoDB 為每個刪除行的記錄當前系統版本號作為行的刪除 ID。
  • UPDATE時,插入一條新紀錄,儲存當前事務版本號為行建立版本號,同時儲存當前事務版本號到原來刪除的行

這裡簡單做下總結:

  • insert 操作時 “建立時間”=DB_ROW_ID,這時,“刪除時間 ”是未定義的;

  • update 時,複製新增行的“建立時間”=DB_ROW_ID,刪除時間未定義,舊資料行“建立時間”不變,刪除時間=該事務的 DB_ROW_ID;

  • delete 操作,相應資料行的“建立時間”不變,刪除時間=該事務的 DB_ROW_ID;

  • select 操作對兩者都不修改,只讀相應的資料

4. 可重複讀的實現

快照讀(MVCC)

當你執行 begin 開啟事務之後,MySQL 會拍下像下圖這樣的快照:

image-20210921204608331

  • 當讀取的記錄的事務版本號小於當前事務版本號,並且不再活躍事務中,說明修改該記錄的事務已經被提交,此記錄可讀
  • 當讀取的記錄的事務版本號大號當前事務版本號,說明修改該記錄的事務已經未提交,此記錄不可讀,通過undo log往前找,直到找到第一個 trx_id 等於或者小於自己事務 ID 的記錄為止

當前讀(間隙鎖)

與其他資料庫,MySQL資料庫的可重複讀可以解決幻讀問題,原理就通過間隙鎖,為某行記錄新增行鎖時同時為附近的記錄也新增行鎖,雖然這種實現方式很多時候會鎖住不需要鎖的區間。如下所示:

image-20210921205402608

5. 讀已提交的實現

得益於MVCC,讀已提交的隔離級別也可以通過undo log+檢視的機制實現,避免頻繁加鎖

具體的實現方式是每執行一個SQL都要重新建立檢視,根據檢視各變數和記錄事務ID判斷此記錄可不可讀

為什麼要每條SQL都要重複建立檢視呢?因此讀已提交隔離級別下可以讀到其他事務已提交的事務,所以每條SQL執行前都要更新檢視中的活躍事務ID。

4、一致性(Consistency)

致性是指事務執行結束後,資料庫的完整性約束沒有被破壞,事務執行的前後都是合法的資料狀態。資料庫的完整性約束包括但不限於:實體完整性(如行的主鍵存在且唯一)、列完整性(如欄位的型別、大小、長度要符合要求)、外來鍵約束、使用者自定義完整性(如轉賬前後,兩個賬戶餘額的和應該不變)

可以說,一致性是事務追求的最終目標:前面提到的原子性、永續性和隔離性,都是為了保證資料庫狀態的一致性。此外,除了資料庫層面的保障,一致性的實現也需要應用層面進行保障。

總結

本文從事務的四大特性出發,結合日誌機制、鎖機制以及隔離級別,簡單梳理了事務四大特性ACID的實現原理以及它們之間的關係,其中最重要的是隔離性的實現,保護經典樂觀鎖MVCC以及檢視機制,希望能對你理解MySQL事務有一點幫助。

作者實力有限,若有錯誤之處,歡迎留言指出。最後祝大家中秋快樂!

參考

相關文章