UPDATE语句日志写入顺序为:先写undo log(记录旧值,保证回滚),再写redo log buffer(记录新值,prepare状态),最后在两阶段提交中先刷redo log至prepare、再写并刷binlog、最终标记redo log为commit。

UPDATE语句触发的三类日志写入顺序不能乱
MySQL执行UPDATE时,undo log、redo log、binlog不是并行写入,而是有严格先后依赖的。最常踩的坑是误以为“先改内存再写日志”,实际是:修改前必须先记undo log(保证可回滚),修改中同步写redo log buffer(物理变更记录),提交时才按两阶段提交协议刷盘——redo log先prepare,等binlog落盘成功后再commit。
这个顺序直接决定崩溃恢复是否可靠。如果binlog写失败但redo log已commit,主从复制会丢数据;反过来,如果redo log没prepare就写binlog,崩溃后可能产生无法重放的逻辑日志。
InnoDB层如何处理脏页与Redo Log Buffer的刷盘时机
UPDATE修改的是Buffer Pool里的数据页,立刻变成“脏页”,但不会马上刷磁盘。真正刷盘由后台线程控制,触发条件包括:
-
innodb_max_dirty_pages_pct达到阈值(默认90%),强制刷新部分脏页 - 系统空闲时,
innodb_io_capacity驱动的刷新节奏 - 检查点(checkpoint)推进时,要求
redo log中已提交但未刷盘的LSN位置前移
redo log buffer的刷盘更敏感:事务提交时默认强制刷(innodb_flush_log_at_trx_commit=1),否则可能只留在内存里。生产环境绝不要设为0或2——看似快,但机器掉电就丢事务。
Binlog写入不是“记录SQL文本”那么简单
Server层生成binlog时,格式取决于binlog_format配置:
-
STATEMENT:记录原始SQL,但函数、临时表、非确定性操作(如now())会导致主从不一致 -
ROW:记录每一行变更前后的镜像(before_image和after_image),安全但体积大,且WHERE条件无索引时会全表扫描再比对 -
MIXED:自动降级,但行为不可控,线上基本不用
注意:binlog只在事务提交时一次性写入,不会为每个UPDATE单独刷盘。如果一个事务含100条UPDATE,binlog里就是100条独立事件,但redo log可能合并成几页的物理修改记录。
MDL锁、行锁、Undo Log版本链如何协同生效
执行UPDATE时,InnoDB要同时维护三套状态:
- 表级
MDL锁(MDL_SHARED_WRITE):防止DDL中途修改表结构,阻塞ALTER TABLE - 行级锁:根据
WHERE条件是否命中索引,决定加Record Lock还是Next-Key Lock(含间隙锁),避免幻读 -
undo log版本链:每次更新都把旧值写入undo page,形成链表;MVCC读取时靠read view判断可见性,而不是直接读最新行
最容易被忽略的是:即使UPDATE没走索引,InnoDB仍会对**所有扫描过的记录**加锁(通常是Next-Key Lock),这会导致莫名的锁等待。查information_schema.INNODB_TRX和INNODB_LOCK_WAITS才能定位真实锁源。


















