mysql更新操作的原子性由innodb的undo log保证:事务开始前先写入undo log,崩溃后可据此回滚未提交修改;undo log记录逻辑变更,支撑原子性与mvcc版本读取。

MySQL更新操作的原子性到底由谁保证?
不是事务 BEGIN/COMMIT 本身,也不是锁机制,而是 InnoDB 在写数据前先写 undo log —— 这是原子性的物理基础。只要 undo log 落盘成功,哪怕更新中途崩溃,重启后也能靠它把未提交的修改“擦掉”。反之,如果 undo log 没写完就挂了,那这次更新根本不会被记录为“已开始”,自然也不会留下脏数据。
Undo Log 回滚段(Rollback Segment)怎么分配和复用?
InnoDB 启动时初始化若干个回滚段(默认 128 个),每个回滚段管理一批 undo log 页面。事务开始时,会从活跃回滚段中分配一个 undo slot,后续所有该事务的 undo 记录都追加到对应链表里。关键点在于:
- 回滚段不是按事务独占分配的,多个短事务可能共享同一个回滚段
- 已提交事务的 undo 页面不会立即释放,要等 purge 线程确认无任何一致性读(consistent read)依赖它才回收
-
innodb_undo_tablespaces控制是否将 undo 存到独立表空间文件(undo_001等),避免系统表空间膨胀 - 长事务会持续占用 undo 页面,导致 purge 延迟,进而让其他事务的
undo log无法清理 —— 这是Undo log size too large或Lock wait timeout exceeded的常见诱因
一条 UPDATE 语句在 undo 层发生了什么?
以 UPDATE t SET name='b' WHERE id=1; 为例,InnoDB 实际执行步骤如下:
- 先定位到聚簇索引中
id=1的记录,加X锁 - 把原记录的完整镜像(包括隐藏字段
DB_TRX_ID、DB_ROLL_PTR)写入当前事务的undo log中 - 更新聚簇索引记录,并将
DB_ROLL_PTR指向刚写入的 undo 日志位置 - 若事务回滚,InnoDB 顺着
DB_ROLL_PTR找到上一版本,逐条重放 undo 记录恢复旧值 - 若事务提交,则标记该 undo 段为“可 purge”,但不立刻删除 —— MVCC 需要它支撑历史版本读取
注意:undo log 记录的是“逻辑日志”(如“把 name 改为 a”),不是物理页变更,所以能跨行、跨页回滚,但也因此无法用于崩溃恢复(那是 redo log 的事)。
为什么有时候 rollback 很慢?
rollback 慢往往不是因为 undo 本身大,而是因为要重放大量 undo 记录并同时维护锁和缓冲池一致性。典型场景包括:
- 大事务更新了几十万行:每行都要生成 undo,rollback 时就得逐行恢复,且恢复过程仍需加锁防止并发读写冲突
- 事务运行期间有大量二级索引更新:每个二级索引变更也会产生 undo,数量可能是聚簇索引的数倍
- 系统负载高 + buffer pool 压力大:undo 回滚需要读入旧数据页,若页不在内存中,就会触发磁盘 I/O
- 开启了
innodb_status_output_locks且锁争用严重:rollback 过程中锁信息采集会拖慢整体速度
真正危险的是“rollback 中被 kill”——此时 InnoDB 会进入强制回滚流程,可能持续几分钟甚至更久,期间该回滚段不可用,新事务可能被阻塞。生产环境应尽量避免手动 kill 大事务。











