事务原子性靠undo log实现:执行前记录旧值或逆操作,回滚时重放undo log;持久性靠redo log强制刷盘保障;锁机制与事务边界强耦合,行锁、间隙锁等随事务结束释放。

事务的原子性靠 undo log 实现
MySQL 的事务原子性不是靠“一次性写完”保证的,而是靠 undo log 在出错或回滚时反向恢复。每条 INSERT、UPDATE、DELETE 操作执行前,InnoDB 都会先写一条对应的 undo log 记录,保存旧值或逻辑逆操作。比如 UPDATE t SET a=5 WHERE id=1 会记录“把 a 改回原值”的 undo 条目。
关键点:
-
undo log存在独立的undo tablespace(或系统表空间),不和数据页混在一起 - 事务未提交前,
undo log不能被覆盖;已提交但还有长事务依赖时,也不能清理 - 执行
ROLLBACK时,InnoDB 不是“删掉新数据”,而是按undo log重放逆操作 -
SELECT读取时若遇到未提交的修改,会用undo log构造一致性视图(MVCC 的基础)
隔离级别由 read view + undo log 共同控制
InnoDB 的可重复读(REPEATABLE READ)不是靠锁住所有行实现的,而是每个事务在第一次 SELECT 时生成一个 read view,里面固化了当时活跃事务 ID 列表。后续查询都基于这个快照判断哪些版本可见。
常见误解:
- “RR 下不会幻读”——其实会,只是普通
SELECT看不到,而SELECT ... FOR UPDATE或更新语句仍可能触发间隙锁来防止幻读 -
read view不包含已提交事务的最新数据,只决定“该不该用undo log回溯到哪个版本” - 不同隔离级别下
read view生成时机不同:RC 每次SELECT都新建,RR 只建一次
持久性靠 redo log 强制刷盘保障
事务提交(COMMIT)并不等于数据页落盘。InnoDB 把变更先写入内存中的 redo log buffer,再刷到磁盘上的 redo log file(顺序 I/O,快)。只要 redo log 写成功,崩溃后就能通过重放日志恢复未刷盘的数据页。
注意几个硬性约束:
-
innodb_flush_log_at_trx_commit=1(默认):每次COMMIT都fsync刷盘,最安全 -
=0:每秒刷一次,崩溃可能丢一秒事务;=2:写 OS cache 即返回,但 OS 崩溃仍可能丢 -
redo log是物理日志(记录“某页某偏移改成了什么”),不是 SQL 语句,不可直接阅读 - 其大小受
innodb_log_file_size限制,写满会触发 checkpoint,强制刷脏页
锁机制与事务边界强耦合
InnoDB 的行锁(record lock)、间隙锁(gap lock)、临键锁(next-key lock)都不是独立存在的,而是由事务在执行语句时动态加上的,并随事务结束统一释放(非语句结束)。
容易踩坑的地方:
- 没有索引的
WHERE条件会导致全表扫描+全表加锁,不是“没走索引就不锁” -
SELECT ... LOCK IN SHARE MODE和FOR UPDATE显式加锁,但普通SELECT在 RR 下也隐式构建 read view,影响并发可见性 - 死锁检测由
innodb_deadlock_detect=ON控制,默认开启,但高并发下可能带来额外开销 - 锁信息只能通过
INFORMATION_SCHEMA.INNODB_TRX、INNODB_LOCKS(8.0 已移除)、INNODB_LOCK_WAITS查看,SHOW ENGINE INNODB STATUS输出更实时
真正难调的是多事务交叉时 undo log 生命周期、read view 可见性、redo log 刷盘节奏、锁持有范围这四者的叠加效应。比如一个长事务没提交,会让 undo log 无法清理,进而阻塞 purge 线程,最终拖慢整个实例的更新性能——这种问题不会报错,但响应越来越慢。










