事務隔離 —— 為什么你改了我還看不見?
在數據庫中,事務(Transaction) 用于保證一組數據庫操作要么全部成功,要么全部失敗,這是一種原子性的操作機制。在 MySQL 中,事務的支持由存儲引擎層實現。但并非所有引擎都支持事務,比如 MySQL 原生的 MyISAM 引擎就不支持事務,而 InnoDB 引擎 則是主流的支持事務的引擎。
一、事務的四大特性(ACID)
特性 | 含義 |
---|---|
原子性(Atomicity) | 事務中包含的所有操作要么全部成功,要么全部失敗 |
一致性(Consistency) | 事務執行前后,數據庫都處于一致狀態 |
隔離性(Isolation) | 并發執行的事務之間彼此隔離,互不影響 |
持久性(Durability) | 事務一旦提交,對數據庫的修改就是永久性的,即使系統故障也不會丟失 |
二、隔離性與隔離級別
當多個事務并發執行時,若沒有隔離,就可能導致以下問題:
- 臟讀(Dirty Read):讀取到未提交事務的數據
- 不可重復讀(Non-repeatable Read):同一事務中多次讀取同一數據,值卻不同
- 幻讀(Phantom Read):同一事務中兩次查詢返回的記錄數不同
為解決這些問題,SQL 標準定義了四種隔離級別:
1. 四大隔離級別說明
隔離級別 | 描述 | 會發生的問題 |
---|---|---|
Read Uncommitted(讀未提交) | 可以讀取未提交事務修改的數據 | 臟讀、不可重復讀、幻讀 |
Read Committed(讀已提交) | 只能讀取已提交事務修改的數據 | 不可重復讀、幻讀 |
Repeatable Read(可重復讀) | 同一事務內多次讀取結果一致(MySQL 默認) | 幻讀(依賴間隙鎖解決) |
Serializable(可串行化) | 強制事務串行執行,防止并發 | 無臟讀/不可重復讀/幻讀,但效率低 |
注意:隔離級別越高,性能越差。需要根據業務需求做權衡。
2. 示例說明(圖示說明略,可自己繪圖添加)
假設有兩個事務 A 和 B,對某行數據進行讀寫操作:
- 在 讀未提交 下,事務 A 會看到事務 B 的未提交修改
- 在 讀已提交 下,A 只能看到 B 已提交的修改
- 在 可重復讀 下,A 在整個事務中看到的始終是開始時的數據快照
- 在 串行化 下,B 的寫操作會被阻塞,直到 A 完成
3. MySQL 中隔離級別的實現方式
- Read Uncommitted:直接讀取最新數據,無視事務
- Read Committed:每次 SQL 執行前創建一個視圖(Read View)
- Repeatable Read:在事務開始時創建視圖,期間一直使用同一個視圖
- Serializable:加鎖控制并發,防止沖突
在 MySQL 中,默認隔離級別為 Repeatable Read,但 Oracle 默認是 Read Committed。
查詢當前隔離級別
SHOW VARIABLES LIKE 'transaction_isolation';
三、事務隔離的實現機制(MVCC)
以“可重復讀”為例,MySQL 使用 多版本并發控制(MVCC) 來實現事務隔離。
1. 回滾日志機制
- 每條記錄在更新時都會生成一條回滾日志(undo log)
- 當前值可以通過逐步回滾恢復到歷史版本
- 不同事務通過自己的視圖(Read View)看到的版本可能不同
例如,某值從 1 → 2 → 3 → 4,回滾日志如下:
當前值 | 回滾日志 |
---|---|
4 | 3、2、1 |
視圖 A 啟動于值為 1 時,則通過回滾可看到 1;視圖 B 則可能看到 2。
2. MVCC 的優點
- 允許讀取操作不加鎖,提升并發性能
- 實現隔離級別無需加重鎖負擔
3. 回滾日志的回收問題
- 回滾日志會在無事務再引用時被清除
- 長事務 會阻止日志清理,導致存儲膨脹
實例:某系統數據僅 20GB,但 undo 日志達 200GB,最終只能重建數據庫解決。
四、事務的啟動方式及建議
1. 顯式啟動事務
BEGIN;
-- 或
START TRANSACTION;
-- 提交或回滾
COMMIT;
ROLLBACK;
2. 隱式啟動方式:autocommit=0
SET autocommit = 0;
- 此模式下,即使執行一個簡單的
SELECT
,也會自動開啟事務 - 若未手動
COMMIT
或ROLLBACK
,事務將一直占用資源,形成 隱性長事務
建議
- 使用
SET autocommit=1
(默認),通過顯式語句控制事務 - 使用
COMMIT WORK AND CHAIN
可在提交后立即開啟下一個事務
-- 查看運行超過60秒的事務
SELECT *
FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
五、總結
- 事務隔離是數據庫并發控制的核心機制
- 在 MySQL 中,推薦使用默認的 Repeatable Read + MVCC 實現事務隔離
- 盡量避免隱式長事務,主動控制事務生命周期
- 根據實際業務權衡性能與隔離性的需求