首页 / MySQL 入门教程 / 事务隔离级别与并发问题

MySQL 入门教程

事务隔离级别与并发问题

本教程共 46 篇 · 第 38 篇 · 更新于 2026-07-30 · 约 14 分钟阅读

MySQLMySQL 入门教程事务隔离脏读幻读MVCCREAD COMMITTEDREPEATABLE READ

38. 事务隔离级别与并发问题

本节目标:搞清楚脏读、不可重复读、幻读三种并发问题的表现和原因,掌握四种隔离级别的区别和选择,理解 MVCC 多版本并发控制的基本原理。

38.1 并发事务的问题

当多个事务同时对同一份数据操作时,如果不做隔离,就会出问题。MySQL 定义了三种经典的并发问题。

脏读(Dirty Read)

事务 A 读到了事务 B 尚未提交的修改。如果 B 后来回滚了,A 读到的就是”脏”数据。

时间线    事务A                  事务B
  1                              BEGIN;
  2                              UPDATE users SET age = 99 WHERE id = 1;
  3     SELECT age FROM users WHERE id = 1;
       -- 读到了 age = 99(B 还没提交)
  4                              ROLLBACK;
  5     -- age 实际还是原值,A 之前读到的 99 是脏数据

脏读是最严重的问题,读到的是根本不存在的数据。

不可重复读(Non-repeatable Read)

事务 A 两次读同一行数据,结果不一样,因为事务 B 在中间修改并提交了。

时间线    事务A                  事务B
  1     BEGIN;
  2     SELECT age FROM users WHERE id = 1;
       -- age = 25
  3                              BEGIN;
  4                              UPDATE users SET age = 30 WHERE id = 1;
  5                              COMMIT;
  6     SELECT age FROM users WHERE id = 1;
       -- age = 30(和上次读的不一样了)
  7     COMMIT;

和脏读的区别:脏读读到的是未提交的数据,不可重复读读到的是已提交的数据。但问题在于,事务 A 在同一个事务里两次读同一行结果不一致,破坏了”可重复读”的预期。

幻读(Phantom Read)

事务 A 两次执行同一条件查询,结果集行数不一样,因为事务 B 在中间插入并提交了新行。

时间线    事务A                  事务B
  1     BEGIN;
  2     SELECT * FROM users WHERE age > 20;
       -- 假设查到 3 行
  3                              BEGIN;
  4                              INSERT INTO users(username, age) VALUES('new', 25);
  5                              COMMIT;
  6     SELECT * FROM users WHERE age > 20;
       -- 查到 4 行(多了一条"幻影"行)
  7     COMMIT;

和不可重复读的区别:不可重复读是同一行数据被改了,幻读是行数变了(有新行插入或旧行删除)。

三种问题对比

问题原因读到的是严重程度
脏读读未提交的修改不存在的脏数据最严重
不可重复读读已提交的修改已提交但前后不一致中等
幻读读已提交的新插入/删除行数变化较轻

38.2 四种隔离级别

SQL 标准定义了四种隔离级别,级别越高隔离性越强,但并发性能越低。

隔离级别脏读不可重复读幻读性能
READ UNCOMMITTED(读未提交)可能可能可能最高
READ COMMITTED(读提交)防止可能可能
REPEATABLE READ(可重复读)防止防止可能*
SERIALIZABLE(串行化)防止防止防止最低

*MySQL InnoDB 的 REPEATABLE READ 级别通过临键锁(Next-key Lock)也能防止幻读,这是 InnoDB 的增强实现,不是 SQL 标准的要求。

MySQL 默认隔离级别是 REPEATABLE READ(可重复读)。

-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 结果:REPEATABLE-READ

-- MySQL 8.0 之前用 @@tx_isolation(已废弃)

设置隔离级别

-- 设置全局隔离级别
SET GLOBAL transaction_isolation = 'READ-COMMITTED';

-- 设置当前会话隔离级别
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 只对下一个事务生效
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
设置范围语法生效范围
全局SET GLOBAL所有新连接
会话SET SESSION当前连接
单事务SET TRANSACTION下一个事务

38.3 各级别详解

READ UNCOMMITTED(读未提交)

最低级别,事务可以读到其他事务未提交的数据。基本不用,了解即可。

READ COMMITTED(读提交)

只能读到其他事务已提交的数据。防止了脏读,但不可重复读和幻读仍会发生。

Oracle 和 PostgreSQL 默认用这个级别。

-- 演示不可重复读
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 事务 A
BEGIN;
SELECT age FROM users WHERE id = 1;  -- 假设 age = 25

-- 事务 B(另一个会话)
BEGIN;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;

-- 事务 A 再查
SELECT age FROM users WHERE id = 1;  -- age = 30,和之前不一样
COMMIT;

REPEATABLE READ(可重复读)— MySQL 默认

同一事务内多次读同一行,结果一致。InnoDB 通过 MVCC 实现这一点。

-- 事务 A
SET SESSION transaction_isolation = 'REPEATABLE-READ';
BEGIN;
SELECT age FROM users WHERE id = 1;  -- age = 25

-- 事务 B
BEGIN;
UPDATE users SET age = 30 WHERE id = 1;
COMMIT;

-- 事务 A 再查
SELECT age FROM users WHERE id = 1;  -- age = 25,没变!
COMMIT;
Note

InnoDB 在 REPEATABLE READ 下不仅防止了不可重复读,还通过临键锁防止了幻读。这是 InnoDB 比标准 SQL 做得更严格的地方。下一节锁机制会详细讲临键锁。

SERIALIZABLE(串行化)

最高级别,所有事务串行执行,完全隔离。性能最差,基本不用于生产环境。

SET SESSION transaction_isolation = 'SERIALIZABLE';
-- 此时所有 SELECT 会自动加共享锁,其他事务无法修改被查的行

38.4 MVCC 简介

MVCC(Multi-Version Concurrency Control,多版本并发控制) 是 InnoDB 实现高并发读写的核心机制。

核心思想

每行数据维护多个版本。事务读取时,根据隔离级别选择”看哪个版本”,不需要加锁就能读到一致的数据。

实现要素

1. 隐藏列

InnoDB 给每行数据加了两个隐藏列:

  • DB_TRX_ID:最后修改这行的事务 ID;
  • DB_ROLL_PTR:指向 undo log 中该行的历史版本。

2. undo log 版本链

每次修改一行数据,旧版本被记录在 undo log 中,通过 DB_ROLL_PTR 串成一条链。这就是这行数据的”历史版本链”。

当前数据行 (trx_id=200) 
  -> undo log: 旧版本 (trx_id=150)
    -> undo log: 更旧版本 (trx_id=100)

3. Read View(读视图)

事务执行查询时,会创建一个 Read View,记录”当前还有哪些事务活跃”。然后沿着版本链找,找到第一个对当前事务可见的版本。

  • READ COMMITTED:每次 SELECT 都创建新的 Read View,所以能看到最新已提交的数据;
  • REPEATABLE READ:事务第一次 SELECT 时创建 Read View,之后一直复用,所以看到的数据永远是事务开始时的快照。
REPEATABLE READ 下:
事务开始 -> 创建 Read View(记录当前活跃事务列表)
每次查询 -> 用同一个 Read View 判断版本可见性
结果 -> 整个事务期间看到的数据一致
Tip

MVCC 只在普通 SELECT(快照读)中生效。UPDATE、DELETE、SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE 这些是当前读,直接读最新数据并加锁,不走 MVCC。

快照读 vs 当前读

类型说明语句
快照读读 MVCC 版本链中的可见版本普通 SELECT
当前读读最新数据并加锁SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEINSERTUPDATEDELETE
-- 快照读:走 MVCC,不加锁
SELECT * FROM users WHERE id = 1;

-- 当前读:读最新数据,加行锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;

38.5 隔离级别选择建议

场景建议级别原因
一般业务REPEATABLE READ(默认)MySQL 默认,兼顾一致性和性能
高并发、允许不一致READ COMMITTED锁冲突少,并发高
严格一致性要求SERIALIZABLE完全隔离,但性能差
Note

大多数互联网公司用默认的 REPEATABLE READ。少数对并发要求极高的场景(如秒杀)会用 READ COMMITTED 减少锁冲突。SERIALIZABLE 几乎不会在生产中使用。

38.6 小结

  • 三种并发问题:脏读(读未提交)、不可重复读(同一行前后不一致)、幻读(行数变化);
  • 四种隔离级别从低到高:READ UNCOMMITTED -> READ COMMITTED -> REPEATABLE READ -> SERIALIZABLE;
  • MySQL 默认 REPEATABLE READ,InnoDB 在此级别下也能防止幻读;
  • MVCC 通过版本链和 Read View 实现快照读,不加锁也能保证一致性读;
  • RC 每次查询创建新 Read View,RR 整个事务复用一个 Read View;
  • 快照读走 MVCC,当前读加锁读最新数据。

下一节讲锁机制,深入理解 InnoDB 的行锁、间隙锁和死锁。