2026年7月11日 · 5 分钟阅读

MySQL 事务原理:ACID、隔离级别、Undo Log、Read View 与 MVCC

梳理 MySQL 事务四大特性、并发问题、隔离级别、LBCC、MVCC、Undo Log、Read View、当前读和快照读。

事务是数据库最重要的机制之一。它保证一组 SQL 要么都成功,要么都失败,同时还要在多事务并发执行时尽量兼顾正确性和性能。

MySQL 事务真正难的地方,不是记住 ACID,而是理解隔离性为什么会被分级、MVCC 为什么能做到读写不冲突。

什么是事务

事务是逻辑上的一组操作。组成这组操作的 SQL,要么全部成功,要么全部失败。

例如转账:

update account set balance = balance - 100 where id = 1;
update account set balance = balance + 100 where id = 2;

这两条 SQL 必须作为一个整体执行。不能扣款成功、加款失败。

ACID

事务有四大特性:

特性含义
Atomicity 原子性事务内操作要么全部成功,要么全部失败
Consistency 一致性事务前后数据满足业务约束
Isolation 隔离性多个事务并发时互不干扰到约定程度
Durability 持久性事务提交后数据永久保存

原子性通常依赖 undo log,持久性依赖 redo log,隔离性依赖锁和 MVCC。

事务并发问题

多个事务同时访问相同数据时,可能出现几类问题:

问题说明
脏读读到其他事务未提交的数据
不可重复读同一事务内两次读取同一行,结果不同
幻读同一事务内两次范围查询,记录数量不同
丢失更新多个事务同时更新,后提交覆盖先提交

隔离级别就是在性能和隔离性之间做取舍。

隔离级别

SQL 标准定义了四种隔离级别:

隔离级别能解决的问题
Read Uncommitted基本不解决,可能脏读
Read Committed解决脏读
Repeatable Read解决脏读、不可重复读,InnoDB 默认级别
Serializable串行执行,隔离最强,性能最低

隔离性越强,并发能力通常越弱。数据库不能简单让所有事务排队执行,否则性能会很差。

LBCC:基于锁的并发控制

LBCC 是 Lock Based Concurrency Control,基于锁的并发控制。

思路很直接:读写冲突时加锁,让事务排队。

优点是规则容易理解,缺点是读写互相阻塞,并发性能受影响。

MVCC:多版本并发控制

MVCC 是 Multi-Version Concurrency Control,多版本并发控制。

它的核心思想是:同一行数据可以存在多个版本,不同事务根据自己的视图读取不同版本。

这样普通 SELECT 可以不加锁,读写不冲突,大幅提升并发能力。

MVCC 依赖两个关键机制:

  • Undo Log。
  • Read View。

Undo Log

Undo Log 保存数据修改前的旧版本。

例如把 name刘备 改成 曹操,undo log 会记录修改前的值。这样事务回滚时可以恢复旧值,快照读时也可以沿着版本链找到对当前事务可见的版本。

Undo Log 的作用:

  • 支持事务回滚。
  • 支持 MVCC 版本链。

一个事务提交后,undo log 不一定立刻删除,因为可能还有其他事务的 Read View 需要读取旧版本。

Read View

Read View 可以理解为事务生成的一张可见性快照。

它会记录生成快照时系统中活跃的事务 ID,并根据规则判断某个数据版本对当前事务是否可见。

判断时通常关注:

  • 当前活跃事务 ID 列表。
  • 最小活跃事务 ID。
  • 下一个即将分配的事务 ID。
  • 当前事务自身 ID。

MVCC 的核心问题就是:版本链中哪个版本对当前事务可见。

RC 和 RR 下 Read View 的差异

Read Committed 和 Repeatable Read 都可以使用 MVCC,但生成 Read View 的时机不同。

隔离级别Read View 生成时机
RC每次普通 SELECT 都生成新的 Read View
RR第一次普通 SELECT 生成 Read View,事务内复用

所以 RC 下同一事务两次查询可能看到其他事务新提交的数据;RR 下同一事务多次查询结果更稳定。

当前读和快照读

InnoDB 读操作可以分为当前读和快照读。

快照读读取的是符合 Read View 的历史版本,普通 SELECT 通常是快照读。

当前读读取最新版本,并且可能加锁,例如:

select * from user where id = 1 for update;
update user set name = '曹操' where id = 1;
delete from user where id = 1;

当前读要读取最新数据,因为它可能要修改记录,不能只看旧版本。

Insert、Update 和 Delete 的事务视角

CUD 操作通常属于当前读逻辑。

以 Update 为例,执行时需要找到当前最新记录并加锁,修改数据后生成新的版本,同时写 undo log 和 redo log。

这也是为什么写操作之间会发生锁等待,而普通快照读可以不阻塞写。

小结

MySQL 事务可以这样串起来:

  1. ACID 描述事务目标。
  2. 隔离级别是在正确性和性能之间做权衡。
  3. 锁能解决并发冲突,但会降低并发能力。
  4. MVCC 用多版本让普通读不加锁。
  5. Undo Log 保存历史版本。
  6. Read View 判断哪个版本可见。
  7. 当前读看最新数据,快照读看事务视图中的数据。

理解 MVCC 后,再看 InnoDB 锁机制和 RR 下的幻读处理,就会清楚很多。