事务是一组操作的集合,这组操作要么全部成功,要么全部失败。数据库把它们作为一个不可分割的工作单位来提交或回滚。
最经典的例子是转账:A 扣 100 元,B 加 100 元。这两步必须一起成功,否则数据就会出错。
事务最基本的执行分叉,其实就两条路:
ACID 是什么
| 特性 | 含义 | 转账例子 |
|---|---|---|
| 原子性 Atomicity | 事务不可分割,要么全成功,要么全失败 | A 扣钱和 B 加钱必须一起完成 |
| 一致性 Consistency | 事务前后数据满足约束和业务规则 | 总金额不能凭空增加或减少 |
| 隔离性 Isolation | 并发事务之间互不干扰到不合理程度 | 别的事务不能读到错误中间态 |
| 持久性 Durability | 提交后的修改永久生效 | 提交后即使宕机也能恢复 |
ACID 不是四个孤立概念。InnoDB 通过锁、MVCC、redo log、undo log、Buffer Pool 等机制共同实现它们。
并发事务会带来哪些问题
脏读
一个事务读到了另一个事务还没提交的数据。如果对方回滚了,前面读到的就是无效数据。
不可重复读
同一个事务内,两次读取同一行数据,结果不一样。原因通常是另一个事务在中间提交了更新。
幻读
一个事务按条件查询时没有某行数据,但插入时发现这行已经存在,像是出现了“幻影”。它关注的是符合条件的记录集合发生变化。
四种隔离级别
隔离级别越高,数据越安全,但并发性能通常越低。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 几乎不做隔离 |
| READ COMMITTED | 避免 | 可能 | 可能 | 每次读只能读已提交数据 |
| REPEATABLE READ | 避免 | 避免 | 通常可处理 | MySQL InnoDB 默认常用级别 |
| SERIALIZABLE | 避免 | 避免 | 避免 | 串行化,性能最低 |
隔离级别解决的是并发读写之间的可见性和冲突问题。后面理解 MVCC 时,READ COMMITTED和REPEATABLE READ的差异尤其重要。
Buffer Pool 与数据页
InnoDB 不会每次都直接读写磁盘。它会把经常访问的数据页缓存在内存中的 Buffer Pool。
数据页是 InnoDB 管理磁盘的最小单位,默认大小为 16KB。执行增删改查时,通常先操作 Buffer Pool 中的数据页;如果页不在内存,再从磁盘加载。
这样做可以减少磁盘 IO,但也带来一个问题:如果数据只改在内存里,还没刷到磁盘就宕机,怎么办?
答案就是 redo log。
redo log:保证持久性
redo log 记录的是数据页的物理修改。事务提交时,InnoDB 会先把修改写入 redo log,再慢慢把脏页刷回磁盘。
这就是 WAL,也就是 Write-Ahead Logging:先写日志,再写数据页。
提交时可以抓住 WAL 这条主线:
redo log 由两部分组成:
| 部分 | 位置 | 作用 |
|---|---|---|
| redo log buffer | 内存 | 暂存日志 |
| redo log file | 磁盘 | 宕机恢复时使用 |
如果事务提交后服务宕机,但脏页还没刷盘,重启后可以根据 redo log 重新应用修改,恢复已提交事务的数据。
undo log:支持回滚和 MVCC
undo log 记录的是数据被修改前的信息,它是逻辑日志。
可以这样理解:
| 操作 | undo log 中记录什么 |
|---|---|
insert | 记录一条反向delete所需信息 |
delete | 记录一条反向insert所需信息 |
update | 记录恢复旧值所需信息 |
当事务回滚时,InnoDB 可以根据 undo log 做反向操作,把数据恢复到修改前的状态。
undo log 还有另一个重要作用:为 MVCC 提供旧版本数据。update和delete产生的 undo log,提交后不一定立刻删除,因为可能还有事务需要读取旧版本。
redo log 和 undo log 的区别
| 对比项 | redo log | undo log |
|---|---|---|
| 记录内容 | 数据页的物理变化 | 修改前的逻辑信息 |
| 核心作用 | 宕机恢复 | 事务回滚、MVCC |
| 支撑特性 | 持久性 | 原子性、一致性、隔离性的一部分 |
| 写入时机 | 事务提交前后参与刷盘策略 | 数据修改前记录旧值 |
一句话总结:redo log 保证提交后的数据能恢复,undo log 保证失败时能撤回,也能让快照读看到旧版本。
面试回答模板
可以这样回答:
事务是一组不可分割的操作,具备 ACID 特性。原子性表示要么全成功要么全失败,一致性表示事务前后数据满足规则,隔离性解决并发事务互相影响的问题,持久性表示提交后数据永久生效。并发事务可能出现脏读、不可重复读、幻读,可以通过不同隔离级别控制。InnoDB 底层通过 undo log 支持回滚和旧版本读取,通过 redo log 保证事务提交后的持久性,通过锁和 MVCC 共同保证隔离性。
小结
事务不是只靠一个机制实现的。undo log 让事务可以回滚,redo log 让提交结果可以恢复,隔离级别定义了并发读写的可见性边界。把这几部分串起来,ACID 就不再是死记硬背的四个词。