事务隔离级别
🔴 困难题目描述
解释 MySQL 的四种事务隔离级别,以及它们分别解决什么并发问题?
示例场景
-- 事务 A
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 B
START TRANSACTION;
SELECT balance FROM users WHERE id = 1;
-- 读到的是扣钱后的值吗?
COMMIT;
-- 事务 A
COMMIT;提示
- 四种隔离级别:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE
- 三种并发问题:脏读、不可重复读、幻读
- MySQL 默认隔离级别:REPEATABLE READ
解法
参考答案 (3 个标签)
隔离级别 并发问题 ACID
四种隔离级别
1. 读未提交(READ UNCOMMITTED)
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 事务 A
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 B
START TRANSACTION;
SELECT balance FROM users WHERE id = 1;
-- 读到扣钱后的值(脏读)
-- 事务 A
ROLLBACK; -- 回滚
-- 事务 B
-- balance 是错误的(脏数据)问题:
- ✓ 脏读:读取未提交的数据
- ✓ 不可重复读
- ✓ 幻读
2. 读已提交(READ COMMITTED)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 事务 A
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 事务 B
START TRANSACTION;
SELECT balance FROM users WHERE id = 1; -- 旧值
-- 事务 A 提交
SELECT balance FROM users WHERE id = 1; -- 新值(不可重复读)
COMMIT;问题:
- ✗ 脏读
- ✓ 不可重复读
- ✓ 幻读
3. 可重复读(REPEATABLE READ)- MySQL 默认
-- 默认隔离级别
SELECT @@transaction_isolation;
-- REPEATABLE-READ
-- 事务 A
START TRANSACTION;
SELECT balance FROM users WHERE id = 1; -- 1000
-- 事务 B
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 事务 A
SELECT balance FROM users WHERE id = 1; -- 仍然是 1000(可重复读)
COMMIT;问题:
- ✗ 脏读
- ✗ 不可重复读
- ✓ 幻读(但 MySQL 通过 MVCC 和 Next-Key Lock 很大程度上解决了)
4. 串行化(SERIALIZABLE)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE age > 18;
-- 范围查询加锁
-- 事务 B
START TRANSACTION;
INSERT INTO users (name, age) VALUES ('David', 20);
-- 阻塞,等待事务 A 提交
-- 事务 A
COMMIT;
-- 事务 B
-- 继续执行问题:
- ✗ 脏读
- ✗ 不可重复读
- ✗ 幻读
性能:最低(锁范围大)
并发问题详解
1. 脏读(Dirty Read)
-- 事务 A
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
-- 未提交
-- 事务 B(READ UNCOMMITTED)
START TRANSACTION;
SELECT balance FROM users WHERE id = 1;
-- 读到扣钱后的值
-- 事务 A
ROLLBACK; -- 回滚
-- 事务 B
-- balance 是脏数据(错误)2. 不可重复读(Non-Repeatable Read)
-- 事务 A
START TRANSACTION;
SELECT balance FROM users WHERE id = 1; -- 1000
-- 事务 B
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 事务 A
SELECT balance FROM users WHERE id = 1; -- 900(不同)
COMMIT;3. 幻读(Phantom Read)
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE age > 18; -- 10 条
-- 结果:Alice(20), Bob(21), ...
-- 事务 B
START TRANSACTION;
INSERT INTO users (name, age) VALUES ('David', 25);
COMMIT;
-- 事务 A
SELECT * FROM users WHERE age > 18; -- 11 条(幻影)
COMMIT;扩展:MySQL 如何解决幻读
MVCC(多版本并发控制)
-- InnoDB 通过 MVCC 实现可重复读
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
-- 使用 Read View,读取事务开始时的版本
-- 事务 B
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 事务 A
SELECT * FROM users WHERE id = 1;
-- 仍然读取旧版本(通过 Undo Log)
COMMIT;Next-Key Lock
-- 范围查询时加锁
SELECT * FROM users WHERE age > 18 FOR UPDATE;
-- 加锁范围:
-- (18, +∞) 的所有记录
-- 加上间隙锁
-- 防止幻读如何选择隔离级别
| 场景 | 推荐级别 | 原因 |
|---|---|---|
| 默认 | REPEATABLE READ | 平衡性能和一致性 |
| 金融 | SERIALIZABLE | 强一致性 |
| 报表 | READ COMMITTED | 允许不可重复读,性能好 |
| 审计 | READ UNCOMMITTED | 极少使用 |
查看和设置隔离级别
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 查看全局隔离级别
SELECT @@global.transaction_isolation;
-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;