导航菜单

事务隔离级别

🔴 困难

题目描述

解释 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;

搜索