MySQL 的事務隔離級別有哪些?如何選擇?
在 MySQL 中,事務隔離級別用于定義多個事務在并發執行時如何隔離彼此的數據操作,從而防止數據的不一致性和各種并發問題。這篇文章,我們一起來詳細地分析它們以及在實際工作中該如何選擇。
一、事務隔離級別
MySQL 支持以下四種標準的事務隔離級別:
- 未提交讀(Read Uncommitted)
- 提交讀(Read Committed)
- 可重復讀(Repeatable Read)
- 可串行化(Serializable)
下面我們將逐一介紹每種隔離級別及其特性,并通過示例說明其具體行為。
1. 未提交讀(Read Uncommitted)
特點:
- 最低的隔離級別。
- 事務可以讀取其他事務尚未提交的數據(臟讀)。
- 可能導致臟讀、不可重復讀和幻讀。
示例:假設有兩個事務 T1 和 T2,操作同一張表 accounts,其中有一行記錄 id=1,balance=1000。
- 事務 T1:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 此時 balance 臨時為 900,但尚未提交
- 事務 T2:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- T2 讀取到 balance = 900(未提交)
如果 T1 最終回滾操作,T2 則讀取到了一個不存在的中間狀態(臟讀)。
2. 提交讀(Read Committed)
特點:
- 事務只能讀取已經提交的數據,防止臟讀。
- 不保證同一事務中的多次讀取一致,可能出現不可重復讀。
- 允許幻讀。
示例:
- 事務 T1:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 提交后,balance = 900
- 事務 T2:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 讀取到 balance = 900
-- 如果 T1 之前已提交,那么 T2 讀取到的是最新值
如果 T1 在 T2 的兩個讀取之間進行了提交,T2 會兩次讀取到不同的值(可重復讀被破壞)。
3. 可重復讀(Repeatable Read)
特點:
- 默認的隔離級別(InnoDB 默認)。
- 保證同一事務中的多次讀取結果一致,防止臟讀和不可重復讀。
- 通過多版本并發控制(MVCC)實現,避免幻讀大部分情況。
- 在 InnoDB 中,使用 Next-Key Lock 機制防止幻讀。
示例:
- 事務 T1:
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1; -- 假設讀取到 balance = 1000
-- 執行其他操作
SELECT balance FROM accounts WHERE id = 1; -- 仍然讀取到 balance = 1000
COMMIT;
- 事務 T2:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
在 T1 的整個事務期間,雖然 T2 修改了 balance 并提交,但 T1 在再次讀取時仍然看到的是最初的 balance = 1000,保證了可重復讀。
注: 在 InnoDB 中,引入了 Next-Key Lock,可以防止新的行被插入,從而避免幻讀。例如:
- 事務 T1:
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 500; -- 讀取所有 balance > 500 的行
-- 執行其他操作
SELECT * FROM accounts WHERE balance > 500; -- 仍然讀取相同的行集,沒有幻讀
COMMIT;
- 事務 T2:
START TRANSACTION;
INSERT INTO accounts (id, balance) VALUES (2, 600);
COMMIT;
由于 Next-Key Lock 的存在,T2 無法在 T1 的事務期間插入 balance > 500 的新記錄,避免了幻讀。
4. 可串行化(Serializable)
特點:
- 最高的隔離級別。
- 強制事務串行執行,仿佛按順序一個接一個地執行。
- 防止臟讀、不可重復讀和幻讀。
- 可能導致性能下降和更高的鎖爭用。
示例:
- 事務 T1:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 500 FOR UPDATE; -- 加鎖,防止其他事務修改或插入
-- 執行其他操作
COMMIT;
- 事務 T2:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 500 FOR UPDATE; -- 如果 T1 正在執行,T2 會等待或阻塞
COMMIT;
在這種隔離級別下,事務 T2 必須等待 T1 完成后才能執行,確保事務的串行執行,避免所有并發問題。
二、如何選擇?
選擇合適的事務隔離級別需要在數據一致性和系統性能之間進行權衡。一般情況下,Repeatable Read 是一個良好的默認選擇,提供了較好的數據一致性和性能平衡。這里我們給出一些常見的使用建議。
(1) 大部分 Web 應用
- 推薦隔離級別:可重復讀(Repeatable Read)
- 原因:提供良好的數據一致性,防止臟讀和不可重復讀,同時在 InnoDB 中通過 Next-Key Lock 減少幻讀問題,適合大多數場景。
(2) 高并發讀操作且數據一致性要求不高
- 推薦隔離級別:提交讀(Read Committed)
- 原因:提高讀操作的并發性,避免長時間持有讀鎖,適合需要高吞吐量但對一致性要求略低的應用。
(3) 分析型或報告系統
- 推薦隔離級別:提交讀(Read Committed) 或 不可重復讀(視具體需求而定)
- 原因:報告和分析操作通常對數據一致性要求不如事務性的寫操作嚴格,可以接受一定程度的數據變化,以提高查詢性能。
(4) 金融交易或庫存管理等關鍵業務
- 推薦隔離級別:可串行化(Serializable)
- 原因:確保最高的數據一致性,防止所有并發問題,但應注意可能帶來的性能開銷。適用于對數據準確性要求極高的場景。
(5) 只進行寫操作且需要避免寫沖突
- 推薦隔離級別:可串行化(Serializable) 或 可重復讀(Repeatable Read)
- 原因:避免寫操作之間的沖突,確保數據完整性。
三、總結
本文,我們分析了 MySQL的 4種標準的事務隔離級別,它們用于定義多個事務在并發執行時如何隔離彼此的數據操作,從而防止數據的不一致性和各種并發問題。最后,我們用一張圖表來分析每種隔離級別的行為:
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 特點描述 |
未提交讀 | ?? | ?? | ?? | 最低隔離級別,允許讀取未提交的數據 |
提交讀 | ? | ?? | ?? | 防止臟讀,允許不可重復讀和幻讀 |
可重復讀 | ? | ? | 部分 | 防止臟讀和不可重復讀,InnoDB 防止大部分幻讀 |
可串行化 | ? | ? | ? | 最高隔離級別,所有事務串行執行 |