隔离级别的汇总

0 阅读7分钟

隔离级别总览表

隔离级别脏读 (Dirty Read)不可重复读 (Non-Repeatable Read)幻读 (Phantom Read)普通 SELECT 是否加锁Read View 创建时机并发性能MySQL 默认?主要实现机制
READ UNCOMMITTED允许允许允许不加锁不使用 Read View最高直接读最新数据(无 MVCC)
READ COMMITTED (RC)不允许允许允许不加锁每次 SELECT 都新建 Read ViewMVCC + 每次刷新快照
REPEATABLE READ (RR)不允许不允许MySQL 中基本不允许不加锁(一致性读)事务内第一次 SELECT 时创建一次,全程复用中等偏高是(默认)MVCC + Next-Key Lock(间隙锁)
SERIALIZABLE不允许不允许彻底不允许加锁(隐式共享锁)同 RR(第一次 SELECT 创建)最低MVCC + 共享锁 + 范围间隙锁

脏读、不可重复读、幻读?

  1. 脏读(Dirty Read)定义:
    一个事务读取到了另一个事务尚未提交(未 COMMIT) 的数据。例子:
  • 事务 A:把账户余额从 1000 修改为 1500,但还没有 COMMIT。
  • 事务 B:此时去查询该账户余额,看到的是 1500。
  • 之后事务 A 执行了 ROLLBACK(回滚),余额又变回 1000。
  • 结果:事务 B 读到的 1500 是脏数据(从来没真正存在过)。

后果:非常危险!可能导致业务逻辑错误(如多发货、重复扣款等)。哪个隔离级别允许脏读?
只有 READ UNCOMMITTED 允许脏读。其他三个隔离级别(RC、RR、SERIALIZABLE)都不允许脏读。


  1. 不可重复读(Non-Repeatable Read)定义:
    同一个事务内,两次读取同一行数据,结果却不一样。例子:
  • 事务 A 开始:

    • 第一次查询:账户余额 = 1000
  • 这时事务 B 把余额修改为 1500 并 COMMIT 了

  • 事务 A 第二次查询同一行:余额变成了 1500

关键特点:

  • 针对的是同一行数据被修改(UPDATE/DELETE)。
  • 读到的数据是别人已经提交的(不是脏读)。

哪个隔离级别会发生?

  • READ COMMITTED:允许不可重复读
  • REPEATABLE READ:不允许(MySQL 默认级别已解决)
  • SERIALIZABLE:也不允许

  1. 幻读(Phantom Read)定义:
    同一个事务内,两次执行相同的范围查询,第二次查询却多出来(或少掉)一些行,像出现了“幻影”一样。例子:
  • 事务 A 开始:

    • 查询 SELECT * FROM orders WHERE user_id = 1; → 返回 5 条记录
  • 这时事务 B 插入了一条 user_id = 1 的新订单,并 COMMIT

  • 事务 A 再次执行完全相同的查询:→ 返回 6 条记录

虽然每行数据本身没变,但结果集的行数发生了变化,这就是幻读。幻读 vs 不可重复读 的区别:

  • 不可重复读:同一行数据前后不一致(UPDATE/DELETE)
  • 幻读:范围查询的结果集前后不一致(主要是 INSERT)

哪个隔离级别会发生?

  • READ COMMITTED:允许幻读
  • REPEATABLE READ:MySQL 通过 Next-Key Lock(间隙锁),很大程度上防止了幻读(不是 100% 杜绝所有情况)
  • SERIALIZABLE:彻底防止幻读(会加范围锁)

总结对比表(最清晰版)

含义针对的对象READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ(默认)SERIALIZABLE
脏读读到别人还没提交的数据任何数据允许不允许不允许不允许
不可重复读同一行数据,两次读结果不同同一行(UPDATE)允许允许不允许不允许
幻读范围查询结果集,两次查询行数不同范围(INSERT)允许允许基本不允许不允许

为什么 READ COMMITTED 就能解决脏读?

  • 在这个级别下,InnoDB 会保证普通 SELECT 只读取其他事务已提交的版本。

  • 未提交的修改(即使别人正在改)对你来说是不可见的。

  • 它通过 MVCC(多版本并发控制) + Read View 机制实现。

RR 如何解决不可重复读?

核心机制:MVCC + Read View(一致性快照)具体实现方式如下:

  1. Read View 只创建一次

    • 当事务开始后,执行第一次普通 SELECT(快照读)时,MySQL 会创建一个 Read View(读视图/一致性快照)。

    • 这个 Read View 会记录:

      • 当前活跃(未提交)事务的 ID 列表(m_ids)
      • 最小活跃事务 ID(up_limit_id / min_trx_id)
      • 下一个事务 ID(low_limit_id / max_trx_id)
      • 当前事务自己的 ID
  2. 整个事务期间复用同一个 Read View

    • 后续的所有普通 SELECT(不加 FOR UPDATE 的查询),都使用同一个 Read View 来判断数据可见性。
    • 即使中间有其他事务修改了数据并提交了,当前事务也始终按照第一次创建 Read View 时的快照来读取数据。
  3. 通过版本链查找可见版本

    • 每行数据都有隐藏的 trx_id(事务 ID)和 roll_ptr(指向 Undo Log 的指针),形成版本链。
    • 查询时从最新版本开始,沿着版本链往下找,判断哪个版本对当前 Read View 是可见的。
    • 因为 Read View 不变,所以每次找到的可见版本都是同一个 → 结果一致。

SERIALIZABLE 怎么解决幻读?

核心机制:MVCC + 隐式加锁(尤其是范围锁 / 间隙锁)具体做法是:

  1. 依然使用 MVCC + Read View(和 RR 一样的基础)

    • 普通 SELECT 仍然走一致性读(快照读)。
  2. 最关键的加强措施 —— 隐式加共享锁(Shared Lock)

    • 在 SERIALIZABLE 隔离级别下,InnoDB 会自动把所有普通 SELECT 语句隐式转换为 SELECT ... FOR SHARE(共享锁,旧版本叫 LOCK IN SHARE MODE)。
    • 这意味着:即使你写的是最普通的 SELECT,MySQL 也会给你查询到的行加上共享锁(S 锁)。
  3. 范围锁(Gap Lock + Next-Key Lock)

    • 当你执行范围查询(带 WHERE 条件)时,InnoDB 不仅锁住已存在的行,还会锁住查询范围内的间隙(Gap)。
    • 其他事务无法在被锁定的间隙中插入新行,也无法修改或删除已锁定的行。
    • 这就彻底阻止了“新行插入”导致的幻读。

结果:其他事务想插入、修改、删除影响当前事务查询结果的数据时,都会被阻塞(等待当前事务 COMMIT 或 ROLLBACK),从而让并发事务看起来像一个接一个串行执行一样。实际例子对比场景:

  • 事务 A:SELECT * FROM orders WHERE user_id = 1;(查询用户 1 的订单,第一次返回 5 条)
  • 事务 B:尝试插入一条 user_id = 1 的新订单

在 REPEATABLE READ 下:

  • 事务 A 的查询通常能防止大部分幻读,但某些复杂条件下仍可能出现新行插入(幻读)。

在 SERIALIZABLE 下:

  • 事务 A 执行 SELECT 时,自动加上共享锁 + 范围间隙锁。
  • 事务 B 的 INSERT 操作会被阻塞(等待事务 A 结束)。
  • 事务 A 第二次查询时,仍然看到 5 条记录(没有幻影出现)。
  • 只有事务 A COMMIT 或 ROLLBACK 后,事务 B 的 INSERT 才能执行。

SERIALIZABLE 的优缺点优点:

  • 彻底解决脏读、不可重复读、幻读(隔离性最强)。
  • 事务最终效果等同于串行执行,数据一致性最高。

缺点(非常明显):

  • 并发性能最差 —— 读也加锁,大量锁等待和死锁风险。
  • 容易导致性能瓶颈,不适合高并发系统。
  • 只在对一致性要求极高、并发量很低的场景使用(如金融核心系统、XA 分布式事务等)。

如何设置 SERIALIZABLE

sql

-- 当前事务设置
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 当前会话设置
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 查看当前隔离级别
SELECT @@transaction_isolation;

生产建议:

  • 大多数业务用 REPEATABLE READ(默认)就足够了。
  • 只有当业务明确要求完全杜绝幻读且能接受性能损失时,才使用 SERIALIZABLE。

一句话总结:

SERIALIZABLE 通过 MVCC(快照读) + 隐式把普通 SELECT 转为加共享锁 + 范围间隙锁,彻底锁住查询范围,阻止其他事务插入/修改影响结果的数据,从而完全解决幻读。