Transaction Isolation Levels
深入解析 SQL 標準定義的四種交易隔離等級,以及 InnoDB 在各等級下的實際行為與實作機制
交易的四個特性(ACID)
| 特性 | 意義 | 由誰保證 |
|---|---|---|
| Atomicity(原子性) | 交易要嘛全做,要嘛全不做 | Undo Log |
| Consistency(一致性) | 交易前後資料滿足所有約束 | 應用層 + A、I、D 共同保證 |
| Isolation(隔離性) | 併發交易互不干擾 | MVCC + Locks |
| Durability(持久性) | COMMIT 後資料不遺失 | Redo Log |
Consistency 不是某個單一機制能保證的,它是其他三者正確運作加上應用邏輯正確的結果。
三種讀異常
SQL 標準定義了三種併發讀異常,隔離等級就是「你願意接受哪幾種」:
Dirty Read(髒讀)
讀到其他交易尚未 COMMIT 的資料。如果對方 ROLLBACK,你讀到的就是不存在的幽靈資料。
T1: UPDATE accounts SET balance = 0 WHERE id = 1; -- 未 COMMIT
T2: SELECT balance FROM accounts WHERE id = 1; -- 讀到 0(髒讀)
T1: ROLLBACK; -- balance 其實還是原來的值
Non-Repeatable Read(不可重複讀)
同一筆交易內兩次讀同一行,結果不同——因為中間有其他交易 COMMIT 了修改。
T1: SELECT balance FROM accounts WHERE id = 1; -- 讀到 1000
T2: UPDATE accounts SET balance = 500 WHERE id = 1; COMMIT;
T1: SELECT balance FROM accounts WHERE id = 1; -- 讀到 500(不可重複讀)
Phantom Read(幻讀)
同一筆交易內兩次用相同條件查範圍,結果筆數不同——因為中間有其他交易 INSERT 或 DELETE 了符合條件的行。
T1: SELECT * FROM orders WHERE amount > 100; -- 3 筆
T2: INSERT INTO orders (amount) VALUES (200); COMMIT;
T1: SELECT * FROM orders WHERE amount > 100; -- 4 筆(幻讀)
注意:幻讀針對的是行數變化(INSERT/DELETE),不可重複讀針對的是同一行值變化(UPDATE)。
四種隔離等級
SQL 標準從低到高定義了四種隔離等級:
| 隔離等級 | Dirty Read | Non-Repeatable Read | Phantom Read | InnoDB 實作 |
|---|---|---|---|---|
| Read Uncommitted | ○ 可能 | ○ 可能 | ○ 可能 | 不用 MVCC,直接讀最新值 |
| Read Committed | × 防止 | ○ 可能 | ○ 可能 | 每次 SELECT 建新 Read View |
| Repeatable Read | × 防止 | × 防止 | △ 部分防止 | 交易首次 SELECT 建 Read View,之後重用 |
| Serializable | × 防止 | × 防止 | × 防止 | 所有 SELECT 加 S lock |
InnoDB 預設是 Repeatable Read(RR),而且在 RR 下透過 MVCC + Next-Key Lock 把幻讀也擋掉了大部分,這是比 SQL 標準更強的保證。
InnoDB 在各等級下的行為
Read Uncommitted
幾乎不用。直接讀 Buffer Pool 中的最新版本,不走 MVCC,所以能讀到別人未 COMMIT 的修改。唯一優勢是零額外開銷,但實務上沒人願意承擔髒讀的風險。
Read Committed(RC)
每次 SELECT 都建立一個新的 Read View(快照),所以能看到「截至這次 SELECT 為止所有已 COMMIT 的修改」。
特性:
- 不會髒讀:Read View 過濾掉未 COMMIT 的交易
- 會不可重複讀:兩次 SELECT 的 Read View 不同,中間有 COMMIT 就看得到
- 會幻讀:同理,新 INSERT 被 COMMIT 後下次 SELECT 看得到
- 鎖行為:只鎖符合條件的行(record lock),不加 gap lock,因此鎖衝突少
很多網際網路公司(阿里巴巴內部規範)選 RC 而非 RR,原因是 RC 的 gap lock 少、死鎖概率低、併發表現好,代價是需要在應用層處理不可重複讀。
Repeatable Read(RR)— InnoDB 預設
交易中第一次 SELECT 建立 Read View,之後所有 SELECT 重用同一份。所以不管別的交易怎麼改,你在同一筆交易裡看到的快照不會變。
-- Session A
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 讀到 1000,建立 Read View
-- Session B
UPDATE accounts SET balance = 500 WHERE id = 1;
COMMIT;
-- Session A(仍在同一筆交易)
SELECT balance FROM accounts WHERE id = 1; -- 仍然讀到 1000(Read View 沒變)
COMMIT;
RR 下的幻讀防護
InnoDB 在 RR 下用兩種手段防幻讀:
- 快照讀(Snapshot Read):一般 SELECT 走 MVCC Read View,天生不受其他交易 INSERT 影響。
- 當前讀(Current Read):
SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE讀的是最新版本,InnoDB 用 Next-Key Lock(record lock + gap lock)鎖住掃描過的範圍,阻止其他交易在這個範圍內 INSERT。
但有一個著名的邊界情況:
-- Session A(RR)
START TRANSACTION;
SELECT * FROM orders WHERE id = 5; -- 不存在,回傳空(快照讀,沒加鎖)
-- Session B
INSERT INTO orders (id, amount) VALUES (5, 200);
COMMIT;
-- Session A
UPDATE orders SET amount = 300 WHERE id = 5; -- 成功!UPDATE 是當前讀,看到了 Session B 的 INSERT
SELECT * FROM orders WHERE id = 5; -- 讀到 id=5, amount=300(幻讀!)
快照讀說不存在,但 UPDATE 當前讀發現存在,修改完後快照讀也看到了——因為自己改過的行會進入自己的版本鏈。要完全防止這種情況,第一次查詢就要用 SELECT ... FOR UPDATE 觸發當前讀 + gap lock。
Serializable
所有 SELECT 自動加 LOCK IN SHARE MODE(S lock),等價於所有讀都是當前讀 + 鎖。完全消除幻讀,但併發度極低,幾乎等於序列化執行。只有對一致性要求極端的場景才會用(分散式交易的 2PC 內部)。
RC vs RR:怎麼選
| 面向 | RC | RR |
|---|---|---|
| Read View | 每次 SELECT 新建 | 首次 SELECT 建立,之後重用 |
| Gap Lock | 無 | 有(Next-Key Lock) |
| 死鎖概率 | 低 | 較高(gap lock 交叉容易死鎖) |
| 幻讀 | 有 | 快照讀防、當前讀靠 Next-Key Lock 防 |
| Binlog 格式要求 | 需要 ROW(STATEMENT 不安全) | STATEMENT 也安全 |
| 適用 | 高併發 OLTP、能接受不可重複讀 | 需要交易內一致快照 |
沒有絕對的好壞。RR 是安全預設;RC 是效能導向但要求開發者理解後果。
查看與設定
-- 查看當前隔離等級
SELECT @@transaction_isolation;
-- Session 級別設定
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 全域設定(新連線生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- my.cnf 永久設定
-- [mysqld]
-- transaction-isolation = READ-COMMITTED
隔離等級與 Log 的關係
| 保證 | 依賴的機制 |
|---|---|
| 原子性(回滾) | Undo Log 記錄舊值,ROLLBACK 時回復 |
| 隔離性(讀) | MVCC(Read View + Undo Log 版本鏈) |
| 隔離性(寫) | Locks(Record Lock、Gap Lock、Next-Key Lock) |
| 持久性 | Redo Log(WAL)+ Binlog(兩階段提交) |
| 一致性 | 以上所有 + 應用邏輯 |
詳見 InnoDB Logs 與 MVCC。