數據庫隔離級別并非安裝后就固定,絕大多數主流數據庫(如MySQL、PostgreSQL、SQL Server)都支持動態調整和運行中自定義,具體調整范圍可分為全局、會話和語句三個層級。
- 全局級別調整:修改數據庫配置文件(如MySQL的my.cnf)并重啟服務,會影響所有新創建的會話,屬于長期生效的配置。
- 會話級別調整:在當前數據庫連接中執行特定SQL命令(如MySQL的
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
),僅對當前會話生效,關閉連接后失效,適合臨時切換隔離級別。 - 語句級別調整:部分數據庫支持為單個事務語句指定隔離級別(如SQL Server的
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN TRANSACTION; ... COMMIT;
),僅對該次事務生效,靈活性最高。
不同數據庫的具體調整語法略有差異,但核心邏輯均支持動態修改,無需重新安裝數據庫。
要模擬MySQL 5.7中事務并發的臟讀、不可重復讀、幻讀,需先創建測試表和基礎數據,再通過「兩個會話模擬并發事務」,結合不同隔離級別驗證問題及解決辦法。以下是完整步驟:
一、基礎準備:創建表與初始化數據
1. 創建測試表(用戶余額表)
-- 建表:id(主鍵)、user_id(用戶ID)、balance(余額)
CREATE TABLE `user_balance` (`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`user_id` INT(11) NOT NULL COMMENT '用戶ID',`balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '賬戶余額',PRIMARY KEY (`id`),UNIQUE KEY `idx_user_id` (`user_id`) -- 唯一索引,確保用戶ID不重復
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶余額表';
2. 插入初始化數據
-- 插入1條測試數據:用戶ID=1001,初始余額1000元
INSERT INTO user_balance (user_id, balance) VALUES (1001, 1000.00);-- 驗證數據
SELECT * FROM user_balance WHERE user_id = 1001;
二、核心概念:MySQL隔離級別與并發問題
MySQL 5.7默認隔離級別是 REPEATABLE READ(可重復讀),不同隔離級別對并發問題的抑制能力不同:
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
READ UNCOMMITTED(讀未提交) | 允許 | 允許 | 允許 |
READ COMMITTED(讀已提交) | 禁止 | 允許 | 允許 |
REPEATABLE READ(可重復讀) | 禁止 | 禁止 | 禁止(InnoDB通過MVCC實現) |
SERIALIZABLE(串行化) | 禁止 | 禁止 | 禁止 |
模擬規則:需打開「兩個MySQL會話」(如Navicat的兩個查詢窗口、CMD的兩個mysql連接),分別執行「事務A」和「事務B」,按步驟操作。
三、場景1:臟讀(Dirty Read)
什么是臟讀?
事務A讀取了事務B未提交的修改數據,若事務B后續回滾,事務A讀取的就是“無效臟數據”。
1. 模擬臟讀(需先設置隔離級別為「READ UNCOMMITTED」)
步驟1:兩個會話均設置隔離級別
-- 會話1、會話2均執行:設置當前會話隔離級別為讀未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
步驟2:開啟事務并執行操作(按順序執行)
步驟 | 會話1(事務A:查詢用戶余額) | 會話2(事務B:修改用戶余額但不提交) |
---|---|---|
1 | BEGIN; (開啟事務)SELECT balance FROM user_balance WHERE user_id=1001; – 結果:1000.00 | - |
2 | - | BEGIN; (開啟事務)UPDATE user_balance SET balance=balance-200 WHERE user_id=1001; – 不執行COMMIT(事務未提交) |
3 | SELECT balance FROM user_balance WHERE user_id=1001; – 結果:800.00(讀取到事務B未提交的修改,臟讀發生!) | - |
4 | - | ROLLBACK; (事務B回滾,修改作廢) |
5 | SELECT balance FROM user_balance WHERE user_id=1001; – 結果:1000.00(數據恢復,驗證步驟3讀的是臟數據) | - |
6 | COMMIT; (關閉事務A) | - |
2. 解決臟讀:提升隔離級別至「READ COMMITTED」及以上
-- 兩個會話均設置隔離級別為讀已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;-- 重復上述步驟2,會發現:步驟3中會話1讀取的余額仍為1000.00(事務B未提交的修改不可見),臟讀被禁止。
四、場景2:不可重復讀(Non-Repeatable Read)
什么是不可重復讀?
事務A在同一事務內多次讀取同一數據,若事務B在兩次讀取間「提交了修改」,則事務A兩次讀取的結果不一致。
1. 模擬不可重復讀(需設置隔離級別為「READ COMMITTED」)
步驟1:兩個會話均設置隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
步驟2:開啟事務并執行操作(按順序執行)
步驟 | 會話1(事務A:多次查詢同一用戶余額) | 會話2(事務B:修改并提交用戶余額) |
---|---|---|
1 | BEGIN; (開啟事務)SELECT balance FROM user_balance WHERE user_id=1001; – 結果:1000.00 | - |
2 | - | BEGIN; (開啟事務)UPDATE user_balance SET balance=balance-200 WHERE user_id=1001; COMMIT; (提交事務,修改生效) |
3 | SELECT balance FROM user_balance WHERE user_id=1001; – 結果:800.00(與步驟1結果不一致,不可重復讀發生!) | - |
4 | COMMIT; (關閉事務A) | - |
2. 解決不可重復讀:提升隔離級別至「REPEATABLE READ」及以上
-- 兩個會話均設置隔離級別為可重復讀(MySQL默認級別)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;-- 重復上述步驟2,會發現:步驟3中會話1讀取的余額仍為1000.00(事務B提交的修改對事務A不可見),不可重復讀被禁止。
五、場景3:幻讀(Phantom Read)
什么是幻讀?
事務A在同一事務內按同一條件多次查詢,若事務B在兩次查詢間「提交了新數據插入/刪除」,則事務A兩次查詢的「結果行數不一致」(像出現了“幻覺”)。
1. 模擬幻讀(需設置隔離級別為「READ COMMITTED」,MySQL默認的REPEATABLE READ已禁止幻讀)
步驟1:兩個會話均設置隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
步驟2:開啟事務并執行操作(按順序執行)
步驟 | 會話1(事務A:按條件多次查詢用戶) | 會話2(事務B:插入新用戶并提交) |
---|---|---|
1 | BEGIN; (開啟事務)SELECT COUNT(*) FROM user_balance WHERE user_id > 1000; – 結果:1(僅user_id=1001) | - |
2 | - | BEGIN; (開啟事務)INSERT INTO user_balance (user_id, balance) VALUES (1002, 1500.00); COMMIT; (提交事務,新用戶插入生效) |
3 | SELECT COUNT(*) FROM user_balance WHERE user_id > 1000; – 結果:2(新增了user_id=1002,行數不一致,幻讀發生!) | - |
4 | COMMIT; (關閉事務A) | - |
2. 解決幻讀:使用「REPEATABLE READ」或「SERIALIZABLE」隔離級別
-- 兩個會話均設置隔離級別為可重復讀(MySQL默認)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;-- 重復上述步驟2,會發現:步驟3中會話1查詢的COUNT(*)仍為1(事務B插入的新數據對事務A不可見),幻讀被禁止。-- 若用SERIALIZABLE級別:會話2插入數據時會被阻塞,直到會話1提交事務,徹底避免幻讀(但性能損耗大)。
六、關鍵總結
- 問題本質:并發事務對數據的「修改/插入」與「讀取」的時序沖突,隔離級別通過控制數據可見性解決沖突。
- MySQL默認隔離級別:REPEATABLE READ,已能禁止臟讀、不可重復讀、幻讀(InnoDB的MVCC機制實現),兼顧性能與一致性。
- 語法記憶:
- 查看當前會話隔離級別:
SELECT @@tx_isolation;
(MySQL 5.7)/SELECT @@transaction_isolation;
(MySQL 8.0+) - 設置會話隔離級別:
SET SESSION TRANSACTION ISOLATION LEVEL 級別名稱;
- 開啟/提交/回滾事務:
BEGIN;
/COMMIT;
/ROLLBACK;
- 查看當前會話隔離級別:
在 MySQL 5.7 配置文件中,用于設置事務默認隔離級別的參數是 transaction_isolation
(或舊版兼容參數 tx_isolation
,兩者功能一致,推薦使用 transaction_isolation
)。
1. 參數說明
- 核心作用:定義 MySQL 實例啟動后,所有新創建會話的默認事務隔離級別,無需在每個會話中手動設置。
- 參數值(對應 4 種隔離級別):
READ-UNCOMMITTED
:讀未提交(可能出現臟讀、不可重復讀、幻讀)READ-COMMITTED
:讀已提交(避免臟讀,可能出現不可重復讀、幻讀)REPEATABLE-READ
:可重復讀(MySQL 5.7 默認級別,避免臟讀、不可重復讀,通過 MVCC 減少幻讀)SERIALIZABLE
:串行化(完全避免三種問題,性能最低)
2. 配置方式(永久生效)
- 找到 MySQL 5.7 的配置文件(路徑因系統而異):
- Linux:通常為
/etc/my.cnf
或/etc/mysql/my.cnf
- Windows:通常為
MySQL安裝目錄/my.ini
- Linux:通常為
- 在
[mysqld]
模塊下添加/修改參數:[mysqld] # 設置默認事務隔離級別為可重復讀(MySQL 5.7 默認值,可根據需求修改) transaction_isolation = REPEATABLE-READ
- 重啟 MySQL 服務使配置生效:
- Linux:
systemctl restart mysqld
- Windows:在“服務”中重啟“MySQL”服務
- Linux:
3. 臨時生效方式(當前會話/全局)
若無需永久修改,可通過 SQL 語句臨時設置(重啟服務后失效):
- 當前會話生效:
SET SESSION transaction_isolation = 'READ-COMMITTED';
- 全局生效(對新會話生效,已存在會話不影響):
SET GLOBAL transaction_isolation = 'SERIALIZABLE';
4. 驗證隔離級別
通過以下 SQL 查看當前生效的隔離級別:
-- 查看當前會話的隔離級別
SELECT @@session.transaction_isolation;-- 查看全局的隔離級別
SELECT @@global.transaction_isolation;