說起MySQL事務處理的四大特性,相信大家都可以張口就來:ACID!
那 MySQL是如何實現ACID的?每種特性的原理又是如何實現的?
今天,本文筆者主要探討MYSQL InnoDB引擎下的ACID實現原理,對事務、鎖以及隔離級別等內容統一進行回顧一下。

1、ACID特性
原子性(Atomicity)
單個事務,為一個不可分割的最小工作單元,整個事務中的所有操作要么全部commit成功,要么全部失敗rollback,對于一個事務來說,不可能只執行其中的一部分SQL操作,這就是事務的原子性。
一致性(Consistency)
數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。在前面的例子中, 一致性確保了,即使在執行第三、四條語句之間時系統崩潰,信用卡賬戶也不會損 失100塊,因為事務最終沒有提交,所以事務中所做的修改也不會保存到數據庫中,保證數據一致性。
隔離性(Isolation)
通常來說,一個事務所做的修改在最終提交以前,對其他事務是不可見(隔離)的。避免多個事務并發執行的時候不會互相干擾。
持久性(Durability)
一旦事務提交,則其所做的修改就會永久保存到數據庫中,之后的其他操作或故障都不會對事務的結果產生影響。
2、ACID 具體實現
原子性:通過undolog來實現。
持久性:通過binlog、redolog來實現。
隔離性:通過(讀寫鎖+MVCC)來實現。
一致性:MySQL通過原子性、持久性、隔離性最終實現數據一致性。
對MySQL來說,邏輯備份日志(binlog)、重做日志(redolog)、回滾日志(undolog)、鎖技術 + MVCC就是MySQL實現事務的基礎。
2.1 原子性原理
事務通常是以BEGIN TRANSACTION 開始,以 COMMIT 或 ROLLBACK 結束。
COMMIT 表示提交,即提交事務的所有操作并持久化到數據庫中。
ROLLBACK表示回滾,即在事務中運行的過程中發生了某種故障,事務不能繼續執行,系統將事務中對數據庫所有已完成的操作全部撤銷,回滾到事務開始時的狀態,這里的操作指對數據庫的更新操作(查詢操作忽略)。這時候需要用到 undolog 來進行回滾。
undolog:
每條數據變更(INSERT/UPDATE/DELETE/REPLACE)等操作都會生成一條undolog記錄,在SQL執行前先于數據持久化到磁盤。
insert語句,回滾時會執行 delete;
delete語句,回滾時會執行insert;
update語句,回滾時便執行相反的update,把數據改回來。
當事務需要回滾時,MySQL會根據回滾日志對事務中已執行的SQL做逆向操作,比如 DELETE 一行數據的逆向操作就是再把這行數據 INSERT回去,其他操作同理。
undolog記錄事務開始前老版本數據,用于實現回滾,保證原子性,實現MVCC,會將數據修改前的舊版本保存在undolog,然后行記錄有個隱藏字段回滾指針指向老版本。
2.2 持久性原理
我們知道,MySQL表數據是持久化到磁盤中的,但如果所有操作都去操作磁盤,等并發上來了,那處理效率無法保證,因此引入了緩沖池(Buffer Pool)的概念,Buffer Pool 中包含了磁盤中部分數據頁的映射,可以當做緩存來用;這樣當修改表數據時,我們把操作記錄先寫到Buffer Pool中,并標記事務已完成,等MySQL空閑時,再把更新操作持久化到磁盤里,從而大大緩解了MySQL并發壓力。
MYSQL的持久性便是由redo log來保證。
redo log
是一種物理日志,作用:會記錄事務開啟后對數據做的修改,crash-safe。
它類似于一個卸貨的小推車,我們若是每卸一件物品就拿著去入庫,那豈不是特浪費時間,若有一個小推車,我們將貨物首先存放在小推車,當推車滿了再往庫里存,可以大大提升效率。
其實就是MySQL里經常說到的WAL技術,WAL的全稱是Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤,也就是先裝小推車,等不忙的時候再裝庫。
特性:空間一定,寫完后會循環寫,有兩個指針write pos指向當前記錄位置,checkpoint指向將擦除的位置,redolog相當于是個取貨小車,貨物太多時來不及一件一件入庫太慢了這樣,就先將貨物放入小車,等到貨物不多或則小車滿了或則店里空閑時再將小車貨物送到庫房。用于crash-safe,數據庫異常斷電等情況可用redo log恢復。
以下只作了解:
寫入流程:先寫redo log buffer,然后wite到文件系統的page cache,此時并沒有持久化,然后fsync持久化到磁盤
寫入策略:根據innodb_flush_log_at_trx_commit參數控制(innodb以事務的什么提交方式刷新日志)
0——>事務提交時只把redo log留在redo log buffer
1——>將redo log直接持久化到磁盤(所以有個雙“1”配置,后面會講)
2——>只是把redo log寫到page cache
2.3 隔離性原理
MYSQL有四種隔離級別,用來解決存在的并發問題:臟讀、幻讀、不可重復讀。

那么不同隔離級別,隔離性是怎樣實現的呢?
一句話:鎖+MVCC。
鎖
表鎖:讀鎖(不會阻塞其他線程的讀操作,阻塞寫操作);寫鎖(讀寫操作都阻塞)
行鎖:需要的時候加上,并不是馬上釋放,等事務提交才釋放,兩階段鎖協議
鎖的類型
間隙鎖-gap lock:鎖定區間范圍,防止幻讀,左開右開,只在可重復讀隔離級別下生效—|—為了阻止多個事務將記錄插入到同一范圍內,而這會導致幻讀問題的產生
記錄鎖-record Lock:鎖定行記錄,索的索引,索引失效,為表鎖
臨鍵鎖-next-key Lock:record lock+gap lock 左開右閉(解決幻讀)
鎖的模式
select .... for update (持有寫鎖,別的不可加讀鎖,也不可加寫鎖)
select .... lock in share mode(持有讀鎖,別的可以再加讀鎖,不可加寫鎖)
共享鎖-讀鎖-S鎖
排他鎖-寫鎖-X鎖
意向鎖:讀意向鎖+寫意向鎖
自增鎖
全局鎖:全庫邏輯備份
死鎖:兩個或多個事務在同一資源上相互占用,并請求加鎖時,造成相互等待,無限阻塞
MVCC:實現多版本并發控制,實現原理:使用版本鏈+Read View
讀已提交和可重復讀實現原理就是MVCC Read View不同的生成時機。可重復讀只在事務開始時生成一個Read View,之后都用的這個;讀已提交每次執行前都會生成Read View。
2.4 一致性原理
一致性是事務追求的最終目標,前文所述的原子性、持久性和隔離性,其實都是為了保證數據庫狀態的一致性,數據庫中的增刪改操作,使數據庫不斷從一個一致性的狀態轉移到另一個一致性的狀態。
總結
事務該回滾的回滾,該提交的提交,提交后該持久化磁盤的持久化磁盤,該寫緩沖池的寫緩沖池+寫日志。
對于數據可見性,通過四種隔離級別進行控制,使得庫表中的有效數據范圍可控,保證業務數據的正確性的前提下,進而提高并發程度,支撐服務高QPS的穩定運行,保證數據的一致性,這就是咱們說個不停的MySQL數據庫事務ACID四大特性。






