首页/新闻资讯/正文详情

MySQL 内核实战(5):redo log、undo log 与崩溃恢复

发布时间:2026/9/24 1:27:55 来源:云帆数科 栏目:资讯中心
MySQL 内核实战(5):redo log、undo log 与崩溃恢复
问题背景上一篇把写路径的锁与死锁讲完留了一个尾巴事务排队改完只是内存里的事它到底怎么变成磁盘上摔不碎的事实这正是崩溃恢复要回答的。相关的一线现场你大概率遇到过mysqld 被 OOM killer 杀掉或kill -9重启后数据一行没丢一行没多——谁做的账innodb_flush_log_at_trx_commit设成 1 和设成 2压测 TPS 差出一倍生产到底该选哪个还有一改就心里发毛的redo log 满了SHOW ENGINE INNODB STATUS里 log 区报错整个实例瞬间冻住只出不进。这些现象背后是同一条主线WALWrite-Ahead Logging提前写日志协议、LSN 水位线、checkpoint 推进以及 redo/undo 两本方向相反的账。本篇回答五个问题为什么改一个字节要写一条日志而不是直接改页redo 与 undo 各自负责什么、和 430 篇的 MVCC 版本链什么关系checkpoint 如何决定重启要恢复多久trx_commit0/1/2分别丢什么数据以及 torn page撕页这个 fsync 都防不住的坑。核心原理第一WAL把随机写变顺序写的协议。一个事务改 3 行可能涉及 5 个 16KB 页若提交时同步把这些页写回磁盘就是把页在哪就得写哪的随机 IO 压进提交路径——机械盘上几毫秒一次再摊上并发就全堵死。WAL 的换法改动先以紧凑记录追加进 redo log顺序写、一块内存 log buffer 攒批页允许留在 Buffer Pool 做脏页晚点再写协议只要求一条铁律——提交返回客户端之前这笔事务的 redo 必须先落盘。于是提交成本从多次随机页写降为一次顺序日志 fsync。redo 记录是物理为主的页号 偏移 改成什么严格说是 logical-physicalMLOG 类型自带幂等判断恢复时不需要理解 SQL 语义。第二redo 与 undo 是同一枚硬币的两面。每条 undo 记录也走 redo 通道落盘undo 的修改本身也要可恢复。分工上redo 管已发生的别丢——崩溃后从 checkpoint 水位起重放把已提交、页却没来得及写回的改动补上undo 管不该发生的别留——回滚未提交事务以及 430 篇讲过的 MVCC 版本链供快照读走。两者串在同一条 LSN逻辑日志序列号全局单调递增时间轴上页头记着我最后被哪条日志改过page LSN日志记录自带 LSNcheckpoint 记哪之前的脏页都已落盘——三个数字对齐重放不多不少。第三checkpoint恢复时间的调节阀。重放 redo 的代价与上次 checkpoint 之后累积的脏改动成正比所以 InnoDB 后台持续把脏页刷盘并推进 checkpoint 水位LSN。SHOW ENGINE INNODB STATUS的 LOG 区里Log sequence number与Last checkpoint at的差值就是此刻崩溃后需要重放的量差值小秒级拉起差值逼近 redo 文件总容量则触发最凶的机制——redo 写满即全局冻结log 是环形文件新日志要覆盖旧区间而旧区间必须先靠 checkpoint 把对应脏页刷完才能复用刷不上就只能让所有事务排队等 IO表现是整个库只读都不利索。redo 容量innodb_redo_log_capacity8.0.30旧版为innodb_log_file_size × 组数本质是允许的脏页水位 × 写峰值扛度不是越大越好——太大时 checkpoint 更懒崩溃恢复更久还容易养成刷盘线程的慢性怠工。第四提交点持久性的三档取舍与撕页。innodb_flush_log_at_trx_commit精确规定提交那一刻做什么1提交线程同步 writefsync 才回 OK——已回 OK 的事务对任何崩溃免疫对进程崩溃与断电都成立2提交只 write 到 OS page cache后台每秒 fsync——进程崩溃不丢OS 会把 cache 落盘整机断电丢掉已回 OK 但 fsync 未及的约一秒0提交什么都不做每秒由后台 writefsync——进程崩溃也丢约一秒。而就算 1 也有一个 fsync 防不住的坑torn page。盘以 512B/4KB 扇区为原子单位InnoDB 的 16KB 页写可能被撕成半新半旧这样的页 redo 重放都会出错。解法是 doublewrite buffer脏页先顺序写进一个 2MB 中转区再写目的地重启时拿中转区副本校验修复——用一次额外顺序写换页级原子性innodb_doublewriteON是绝对不该关的默认值它同时是 primary/replica 半同步之类无关的机制。第一次代码实验及输出下面是确定性 WAL 模拟纯 Python 内存模型非真 mysqldEngine 维护磁盘页、脏页表、带 LSN 的 redo 流与 undo 前像表。剧本T1 提交T2 改了但未提交checkpoint 把未提交的脏页也冲下磁盘真实 InnoDB 的 page cleaner 正是如此回滚交给 undoT3 提交但页仍脏在内存此时崩溃。恢复阶段从日志重建提交名单checkpoint 之后的已提交记录做 REDO未提交事务按 undo 前像做 UNDO。# InnoDB 日志体系最小模型: redo(带 LSN 的物理改动, 循环追加) undo(逻辑前像)# WAL 铁律: 事务提交前 redo 必须落盘, 脏页允许滞留内存晚写classEngine:def__init__(self):self.disk_pages{acct_A:100,acct_B:50}# 磁盘上的页self.dirty{}# Buffer Pool 脏页self.redo[]# 已落盘 redo: (lsn, trx, page, val)self.redo_buf[]# log buffer(未落盘)self.undo{}# trx - [(page, 前像, 后像)] 供回滚self.lsn0self.ckpt_lsn0defupdate(self,trx,page,val):self.lsn1self.undo.setdefault(trx,[]).append((page,self.cur(page),val))self.redo_buf.append((self.lsn,trx,page,val))self.dirty[page]valdefcur(self,page):returnself.dirty.get(page,self.disk_pages[page])defcommit(self,trx):self.lsn1self.redo_buf.append((self.lsn,trx,COMMIT,None))# WAL: 提交返回客户端之前, 同步把 log buffer 刷到磁盘(trx_commit 1 的语义)self.redoself.redo_buf self.redo_buf[]deflog_writer(self):# log_writer 线程持续把 log buffer 写入日志文件(写不等 fsync)self.redoself.redo_buf self.redo_buf[]defcheckpoint(self):forp,vinself.dirty.items():# 脏页全部写回(含未提交事务的!)self.disk_pages[p]v self.dirty.clear()self.log_writer()self.ckpt_lsnself.lsndefcrash(self):self.dirty,self.redo_buf{},[]# 内存全丢, 未落盘日志蒸发defrecover(self):committed{tfor_,t,p,_inself.redoifpCOMMIT}# 从日志重建提交名单plan[]forlsn,trx,page,valinself.redo:# 1) REDO 阶段: 重放 ckpt 后的已提交改动iflsnself.ckpt_lsnandtrxincommittedandpage!COMMIT:self.disk_pages[page]val plan.append(redo lsn%d %s.%s%s%(lsn,trx,page,val))fortrxinsorted({tfor_,t,_,_inself.redo}-committed):forpage,old,newinreversed(self.undo.get(trx,[])):# 2) UNDO 阶段ifself.disk_pages.get(page)new:self.disk_pages[page]old plan.append(undo %s.%s%s 回滚到前像 %s%(trx,page,new,old))returnplan eEngine()e.update(T1,acct_A,130);e.commit(T1)print(T1: acct_A 100-130 已提交, lsn%d%e.lsn)e.update(T2,acct_B,70)print(T2: acct_B 50-70 改完未提交)e.checkpoint()print(checkpoint: 脏页写回磁盘 acct_A%d acct_B%d (未提交的也被冲下去了!), ckpt_lsn%d%(e.disk_pages[acct_A],e.disk_pages[acct_B],e.ckpt_lsn))e.update(T3,acct_A,999);e.commit(T3)print(T3: acct_A 130-999 已提交, redo 落盘但页还脏在内存)e.crash()print(---- mysqld 崩溃(内存全丢) ----)forstepine.recover():print(恢复: step)print(恢复后磁盘: acct_A%d acct_B%d | 期望: A999(T3不丢) B50(T2未提交须消失)%(e.disk_pages[acct_A],e.disk_pages[acct_B]))运行输出T1: acct_A 100-130 已提交, lsn2 T2: acct_B 50-70 改完未提交 checkpoint: 脏页写回磁盘 acct_A130 acct_B70 (未提交的也被冲下去了!), ckpt_lsn3 T3: acct_A 130-999 已提交, redo 落盘但页还脏在内存 ---- mysqld 崩溃(内存全丢) ---- 恢复: redo lsn4 T3.acct_A999 恢复: undo T2.acct_B70 回滚到前像 50 恢复后磁盘: acct_A999 acct_B50 | 期望: A999(T3不丢) B50(T2未提交须消失)整个剧本里最反直觉的是 checkpoint 那一行未提交事务 T2 的脏改动也会被写到数据文件里。这不是 bug 而是设计——page cleaner 只认页的水位、不认事务边界如果恢复只会 REDO磁盘上就永远留着 T2 的罪证。所以恢复必须两阶段先按 redo 把所有痕迹补齐到崩溃瞬间的最大世界再用 undo 把未提交事务的修改逐条抹去真实 InnoDB 里这步是 rollback segments 上的持久化 undo 链 purge 收尾模型简化为前像表。注意 T3 的两世身份它的 redo 在提交时已落盘WAL 铁律但它的页到崩溃仍是脏的——重放 lsn4 那一条就一分不差。已提交不丢、未提交不留不是魔法是这两条流水线的合取。工程化改进第一步参数基线金融/交易库双 1可容忍秒级丢失的报表库才谈 2。innodb_flush_log_at_trx_commit1sync_binlog1下一篇的主角是默认即正确的基线动它们之前先问业务已告诉用户成功的操作重启后能不能少一笔2 省下的是每次提交的 fsync 调用机械盘时代收益巨大NVMe/带电容保护写缓存的 RAID 上往往只换来 10%~30% 吞吐——用一次断电丢一批订单去换这点数字多数业务不划算。云盘注意宿主掉电时实例内看是 2和云盘自己的持久化语义是两层账确认云厂商文档对 fsync 的承诺再下结论。第二步给 redo 做容量与水位监控。看两个数SHOW ENGINE INNODB STATUSLOG 区Log sequence number - Last checkpoint at的差值占 redo 总容量的比例逼近 100% 就是冻结前夜以及innodb_data_written的分钟级增速推断写峰值能否在 redo 转一圈内被 checkpoint 消化。innodb_redo_log_capacity经验值取高峰期 1 小时的 redo 产量量级并至少给几个 GB8.0.30 支持在线调整该参数大促前的扩容不必再重启。第三步别给恢复制造人祸。崩溃重启时Streaming recovery ... in progress日志刷几分钟是正常重放中途绝不再 kill——二次崩溃只会从头再来恢复慢的根治是 redo 容量与 checkpoint 调优第二步不是 force。真遇到页损坏拉不起checksum errorinnodb_force_recovery1..6按最小级别逐个试、且只为把数据mysqldump/SELECT INTO OUTFILE抢救出来级别 3 以上禁止写业务库事先准备好 HA 切换与备份回放的预案比事后调 force 参数体面得多。第四步把 undo 纳入日常巡检。undo 表空间8.0 默认 2 个独立 undo 表空间自动回收大小与 430 篇的 purge lag 直接挂钩information_schema.INNODB_METRICS里trx_rseg_history_len历史链表长度持续上涨就是长事务钉住了旧版本redo/undo 都会跟着膨胀还会拉长崩溃恢复时要回滚的量。治理动作与 430 一致告警 kill 超时事务这是同一颗药。第二次代码实验及输出把三档 trx_commit × 两种灾难做成损失矩阵确定性模拟trx1~trx8 在 t1~8 依次提交并收到客户端 OKt5.5 突发灾难模式 0/2 的后台周期 fsync 近似为每秒末一次。对比进程崩溃OS 存活、内存丢失与整机断电OS cache 也丢下各自吞掉了哪些已回 OK的提交。# innodb_flush_log_at_trx_commit 三档 x 两种灾难: 谁丢已回 OK的提交?# 时间以 tick 计, trx1..trx8 在 t1..8 提交并回 OK; t5.5 突发灾难# 设后台每秒末(t0.75)做一次周期 fsync(模式2/0 的 flush 节奏近似)COMMIT_T{t:trx%d%tfortinrange(1,9)}NOW5.5acked[cfort,cinsorted(COMMIT_T.items())iftNOW]print(断电前已提交并回 OK: %s%acked)print((trx6..8 提交在灾难之后, 客户端只会看到失败, 不算丢失)\n)deflost(mode,disaster):ifmode1:# 提交点同步 fsync, 回 OK 即持久return[]ifmode2:# 提交只 write() 进 OS page cacheifdisaster进程崩溃:return[]# OS 还活着, cache 稍后自然落盘# mode 0: 提交留在 InnoDB log buffer; 0/2 的断电都只剩周期 flush 追上的部分last_flushmax((kforkinrange(1,9)ifk0.75NOW),default0)return[cforcinackedifint(c[3:])last_flush]formodein(1,2,0):print(trx_commit%d 进程崩溃丢: %-14s 整机断电丢: %s%(mode,lost(mode,进程崩溃)or无,lost(mode,断电)or无))print()print(双 1 配置 (trx_commit1 sync_binlog1):)print( redo 与 binlog 各自独立 fsync, XID 两阶段提交对齐, 任一单边崩溃都不丢已回 OK 的事务)运行输出断电前已提交并回 OK: [trx1, trx2, trx3, trx4, trx5] (trx6..8 提交在灾难之后, 客户端只会看到失败, 不算丢失) trx_commit1 进程崩溃丢: 无 整机断电丢: 无 trx_commit2 进程崩溃丢: 无 整机断电丢: [trx5] trx_commit0 进程崩溃丢: [trx5] 整机断电丢: [trx5] 双 1 配置 (trx_commit1 sync_binlog1): redo 与 binlog 各自独立 fsync, XID 两阶段提交对齐, 任一单边崩溃都不丢已回 OK 的事务矩阵要横着读2 与 0 的差别只发生在进程崩、机器活着时——2 的数据已在 OS page cache内核会替它落盘0 还躺在 mysqld 自己的内存里人死账消。而断电时两者殊途同归都丢最近一个 flush 周期所以我设了 2 所以比 0 安全是半对的直觉它只在进程级灾难里成立。1 两列全零是因为它在回 OK这个动作和fsync 完成之间画了等号——承诺的边界就是持久性的边界。最后一行提醒这条链还有另一半redo 落盘只保证 InnoDB 自身一致事务若还要进 binlog 供从库/订阅消费两边各有一次 fsync、各崩各的对齐靠 XID 两阶段提交——这正是下一篇的主菜。常见陷阱其一把 1 当万能保险转头innodb_doublewriteOFF提性能fsync 防不了 16KB 页被 4KB 扇区撕开redo 重放遇半新半旧页直接起不来——省那点写放大换来的是数据文件级损坏。其二redo 配小了只看到暂时没事日常低峰 checkpoint 勉强跟得上一次批量导数/大事务 UPDATE 直接把Log sequence number - checkpoint打满全库冻结等刷盘故障现场却是什么都没做就是卡容量按写峰值算不要按默认值躺平。其三恢复中反复 killrecovery 无断点续传每次都是全量重来越 kill 越起不来耐心等日志里的恢复进度同时查备份与从库。其四autocommit0配 1 却感觉不到 fsync 开销日志只在事务提交时刷一个开着 40 分钟未提交的事务等于给 39 分钟的改动免了持久化承诺还倒贴长事务锁与 purge 债——框架层的事务边界要与参数假设对齐。其五以为 binlog 能代替 redo 做崩溃恢复binlog 是 server 层的逻辑/行事件按语句序消费、没有页概念拿它恢复等于从 checkpoint 起重放整个业务的逻辑变更慢几个数量级且无法定位页——两套日志的层次不同谁也替不了谁。落地清单生产基线双 1innodb_flush_log_at_trx_commit1sync_binlog1innodb_doublewriteON降级须业务书面确认丢失窗口监控SHOW ENGINE INNODB STATUS的 LSN-checkpoint 差值占 redo 容量比例70% 告警按写峰值配innodb_redo_log_capacity崩溃恢复期间禁止再次 kill页损坏走innodb_force_recovery只做抢救导出平时演练备份回放巡检INNODB_METRICS.trx_rseg_history_len与 undo 表空间水位长事务治理与 430 共用同一告警大事务/批量导数前评估 redo 消化能力分片提交、错峰把冻结风险拆成小口本篇把 InnoDB 自己的账本闭合了redo 保已提交不丢undo 保未提交不留checkpoint 控制恢复时长。但 MySQL 其实有两套日志——server 层的 binlog 不参与崩溃恢复却决定从库有没有数据、订阅能不能回放、误删能不能闪回。两本账各自 fsync 就有主库提交了、从库丢了的缝隙于是才有了两阶段提交、位点与 GTID。下一篇《MySQL 内核实战6binlog 与主从复制原理》把复制链路和丢数据窗口讲透。参考来源MySQL 8.0 Reference Manualinnodb_flush_log_at_trx_commit 系统变量https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_innodb_flush_log_at_trx_commitMySQL 8.0 Reference ManualForcing InnoDB Recoveryhttps://dev.mysql.com/doc/refman/8.0/en/forcing-innodb-recovery.htmlMySQL 8.0 Reference ManualInnoDB Undo Tablespaceshttps://dev.mysql.com/doc/refman/8.0/en/innodb-undo-tablespaces.htmlWikipediaWrite-ahead logginghttps://en.wikipedia.org/wiki/Write-ahead_loggingWikipediaUninterruptible power supply磁盘写缓存的电池保护备份https://en.wikipedia.org/wiki/Uninterruptible_power_supply 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《MySQL 内核实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。

相关推荐

ESP32选型实战:S3与C3性能对比及避坑指南
ESP32选型实战:S3与C3性能对比及避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:27:43

GB/T 27930-2015 BMS充电协议测试实战:从报文解析到故障注入
GB/T 27930-2015 BMS充电协议测试实战:从报文解析到故障注入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:27:43

Windows 10 RECOVERY蓝屏修复全指南:引导重建与驱动定位
Windows 10 RECOVERY蓝屏修复全指南:引导重建与驱动定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:27:37

EKS IRSA 调 SQS 仍 403:先补 GetQueueUrl
EKS IRSA 调 SQS 仍 403:先补 GetQueueUrl

一句话摘要:Pod 已挂 IRSA,策略里也有收发删,SDK 仍 AccessDenied——缺的往往是 GetQueueUrl,且不要去改节点角色。 目录 前言 一、先分清三条链 二、IRSA 只读盘点 三、收发删不够:补三个只读动作 四、队列不存在不是没权限 · 2026/9/24 2:12:31

2026年AI视频总结工具推荐:支持B站、课程和播客的4款实用工具
2026年AI视频总结工具推荐:支持B站、课程和播客的4款实用工具

课程录播、B站知识视频、播客和访谈越来越长,但真正有价值的内容往往藏在几十分钟甚至几小时的音视频里。AI视频总结工具可以帮助用户提取重点、生成结构化内容,并在需要时快速回看原视频。选工具时,建议重点看三件事:是否支持你的… · 2026/9/24 2:12:00

千问 8 通用立减,输入专属活动口令,外卖打车都能用
千问 8 通用立减,输入专属活动口令,外卖打车都能用

1、先把千问这个APP下载在手机里2、然后在对话框里输申领口令(固定中文135523),方法如下3、会看到"待领取"按钮,按照页面指引完成账号绑定,成功后券就会自动发放到你的卡包中。整个流程也就完成了&#xff0… · 2026/9/24 2:11:42

Ekko Agent Skill 创作指南:基于 skill-creator 的设计、创建、维护与验证全流程
Ekko Agent Skill 创作指南:基于 skill-creator 的设计、创建、维护与验证全流程

AI 应用人工智能AI Agent本地部署前端后端工作流自动化 【免费下载链接】ekko-studio Ekko Studio is a local-first AI workspace for multi-agent chat, coding, and visual workflows, available on desktop and the web. 项目地址: https://gitcode.com/gh_mirr… · 2026/9/24 2:11:42

ESP32 SPI驱动W5500以太网:从调库到时序实战
ESP32 SPI驱动W5500以太网:从调库到时序实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 2:11:30

LVDS ADC数据对齐实战:Bitslip用法与三种对齐策略解析
LVDS ADC数据对齐实战:Bitslip用法与三种对齐策略解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 2:11:30

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码