MySQL UPDATE执行时先写PREPARE状态redo log,再写binlog,最后标记COMMIT,通过两阶段提交保证一致性;undo log仅存旧值用于MVCC和回滚,不参与2PC。

UPDATE执行时Redo Log先写入PREPARE状态
MySQL执行UPDATE语句时,InnoDB引擎不会等数据页刷盘才提交事务。它先把这次修改对应的物理页变更(比如“页号123,偏移量456,写入新值0xABC”)写入redo log buffer,并在事务进入prepare阶段时,将该条日志标记为PREPARE状态并刷到磁盘(取决于innodb_flush_log_at_trx_commit配置)。这一步不涉及SQL逻辑,只记录底层字节级改动。
关键点在于:此时redo log已落盘,但事务尚未真正提交;binlog还没动过,数据页也仍是内存中的脏页。
-
innodb_flush_log_at_trx_commit=1:prepare阶段就强制刷盘,崩溃后可恢复 -
=0或=2:可能只写入OS缓存,宕机后这部分PREPARE日志会丢失,导致事务被回滚 - 即使其他事务也在写
redo log buffer,prepare刷盘会把整个buffer一并刷出——不是只刷当前事务
Binlog写入发生在Redo Log prepare之后、commit之前
Server层收到InnoDB返回的prepare成功信号后,才开始把这条UPDATE的完整逻辑操作(如UPDATE t SET name='alice' WHERE id=1,或ROW格式下的前后镜像)写入binlog file。这个过程受sync_binlog控制:
-
sync_binlog=1:必须刷盘成功,才通知InnoDB执行commit -
sync_binlog=0:依赖OS缓存,可能丢事务(尤其主从场景下风险极高) - 如果
binlog写入失败(磁盘满、权限不足),整个事务会rollback,InnoDB也会撤销prepare状态
注意:binlog写入的是逻辑变更,不是物理页偏移;它不管Buffer Pool里有没有脏页,只管“用户干了什么”。
两阶段提交完成最终确认
InnoDB在收到Server层“binlog已落盘”的确认后,才把对应redo log条目标记为COMMIT,事务正式结束。这个握手协议就是两阶段提交(2PC):
- crash发生在
PREPARE后、binlog写入前 → 重启后发现无对应binlog,直接回滚该事务 - crash发生在
binlog写入后、InnoDBCOMMIT前 → 重启后发现binlog存在且redo log是PREPARE状态,自动重放并转为COMMIT - 没有2PC,主库崩溃后可能
redo log已提交但binlog缺失,从库永远同步不到这条更新
UPDATE后的Undo Log不是“反向SQL”,而是版本链锚点
UPDATE产生的undo log不存UPDATE t SET name='bob' WHERE id=1的逆操作,而是存旧值本身(ROW格式下是整行前镜像)。它不参与2PC,也不写binlog,但直接影响MVCC和回滚:
- 事务未提交时,
ROLLBACK靠它还原数据 - 事务提交后,只要还有活跃事务需要读取该行旧版本,
undo log就不能删 - 长事务会阻塞
purge线程,导致ibdata1膨胀、undo log空间无法复用
真正容易被忽略的是:UPDATE语句触发的redo log和binlog协同,本质是跨存储引擎层与Server层的原子性保证;而undo log只服务InnoDB内部,它的生命周期完全独立于那两个日志。


















