數據庫acid實現原理


1.原子性:是指一個事務是一個不可分割的工作單位,其中的操作要么都做,要么都不做;如果事務中一個sql語句執行失敗,則已執行的語句也必須回滾,數據庫退回到事務前的狀態。

實現原理:undo log

MySQL的日志有很多種,如二進制日志、錯誤日志、查詢日志、慢查詢日志等,此外InnoDB存儲引擎還提供了兩種事務日志:redo log(重做日志)和undo log(回滾日志)。其中redo log用於保證事務持久性;undo log則是事務原子性和隔離性實現的基礎。

undo log:實現原子性的關鍵,是當事務回滾時能夠撤銷所有已經成功執行的sql語句。InnoDB實現回滾,靠的是undo log:當事務對數據庫進行修改時,InnoDB會生成對應的undo log;如果事務執行失敗或調用了rollback,導致事務需要回滾,便可以利用undo log中的信息將數據回滾到修改之前的樣子

undo log屬於邏輯日志,它記錄的是sql執行相關的信息。當發生回滾時,InnoDB會根據undo log的內容做與之前相反的工作:對於每個insert,回滾時會執行delete;對於每個delete,回滾時會執行insert;對於每個update,回滾時會執行一個相反的update,把數據改回去。

 

2.持久性

實現原理:redo log

redo log和undo log都屬於InnoDB的事務日志。下面先聊一下redo log存在的背景。

InnoDB作為MySQL的存儲引擎,數據是存放在磁盤中的,但如果每次讀寫數據都需要磁盤IO,效率會很低。為此,InnoDB提供了緩存(Buffer Pool),Buffer Pool中包含了磁盤中部分數據頁的映射,作為訪問數據庫的緩沖:當從數據庫讀取數據時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool;當向數據庫寫入數據時,會首先寫入Buffer Pool,Buffer Pool中修改的數據會定期刷新到磁盤中(這一過程稱為刷臟)。

Buffer Pool的使用大大提高了讀寫數據的效率,但是也帶了新的問題:如果MySQL宕機,而此時Buffer Pool中修改的數據還沒有刷新到磁盤,就會導致數據的丟失,事務的持久性無法保證。

於是,redo log被引入來解決這個問題:當數據修改時,除了修改Buffer Pool中的數據,還會在redo log記錄這次操作;當事務提交時,會調用fsync接口對redo log進行刷盤。如果MySQL宕機,重啟時可以讀取redo log中的數據,對數據庫進行恢復。redo log采用的是WAL(Write-ahead logging,預寫式日志),所有修改先寫入日志,再更新到Buffer Pool,保證了數據不會因MySQL宕機而丟失,從而滿足了持久性要求。

既然redo log也需要在事務提交時將日志寫入磁盤,為什么它比直接將Buffer Pool中修改的數據寫入磁盤(即刷臟)要快呢?主要有以下兩方面的原因:

  • 刷臟是隨機IO,因為每次修改的數據位置隨機,但寫redo log是追加操作,屬於順序IO。

  • 刷臟是以數據頁(Page)為單位的,MySQL默認頁大小是16KB,一個Page上一個小修改都要整頁寫入;而redo log中只包含真正需要寫入的部分,無效IO大大減少。

3.隔離性:隔離性追求的是並發情形下事務之間互不干擾

實現原理:鎖機制

行鎖與表鎖

按照粒度,鎖可以分為表鎖、行鎖以及其他位於二者之間的鎖。表鎖在操作數據時會鎖定整張表,並發性能較差;行鎖則只鎖定需要操作的數據,並發性能好。但是由於加鎖本身需要消耗資源(獲得鎖、檢查鎖、釋放鎖等都需要消耗資源),因此在鎖定數據較多情況下使用表鎖可以節省大量資源。MySQL中不同的存儲引擎支持的鎖是不一樣的,例如MyIsam只支持表鎖,而InnoDB同時支持表鎖和行鎖,且出於性能考慮,絕大多數情況下使用的都是行鎖。

加鎖讀與next-key lock

按照是否加鎖,MySQL的讀可以分為兩種:

一種是非加鎖讀,也稱作快照讀、一致性讀,使用普通的select語句,這種情況下使用MVCC避免了臟讀、不可重復讀、幻讀,保證了隔離性。

MVCC:在同一時刻,不同的事務讀取到的數據可能是不同的(即多版本)

MVCC最大的優點是讀不加鎖,因此讀寫不沖突,並發性能好。InnoDB實現MVCC,多個版本的數據可以共存,主要基於以下技術及數據結構:

  1. 隱藏列:InnoDB中每行數據都有隱藏列,隱藏列中包含了本行數據的事務id、指向undo log的指針等。

  2. 基於undo log的版本鏈:前面說到每行數據的隱藏列中包含了指向undo log的指針,而每條undo log也會指向更早版本的undo log,從而形成一條版本鏈。

 

  3.   ReadView:通過隱藏列和版本鏈,MySQL可以將數據恢復到指定版本;但是具體要恢復到哪個版本,則需要根據ReadView來確定。所謂ReadView,是指事務(記做事務A)在某一時刻給整個事務系統(trx_sys)打快照,之后再進行讀操作時,會將讀取到的數據中的事務id與trx_sys快照比較,從而判斷數據對該ReadView是否可見,即對事務A是否可見。

trx_sys中的主要內容,以及判斷可見性的方法如下

 

    • low_limit_id:表示生成ReadView時系統中應該分配給下一個事務的id。如果數據的事務id大於等於low_limit_id,則對該ReadView不可見。
    • up_limit_id:表示生成ReadView時當前系統中活躍的讀寫事務中最小的事務id。如果數據的事務id小於up_limit_id,則對該ReadView可見。
    • rw_trx_ids:表示生成ReadView時當前系統中活躍的讀寫事務的事務id列表。如果數據的事務id在low_limit_id和up_limit_id之間,則需要判斷事務id是否在rw_trx_ids中:如果在,說明生成ReadView時事務仍在活躍中,因此數據對ReadView不可見;如果不在,說明生成ReadView時事務已經提交了,因此數據對ReadView可見。

 

加鎖讀在查詢時會對查詢的數據加鎖(共享鎖或排它鎖)。由於鎖的特性,當某事務對數據進行加鎖讀后,其他事務無法對數據進行寫操作,因此可以避免臟讀和不可重復讀。而避免幻讀,則需要通過next-key lock。next-key lock是行鎖的一種,實現相當於record lock(記錄鎖) + gap lock(間隙鎖);其特點是不僅會鎖住記錄本身(record lock的功能),還會鎖定一個范圍(gap lock的功能)。因此,加鎖讀同樣可以避免臟讀、不可重復讀和幻讀,保證隔離性。

 

下面總結一下ACID特性及其實現原理:

  • 原子性:語句要么全執行,要么全不執行,是事務最核心的特性,事務本身就是以原子性來定義的;實現主要基於undo log
  • 持久性:保證事務提交后不會因為宕機等原因導致數據丟失;實現主要基於redo log
  • 隔離性:保證事務執行盡可能不受其他事務影響;InnoDB默認的隔離級別是RR,RR的實現主要基於鎖機制(包含next-key lock)、MVCC(包括數據的隱藏列、基於undo log的版本鏈、ReadView)
  • 一致性:事務追求的最終目標,一致性的實現既需要數據庫層面的保障,也需要應用層面的保障

 


免責聲明!

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



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