MySQL 事务隔离级别详解

MySQL 事务隔离级别详解

关键词:隔离级别、脏读、不可重复读、幻读、MVCC、间隙锁。
重点:搞清楚四个隔离级别分别“允许/禁止”哪些现象,以及 InnoDB 实际是怎么做的。


一、为什么需要事务隔离?

在数据库里,多个事务是并发执行的:

  • 一个事务在读数据;
  • 另一个事务在改数据;
  • 还有一个事务在插入/删除数据。

如果完全不隔离:

  • 你可能读到别人还没提交的修改(脏读);
  • 同一个事务里两次读同一行,结果却不同(不可重复读);
  • 同一个事务里两次按条件查询,前后出现的行数不一样(幻读)。

事务隔离级别就是在:

“读到最新数据” 和 “读到稳定一致的数据” 以及 “系统性能” 之间做平衡。

SQL 标准定义了 4 个隔离级别,对应不同的安全性和性能取舍。


二、三种典型并发现象

先搞清楚几个常见“坏现象”是什么。

2.1 脏读(Dirty Read)

  • 事务 A 读取到了事务 B 尚未提交 的修改;
  • 如果 B 后面回滚了,A 读到的就是“根本没生效过的假数据”。

示意:

T1: 事务 B:UPDATE account SET balance = balance - 100 WHERE id = 1;
T2: 事务 A:SELECT balance FROM account WHERE id = 1;  -- 读到了减少 100 后的值
T3: 事务 B:ROLLBACK;

此时事务 A 读到的就是“脏数据”。

2.2 不可重复读(Non-Repeatable Read)

  • 同一个事务中,两次读取同一行数据,结果不一样;
  • 原因是:两次读取之间,别的事务提交了对该行的修改。

示意:

T1: 事务 A:SELECT balance FROM account WHERE id = 1;  -- 读到 1000
T2: 事务 B:UPDATE account SET balance = 800 WHERE id = 1; COMMIT;
T3: 事务 A:再次 SELECT balance FROM account WHERE id = 1;  -- 读到 800

对于一部分业务来说,“同一个事务里两次查应该一致”是刚性需求。

2.3 幻读(Phantom Read)

  • 同一个事务中,两次按某个条件查询,结果的行数不一样
  • 原因是:两次查询之间,有别的事务插入/删除了满足条件的行。

示意:

T1: 事务 A:SELECT * FROM order WHERE amount > 100;  -- 结果 5 行
T2: 事务 B:INSERT INTO order(id, amount) VALUES(100, 200); COMMIT;
T3: 事务 A:再次 SELECT * FROM order WHERE amount > 100;  -- 结果 6 行

对于“统计 + 校验”等场景,这种“幻影数据”可能会搞乱逻辑。


三、四种事务隔离级别(SQL 标准)

SQL 标准定义了 4 个隔离级别:

  1. READ UNCOMMITTED(读未提交)
  2. READ COMMITTED(读已提交)
  3. REPEATABLE READ(可重复读)
  4. SERIALIZABLE(可串行化)

它们从“弱到强”的对应关系是:

READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE

隔离越强:

  • 读到的数据越“稳定一致”;
  • 并发性能通常越差。

3.1 READ UNCOMMITTED(读未提交)

特点:

  • 可以读到别的事务尚未提交的修改;
  • 性能最高,但数据安全性最差。

存在问题:

  • ✅ 脏读:可能;
  • ✅ 不可重复读:可能;
  • ✅ 幻读:可能。

这个隔离级别一般不会在生产中使用。


3.2 READ COMMITTED(读已提交)

特点:

  • 每一次查询都只能看到已经提交的最新数据;
  • 每次 SELECT 都会生成一个新的“视图”。

效果:

  • ❌ 脏读:被禁止;
  • ✅ 不可重复读:依然可能;
  • ✅ 幻读:依然可能。

示意:

事务 A:
  SELECT balance FROM account WHERE id = 1;  -- 第一次读

事务 B:
  UPDATE account SET balance = 800 WHERE id = 1; COMMIT;

事务 A:
  SELECT balance FROM account WHERE id = 1;  -- 第二次读,看到 800

两次读到的结果可能不同,因此称为“读已提交”,而不是“可重复读”。

在很多商业数据库(如 Oracle)中,默认隔离级别就是 READ COMMITTED。


3.3 REPEATABLE READ(可重复读)

特点:

  • 一个事务从开始到结束,看到的数据是一致的“快照”;
  • 同一事务中多次读同一行,结果保持一致
  • MySQL InnoDB 的默认隔离级别

效果:

  • ❌ 脏读:禁止;
  • ❌ 不可重复读:禁止(对同一行);
  • ✅ 幻读:按照 SQL 标准定义,理论上仍可能

但是:

InnoDB 在 REPEATABLE READ 下,配合 MVCC + 间隙锁/Next-Key Lock,在大多数场景下也能避免幻读;
只是在严格定义上,仍认为“幻读可能存在某些边界情况”。

示意:

事务 A(RR):
  第一次 SELECT * FROM t WHERE id = 1;  -- 读到旧版本

事务 B:
  UPDATE t SET ... WHERE id = 1; COMMIT;  -- 提交新值

事务 A:
  第二次 SELECT * FROM t WHERE id = 1;   -- 仍然读到第一次看到的版本

因为 A 的“视图”是在事务开始时创建的,后续读都以这个视图为准。


3.4 SERIALIZABLE(可串行化)

特点:

  • 最高隔离级别;
  • 强制所有事务看起来像是顺序执行的
  • 通常通过对读也加锁(类似范围锁/表锁)来实现。

效果:

  • ❌ 脏读:禁止;
  • ❌ 不可重复读:禁止;
  • ❌ 幻读:禁止。

代价是:

  • 并发能力大幅下降;
  • 在高并发场景下一般不建议使用。

四、MySQL InnoDB 中的实际实现细节

4.1 全局 / 会话隔离级别

查看当前隔离级别(MySQL 8.0):

SELECT @@transaction_isolation;
-- 或
SELECT @@global.transaction_isolation;

设置隔离级别:

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

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

MySQL 5.x 常用写法(已逐渐被替换):

SELECT @@tx_isolation;
SET SESSION tx_isolation = 'READ-COMMITTED';

InnoDB 默认隔离级别:REPEATABLE READ


4.2 READ COMMITTED vs REPEATABLE READ:差别在哪?

核心差别在于一致性视图(Read View)创建时机

  • READ COMMITTED:

    • 每次 SELECT 都会生成一个新的 Read View
    • 因此每次查询都能看到当前已经提交的最新版本
    • 故容易出现“不可重复读”。
  • REPEATABLE READ:

    • 在事务第一次读取数据时创建 Read View;
    • 整个事务期间都复用同一个 Read View;
    • 后续再读同一行,看到的仍然是第一次看到的版本;
    • 避免了不可重复读。

这就是 InnoDB 利用 MVCC(多版本并发控制) 做出不同隔离语义的核心。


4.3 幻读与间隙锁 / Next-Key Lock

为了控制“幻读”,InnoDB 在可重复读级别下:

  • 对范围查询 + 锁定读(SELECT ... FOR UPDATE / FOR SHARE)会加:
    • 间隙锁(Gap Lock):锁定某个区间,禁止在区间内插入新行;
    • Next-Key Lock:记录锁 + 间隙锁的组合,锁 [前一个值, 当前值] 区间。

这样:

  • 一个事务在某个范围上进行锁定读或修改;
  • 另一个事务就没法往这个范围内插入新记录;
  • 从而在多数情况下避免了“我查询某条件范围,后面新插入的记录突然冒出来”的幻读问题。

不过:

  • 对纯查询(普通 SELECT 不加锁)来说,避免幻读主要是通过 MVCC 来实现“读到一个旧快照”;
  • 对加锁读来说,则通过 Next-Key Lock + Gap Lock 尽量避免幻读。

五、各隔离级别与并发现象对照表

用一个表总览一下:

隔离级别脏读(Dirty Read)不可重复读(Non-Repeatable)幻读(Phantom)
READ UNCOMMITTED✅ 可能✅ 可能✅ 可能
READ COMMITTED❌ 禁止✅ 可能✅ 可能
REPEATABLE READ*❌ 禁止❌ 禁止✅ 理论上可能*
SERIALIZABLE❌ 禁止❌ 禁止❌ 禁止

说明:

  • 标准语义下,REPEATABLE READ 仍可能有幻读。
  • InnoDB 在 REPEATABLE READ + 间隙锁/Next-Key Lock 下,大部分实际场景中也能避免幻读。

六、怎么选隔离级别?(实践建议)

  1. 绝大多数业务:用默认的 REPEATABLE READ 就够了

    • InnoDB 默认是 REPEATABLE READ;
    • 配合 MVCC 和 Next-Key Lock,能很好平衡一致性和性能。
  2. 如果有强烈的“读最新”需求,可以考虑 READ COMMITTED

    例如:

    • 某些业务要求:一旦事务 B 提交,我的后续读立刻能看到它;
    • 不介意“同一事务内两次读同一行不一样”。
  3. SERIALIZABLE 一般只在一致性要求极端高且并发不大的系统里使用

    • 比如一些对账、结算、强一致金融场景;
    • 否则容易把并发性能打爆。
  4. READ UNCOMMITTED 几乎没有场景需要,基本不要用


七、面试/复习高频问答小抄

Q1:MySQL 默认的事务隔离级别是什么?

  • InnoDB 默认是 REPEATABLE READ(可重复读)

Q2:四个隔离级别分别解决/遗留了哪些问题?

  • READ UNCOMMITTED:什么问题都不解决,允许脏读;
  • READ COMMITTED:解决脏读;不可重复读、幻读仍存在;
  • REPEATABLE READ:解决脏读 + 不可重复读;幻读理论上仍可能;
  • SERIALIZABLE:理论上解决所有问题,但性能最差。

Q3:READ COMMITTED 和 REPEATABLE READ 的实现差别?

  • READ COMMITTED:每次 SELECT 都新建一个 Read View;
  • REPEATABLE READ:事务第一次 SELECT 时创建 Read View,后续复用;
  • 都依赖 MVCC(多版本并发控制)。

Q4:InnoDB 如何处理幻读?

  • 利用 MVCC 提供一致性视图,使得查询看到的是历史快照;
  • 配合 间隙锁(Gap Lock)Next-Key Lock,在加锁读和更新场景中尽量避免其他事务在范围内插入新行。

Q5:如何查看和设置当前隔离级别?

-- 查看
SELECT @@transaction_isolation;

-- 设置当前会话
SET SESSION transaction_isolation = 'READ-COMMITTED';

八、小结

  1. 事务隔离级别是用来控制并发读写时“看见什么”的规则集合;
  2. MySQL/InnoDB 提供四个级别:读未提交、读已提交、可重复读、可串行化;
  3. 实现手段主要有:
    • MVCC(多版本并发控制):解决大部分读写并发问题;
    • 行锁 + 间隙锁 + Next-Key Lock:控制并发写和“范围插入”;
  4. 日常绝大部分情况:
    • 用 InnoDB 默认的 REPEATABLE READ 就足够;
    • 极端需要读最新且能接受不可重复读时,用 READ COMMITTED。

理解了“隔离级别 = 看见哪些版本的数据 + 加哪些锁”,你基本就把 MySQL 事务隔离级别的本质吃透了。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值