ACID的實現原理


引言

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事務有一點幫助。

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

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM