1. 事務概述
事務可由一條簡單的SQL語句組成,也可以由一組復雜的SQL語句組成. 事務是訪問并更新數據庫中各種數據項的一個程序執行單元. 在事務中的操作, 要么都做修改, 要么都不做.
對于 InnoDB存儲引擎而言, 其默認的事務隔離級別 RR , 完全遵循和滿足了事務的 ACID 特性.
1.1 原子性
原子性指整個數據庫事務是不可分割的工作單位. 只有使事務中所有的數據庫操作都執行成功, 才算整個事務成功. 事務中任何一個 SQL 語句執行失敗, 已經執行成功的 SQL 語句也必須撤銷, 數據庫狀態應該退回到執行事務前的狀態.
1.2 一致性
一致性指事務將數據庫從一種狀態轉變為下一種一致的狀態. 在事務開始之前和事務結束后, 數據庫的完整性約束沒有被破壞.
1.3 隔離性
事務的隔離性要求每個讀寫事務的對象對其他事務的操作對象能相互分離, 即該事務提交前對其他事務都不可見.
1.4 持久性
事務一旦提交, 其結果是永久性的. 即使發生宕機等故障, 數據庫也能恢復.
2. 事務的實現
隔離性是由鎖實現的, 原子性, 一致性, 持久性通過數據庫的 redo log 和 undo log 來完成.
2.1 redo
基本概念
重做日志用來實現事務的持久性. 其由兩部分組成: ①重做日志緩沖 (redo log buffer), 其在內存; ②是重做日志文件 (redo log file), 其是持久的.
InnoDB存儲引擎, 其通過 Force Log at Commit 機制實現事務的持久性, 即當事務提交的時候, 必須先將該事務的所有日志文件進行持久化, 待事務 COMMIT 操作完成后才算完成.
為了保證每次日志都寫入重做日志文件, 在每次將重做日志緩沖寫入重做日志文件后, InnoDB存儲引擎都需要調用一次 fsync 操作.
參數 innodb_flush_log_at_trx_commit
用來控制重做日志刷新到磁盤的策略. 該參數默認1. 參數解釋如下:
0 | 事務提交時不進行刷盤, 由后臺線程去每秒刷盤一次. |
---|---|
1(默認) | 事務提交時,必須調用 fsync 函數進行刷盤. |
2 | 事務提交時,僅寫入操作系統的緩沖種,不進行 fsync. |
隨然用戶可以通過設置為0或2來提高并發性能, 但是這樣會喪失事務的持久性.
重做日志和二進制日志
- 重做日志是在 InnoDB存儲引擎層產生; 而二進制日志是在 MySQL 數據庫層面產生的.
- 二進制日志是一種邏輯日志; 重做日志是物理日志, 記錄的是對于每個頁的修改.
- 兩種日志記錄的時間點不同, 二進制日志是在事務提交完成后進行一次寫入; 重做日志文件在事務種不斷的寫入.
兩階段提交
在事務提交時, 先寫二進制日志, 再寫InnoDB存儲引擎的重做日志. 對于上述兩個操作也是原子的, 即二進制日志和重做日志必須同時寫入.
若二進制日志寫完了, 而在 寫入InnoDB存儲引擎時(第三步)發生了宕機, 那么 slave 可能會接受到 master 傳過去的二進制日志并執行, 最終導致主從不一致, 如下圖示
為了解決上述問題, MySQL數據庫在 binlog 與 InnoDB存儲引擎之間采用兩階段提交. 當事務提交時, InnoDB存儲引擎會先做一個 PREPARE 操作, 將事務的 xid 寫入, 接著進行二進制日志的寫入, 如下圖演示.
MySQL數據庫中InnoDB存儲引擎的兩階段提交用于確保事務在主庫(Master)和從庫(Slave)之間的一致性。下面是對圖中各個步驟的解釋:
- innodb prepare(步驟①):
- 在主庫上,事務首先進入準備階段。此時,InnoDB會記錄事務的所有變更到內存中的事務日志(redo log),但這些變更還沒有被實際應用到數據庫文件中。
- 這個階段確保了即使在事務提交之前發生故障,數據庫也能通過重做日志恢復到一致的狀態。
- write binlog(步驟②):
- 主庫將事務的變更記錄寫入到二進制日志(binlog)中。二進制日志記錄了所有修改數據庫狀態的操作,用于復制到從庫或在主庫故障時恢復。
- 寫入binlog是兩階段提交的第一階段,稱為“prepare”階段。
- 發送binlog到從庫(步驟③):
- 主庫將二進制日志發送到從庫。從庫接收到binlog后,將其寫入到自己的中繼日志(relay log)中。
- 這個過程確保了從庫能夠接收到主庫上所有事務的變更,以便進行數據復制。
- innodb write redo log(步驟④):
- 主庫在確認從庫已經接收到二進制日志后,將事務的變更從內存中的事務日志(redo log)應用到數據庫文件中,完成事務的提交。
- 這個階段是兩階段提交的第二階段,稱為“commit”階段。
通過這種兩階段提交機制,MySQL確保了即使在主庫和從庫之間發生故障,事務的一致性和完整性也能得到保證。這種機制對于分布式數據庫系統來說非常重要,因為它可以防止數據不一致和丟失。
3. undo
3.1 基本概念
在對數據庫進行修改時, InnoDB 還會產生一定量的 undo . 這樣如果用戶執行的事務或語句由于某種原因失敗時, 就可以用這些 undo 信息將數據回滾到修改之前的樣子.
undo 是邏輯日志.
undo 存放在共享表空間.
除了 回滾操作, undo 的另一個作用是 MVCC , 即在 InnoDB 存儲引擎種 MVCC 實現是通過 undo 讀取之前的行版本信息, 以此來實現非鎖定讀.
當事務提交后, 并不會立馬刪除 undo , 這是因為 MVCC 中可能有使用到.
4. 事務的隔離級別及其實現原理
SQL 表中定義了四個隔離級別:
讀未提交
讀已提交
可重復讀 (InnoDB存儲引擎默認隔離級別)
串行化讀
串行化讀 的事務隔離級別下, InnoDB 存儲引擎會對每個 SELECT 語句后自動加上
LOCK IN SHARE MODE
(共享鎖), 因此在整個事務隔離級別下, 就不是一致性的非鎖定讀了.
隔離級別的實現原理
實現隔離機制的方法主要有兩種:
- 讀寫鎖
- 一致性快照讀,即 MVCC
MySql使用不同的鎖策略(Locking Strategy)/MVCC來實現四種不同的隔離級別。RR、RC的實現原理跟MVCC有關,RU和Serializable跟鎖有關。
4.1 讀未提交(Read Uncommitted)
官方說法:
SELECT statements are performed in a nonlocking fashion, but a possible earlier version of a row might be used. Thus, using this isolation level, such reads are not consistent.
讀未提交,采取的是讀不加鎖原理。
- 事務讀不加鎖,不阻塞其他事務的讀和寫
- 事務寫阻塞其他事務寫,但不阻塞其他事務讀;
4.2 讀已提交
MVCC
4.3 可重復讀
MVCC
4.4 串行化
- 所有
<font style="color:rgb(180, 180, 180) !important;background-color:rgb(36, 36, 41) !important;">SELECT</font>
語句會隱式轉化為<font style="color:rgb(180, 180, 180) !important;background-color:rgb(36, 36, 41) !important;">SELECT … FOR SHARE</font>
,即加共享鎖。 - 讀加共享鎖,寫加排他鎖,讀寫互斥。如果有未提交的事務正在修改某些行,所有 select 這些行的語句都會阻塞。
5. 不好的事務習慣
- 在循環中提交
- 長事務 (應該拆解為小事務)
6. 實現原理(來自DeepSeek)
MySQL 5.7通過以下機制實現事務的ACID特性及隔離級別:
一、ACID實現原理
- 原子性(Atomicity)
- 實現機制:Undo Log
當事務執行時,所有修改會生成對應的Undo Log。若事務失敗或回滾,MySQL利用Undo Log逆向操作恢復數據。例如,插入操作會記錄刪除的Undo Log,刪除操作會記錄插入的Undo Log。
- 實現機制:Undo Log
- 持久性(Durability)
- 實現機制:Redo Log + 刷盤策略
事務提交時,修改先寫入Redo Log(順序寫,性能高),再異步刷到磁盤。崩潰恢復時,通過Redo Log重放未落盤的修改。InnoDB的innodb_flush_log_at_trx_commit=1
強制每次提交刷Redo Log到磁盤。
- 實現機制:Redo Log + 刷盤策略
- 隔離性(Isolation)
- 實現機制:鎖(Locking) + MVCC(多版本并發控制)
- 鎖:寫操作加行鎖、間隙鎖(Gap Lock)、臨鍵鎖(Next-Key Lock),解決臟寫和部分幻讀。
- MVCC:通過ReadView和Undo Log鏈實現非阻塞讀,解決臟讀、不可重復讀。
- 實現機制:鎖(Locking) + MVCC(多版本并發控制)
- 一致性(Consistency)
- 由應用邏輯(如約束、觸發器等)和數據庫其他三個特性(原子性、隔離性、持久性)共同保證。
二、四個隔離級別的實現原理
MySQL支持四種隔離級別,隔離性由鎖和MVCC共同實現:
1. 讀未提交
- 行為:允許讀取其他事務未提交的數據(臟讀)。
- 實現:
- 寫操作加行級排他鎖(X Lock),事務結束后釋放。
- 讀操作不加鎖,直接讀取最新版本(包括未提交的數據)。
2. 讀已提交
- 行為:事務內每次查詢生成新快照,解決臟讀,但存在不可重復讀。
- 實現:
- MVCC:每條SELECT語句生成獨立的ReadView,僅讀取已提交的數據版本。
- 鎖機制:寫操作加行鎖,事務提交后釋放鎖。例如,
UPDATE
操作加X鎖,提交后釋放。
3. 可重復讀
- 行為:事務內首次讀生成快照,后續讀復用該快照,解決不可重復讀。通過臨鍵鎖解決幻讀。
- 實現:
- MVCC:事務開始時生成ReadView,后續所有讀操作基于該視圖,通過Undo Log鏈訪問歷史版本。
- 鎖機制:
- 寫操作(如
UPDATE
)加臨鍵鎖(Next-Key Lock),鎖住記錄+間隙,阻止其他事務插入。 - 普通SELECT使用快照讀(無鎖),
SELECT ... FOR UPDATE
使用當前讀(加臨鍵鎖)。
- 寫操作(如
4. 串行化
- 行為:強制事務串行執行,無并發問題,性能最低。
- 實現:
- 所有SELECT隱式轉換為
SELECT ... FOR SHARE
(加S鎖),讀寫互斥。 - 寫操作加X鎖,讀操作加S鎖,通過嚴格的鎖沖突實現串行化。
- 所有SELECT隱式轉換為
三、MVCC與鎖的協作
- MVCC核心結構:
- Undo Log鏈:每行記錄包含
DB_TRX_ID
字段(最后修改的事務ID),通過Undo Log構建歷史版本鏈。 - ReadView:記錄活躍事務ID列表,判斷數據版本可見性(通過比較
DB_TRX_ID
與ReadView的up_limit_id
和low_limit_id
)。
- Undo Log鏈:每行記錄包含
- 不同隔離級別的ReadView生成策略:
- RC:每次SELECT生成新ReadView。
- RR:事務首次SELECT生成ReadView,后續復用。
- 臨鍵鎖(Next-Key Lock)的作用:
- 鎖住記錄及之前的間隙(如
id=5
的鎖范圍是(-∞,5]
),阻止其他事務插入,解決幻讀。
- 鎖住記錄及之前的間隙(如
四、總結
- ACID依賴機制:
- 原子性→Undo Log,持久性→Redo Log,隔離性→鎖+MVCC,一致性→三者共同保障。
- 隔離級別權衡:
- 隔離級別越高,鎖沖突越多,并發性能越低。
- MySQL的RR通過MVCC+臨鍵鎖在性能與一致性間取得平衡。
通過理解這些機制,可以更好地設計事務邏輯和調優數據庫并發性能。