ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL三大日志详解:binlog、redo log与undo log机制与实战

MySQL三大日志详解:binlog、redo log与undo log机制与实战 干了这么多年MySQL运维和开发遇到过不少“神秘事件”明明刚提交的事务数据库一崩就找不到了主库上执行了一条delete备库跟到一半卡死不小心删了全表用了各种恢复工具才抢回数据。这些问题的答案其实都藏在MySQL的三大日志里binlog、redo log、undo log。MySQL面试基本必问这三兄弟生产环境出了故障也绕不开它们可以说搞懂三大日志就搞懂了MySQL的数据安全和一致性的底层逻辑。这篇内容我会从三个日志各自负责什么说起再讲清楚它们之间怎么配合最后落到真实场景的配置、排查和恢复操作上。不端着讲理论尽量把每个机制背后的“为什么这么做”也讲明白适合刚入门想进阶的开发者也适合被线上问题折磨过的DBA。1. 三大日志各自管什么——先搞清楚它们的分工很多人一开始容易把binlog、redo log、undo log混为一谈因为它们都在“记录东西”。但实际上它们分属MySQL不同的层次服务的核心目标完全不一样。先记住一句话binlog是Server层的事redo log和undo log是InnoDB存储引擎的事。Server层负责连接管理、SQL解析、优化存储引擎层负责数据真正落地。日志分属两层就注定了它们的职责天然不同。1.1 binlogMySQL Server层的大事记binlog也叫二进制日志记录的是数据库执行过的“所有会改变数据的操作”比如INSERT、UPDATE、DELETE以及表结构变更DDL。它的记录方式是逻辑日志也就是说它记的不是“哪一个数据页的第几字节改成了什么”而是“执行了一条什么样的SQL或者某一行的某个列值从A变成了B”。逻辑日志的好处是跨版本、跨引擎通用拿到binlog几乎可以在任何一套MySQL上重新回放。binlog的核心用途有两个一个是主从复制主库把binlog发给备库备库回放这些日志就能得到和主库一致的数据另一个是数据恢复通过全量备份binlog增量就能把数据恢复到任意时间点。binlog是追加写的文件写到一定大小就滚动到下一个文件所以你可以看到磁盘上有一堆mysql-bin.000001、mysql-bin.000002这类文件。实用理解binlog就像银行流水单每一笔交易都有记录几号几点几分发生了什么事都写得清清楚楚。银行要查账、要恢复靠的都是流水单。MySQL要恢复数据、做主从同步靠的也是binlog。1.2 redo logInnoDB的“保险丝”redo log是InnoDB引擎特有的物理日志记录的是“在某个数据页的某个偏移量上做了什么修改”。它解决的问题是崩溃恢复假如数据库在写入磁盘的过程中突然宕机内存里已经修改但还没落盘的数据就会丢失InnoDB需要用redo log把这些修改重新做一遍保证已提交事务不丢。这里就牵扯出InnoDB一个非常核心的机制——WALWrite-Ahead Logging预写日志。你在InnoDB里做一次UPDATE不是先改磁盘上的数据页而是先在内存的Buffer Pool里改同时把这次修改记录到redo log的缓冲区然后在合适的时机刷到磁盘上的redo log文件。等redo log落了盘这个事务就可以算提交成功了。数据页本身可以慢慢刷盘不急于这一时。使用生活类比来说redo log就是飞机的黑匣子。飞机飞行途中系统异常、引擎罢工事后调查靠的就是黑匣子里的飞行数据记录。MySQL宕机后靠redo log把所有已提交但没来得及写进磁盘的改变重放一遍不让数据丢。有一点要特别注意redo log记录的是“物理修改”不是“完整SQL”。它的大小是固定的采用循环写的方式写满后老记录会被覆盖所以它只在数据库崩溃恢复那一刻起作用不能拿去回放历史。1.3 undo log事务回滚与MVCC的底牌undo log名字叫“撤销日志”一听就知道是拿来撤销操作的。它记录的是事务里每一步操作的反向操作INSERT对应DELETEDELETE对应INSERTUPDATE对应UPDATE回来。事务回滚时InnoDB就顺着undo log把操作反向执行一遍把数据恢复到事务开始之前的样子。但undo log的价值远不止“回滚”这么简单。它是InnoDB实现MVCC多版本并发控制的基础。MySQL默认的隔离级别是REPEATABLE READ一个事务里执行同一条SELECT读到的结果必须一致。它是怎么做到的靠的就是undo log构建的版本链当一行数据被多次修改时各个版本的记录会通过指针串联成一个版本链事务读取数据时根据自己启动时看到的版本号在版本链上找到对应版本的数据。生活里最接近的比喻是保存多份草稿你写文章每改动一次就保留一个版本点“撤销”能回到上一个版本点“历史记录”能看到每次改了什么。undo log就是让数据保持多个版本让不同事务能读到各自该看的版本互不干扰。我们整理一下三个日志的分工差异看下面这张表就清楚了对比项binlogredo logundo log所属层Server层InnoDB引擎层InnoDB引擎层记录内容SQL逻辑操作物理页修改反向操作逻辑写入时机事务提交时事务执行过程中持续写事务执行过程中持续写用途主从复制、数据恢复崩溃恢复、保证持久性回滚、MVCC版本控制文件形态多个滚动文件固定大小循环写表空间内/独立undo文件能否删可以清理历史文件自动覆盖由purge线程自动清理2. 三者的核心机制与配合逻辑——单看没感觉配合起来才精彩2.1 为什么需要“两阶段提交”很多人在面试时被问到一个经典问题为什么binlog和redo log的写入需要两阶段提交这个设计背后其实藏着一个非常实际的痛点两个日志分别由Server层和InnoDB层各自维护如果不做协调数据库崩溃时很容易出现“数据不一致”。举个例子。假设你先写binlog、再写redo logbinlog写完、redo log还没写时崩溃了备库拿binlog回放发现这条事务已存在但主库重启后用redo log恢复却发现这个事务并没有提交成功。这就导致主备数据不一致。反过来先写redo log再写binlogredo log写完、binlog没写时崩溃主库重启后事务恢复成功但备库没有这条记录还是不一致。两阶段提交是这样解决的事务执行过程中InnoDB先把redo log写入并标记为“prepare”状态。Server层写完binlog并且在binlog里标记事务已提交。InnoDB收到Server层确认后把redo log的状态更新为“commit”。这个流程保证了一个关键结论binlog里存在的事务redo log里一定也提交了redo log里提交了的事务binlog里一定也有记录。崩溃恢复时InnoDB只需要检查每个prepare状态的redo log事务对应的binlog是否完整存在存在就提交不存在就回滚。两个日志口径一致主备数据才能保持一致。这里真正要理解的是MySQL把“事务提交成功”的定义绑定在了binlog写入成功上。为什么因为主从复制是异步的备库赖以同步的依据就是binlogbinlog都没写成功就意味着这个事务不应该在复制链路里存在。2.2 崩溃恢复时MySQL到底做了什么既然提到崩溃恢复就展开说一下InnoDB恢复的完整过程。数据库进程崩溃或机器掉电后内存里所有数据都会丢失恢复完全依赖磁盘上已经存在的redo log和binlog。InnoDB启动时会从最近的检查点开始扫描redo log文件把日志里记录的所有已提交事务的修改重新刷到磁盘的数据页上。这个操作叫REDO阶段也就是“重做”。对于状态是prepare但binlog里找不到对应记录的事务InnoDB会执行回滚操作把这些数据恢复原状这个叫UNDO阶段。值得注意的是这个UNDO是恢复过程中的回滚和事务执行时的回滚走的是同一套机制。恢复完成后所有已提交事务的数据都在磁盘上所有未提交或已回滚的事务也不会留下半成品数据数据库对外表现为一个一致的状态。这里有必要说清楚一个误区很多人以为binlog参与了崩溃恢复时的数据重放其实不对。崩溃恢复只靠redo log完成binlog是在主从复制场景里给备库用的。主库自己恢复并不依赖binlog。所以一句话总结就是redo log保证主库自己不丢数据binlog保证备库能追上主库二者配合保证整个集群不丢数据。2.3 undo log如何支撑MVCC版本链MVCC是InnoDB实现高性能并发读的核心很多业务系统能一边写一边读不锁死全靠它。要理解MVCC就得先理解undo log里版本链的构成。InnoDB每行数据里都藏着两个隐藏列一个叫DB_TRX_ID记录最后修改这行数据的事务ID另一个叫DB_ROLL_PTR指向undo log中该行上一个版本的记录。每次对这行做UPDATE或DELETEInnoDB不会直接覆盖旧数据而是把当前版本的数据先写一份到undo log然后在数据页上生成新版本同时把新版本的DB_ROLL_PTR指向undo log里的旧版本。这样就形成了一条从最新版本延伸到最老版本的链表。事务执行快照读时会拿自己的视图ReadView去比对版本链上每个记录的DB_TRX_ID找到第一个比自己视图更早的版本返回。版本链上太老的数据没人用了之后后台的purge线程会定期清理释放空间。这一机制直接回答了“为什么MySQL在REPEATABLE READ隔离级别下同一个事务里反复SELECT结果都一样”因为事务第一次SELECT时生成了视图后续所有读取都基于这个视图去找版本别的提交根本不在视野里。3. 实操配置与场景落地——把机制用起来才算真懂3.1 三个日志相关的关键参数怎么配理论说得再多最后还是得落到配置上。我按三类日志挑几个生产环境中最重要的参数讲每个参数后面都会说明推荐值和理由。binlog相关的核心参数log_bin开启binlog的开关配置文件中写log_binON并指定log_bin/data/mysql/binlog/mysql-bin路径最好单独放一个磁盘。binlog_formatbinlog的记录格式有STATEMENT、ROW、MIXED三种。现代版本建议直接用ROW记录每行数据的变化虽然占用空间大一点但数据最准确不会出现某些SQL在备库回放结果不一致的问题。binlog_row_image控制在ROW格式下记录多少列的数据推荐设置为minimal只记录实际变化的列和主键列能把binlog体积显著降下来。max_binlog_size单个binlog文件最大体积默认1GB一般保持默认。文件太大虽然数量少但备库拉取时的断点恢复粒度粗太小又会产生大量文件增加管理和清理成本。expire_logs_days或binlog_expire_logs_secondsbinlog过期自动清理时间前者以天为单位后者以秒为单位MySQL 8.0支持后者。这个值要结合备份策略和业务容忍度来设定一般建议至少保留2到3天以便出问题时还有日志可挖。redo log相关的关键参数innodb_flush_log_at_trx_commit控制事务提交时redo log的刷盘策略。最容易理解的三个值0表示每秒刷一次盘崩溃可能丢最近一秒的事务1表示每次提交都刷盘不丢数据2表示提交时只写入操作系统缓存每秒再刷盘崩溃时可能丢最近一秒数据但MySQL进程挂了不会丢。生产环境默认就设1在数据面前别省那点性能除非你明确能接受小概率丢数据。innodb_log_file_size每个redo log文件的大小。设置太小会导致刷盘频繁、性能下降设置太大则崩溃恢复时间变长。线上一般建议设成256MB或512MB起步具体要结合业务写入量观察。innodb_log_files_in_groupredo log文件组里文件的数量配合上面的参数决定了redo log总量。8.0.30版本之后参数改成了innodb_redo_log_capacity直接指定redo log总容量配置更直观。undo log相关的关键参数innodb_undo_tablespacesundo log表空间数量MySQL 8.0默认已经使用独立的undo表空间一般不用动。innodb_max_undo_log_sizeundo log表空间的初始大小上限当undo log超过这个值时会触发truncate操作。innodb_undo_log_truncate是否自动清理过大的undo log建议保持开启。长事务会持续占用undo log空间导致版本链超长、purge不及时进而引发undo膨胀这个参数就是应对这种情况的。3.2 误删数据后怎么靠binlog和时间点恢复误删数据是DBA最怕的事故之一但一旦成了事故能不能救回来核心就看binlog策略和恢复的操作手法。我用一个典型场景走一遍流程假设今天10点整有人误执行了DELETE FROM orders WHERE create_time 2024-01-01把不该删的历史订单全删了现在要把数据恢复到10点之前的状态。第一步确认当前binlog格式。如果binlog_format是STATEMENT那恢复会麻烦一些因为你只知道当时执行了这条DELETE语句但不知道它具体影响了哪些行。如果binlog_format是ROWbinlog里会记录每一行被删除前后的完整镜像恢复时可以直接反着把行插回去。这也是为什么我前面强烈推荐ROW格式的原因。第二步找到操作发生的时间点对应的binlog文件位置。用SHOW BINLOG EVENTS IN mysql-bin.000023查看日志内容或者用mysqlbinlog工具配合--start-datetime和--stop-datetime参数定位到这个DELETE事务在binlog中的具体位置记下来。第三步解析并反转日志。如果用的是ROW格式且binlog_row_image是full日志里同时有删除前的镜像。常见的做法是使用mysqlbinlog把binlog解析成SQL文本然后人工或借助工具把DELETE转换为INSERT。更高效的做法是借助开源工具binlog2sql它能直接生成反向SQL。第四步执行恢复。把转换完的SQL在目标实例上回放注意恢复前一定要确认目标实例的数据状态避免把正在改动的数据搞乱。另外强烈建议用临时实例做恢复演练确认无误后再对线上执行不要上来就直接操作主库。整个流程里最容易踩的坑有两个一个是binlog没保留到事发时间点那就只能恢复到最近一次备份损失会更大另一个是用了STATEMENT格式没记录数据行恢复只能靠猜测基本不现实。所以现在就应该去检查一下你们的binlog_format和保留策略别等出事了再后悔。3.3 binlog日志可以删除吗——清理策略与风险控制“binlog日志可以删除吗”这个问题几乎是每个MySQL新手都会问的。可以删但要清楚删除的条件和风险。binlog是追加写的滚动文件旧的binlog如果确认不再需要完全可以手动删除或配置自动清理。自动清理配置刚才提到过就是binlog_expire_logs_seconds设置binlog文件的过期时间。系统在写入新日志或轮转时会检查过期文件自动删除。手动删除使用PURGE BINARY LOGS TO mysql-bin.000020或PURGE BINARY LOGS BEFORE 2024-01-01 00:00:00只删除指定位置之前的文件。这里有一个很重要的风险点binlog可能正在被备库读取。备库IO线程从主库拉取binlog时有自己的读取位置如果主库把这个位置之前的binlog清理掉了备库就会报错找不到binlog主从复制中断。所以在清理binlog之前必须确认所有从库都已经消费了要删除的日志。建议先登录备库执行SHOW SLAVE STATUS查看Master_Log_File和Exec_Master_Log_Pos确保这两个位置已经越过要删除的binlog文件。另外手动删除binlog时千万别直接用rm命令删文件也不要直接操作系统层面的文件。原因很简单MySQL的binlog文件有索引文件记录当前活跃的列表直接删文件会造成索引不一致后续可能引发复制、恢复时的各种问题。正确姿势是用PURGE命令或者通过配置保留策略让系统自动清理。3.4 undo log膨胀与长事务——被忽视的隐形炸弹相对于binlog和redo logundo log在业务侧的可见性低很多但它引发的故障一点不少。最常见的问题就是事务内大量更新后不提交或者干脆是个长事务开着不关导致undo log不断累积undo表空间急剧膨胀。更直接的影响是版本链太长SELECT查询需要遍历多个版本才能找到目标数据性能肉眼可见地恶化严重时甚至会撑爆磁盘。排查思路很简单用SHOW ENGINE INNODB STATUS查看当前活跃事务信息重点看事务执行时间和undo log使用情况。实际工作中我也见过因为程序里忘记提交事务直接把undo表空间撑到几十GB的真实案例。解决方法是配置innodb_undo_log_truncate自动清理同时更重要的是在应用层治理长事务把事务体量控制在小范围、快提交、快释放。4. 常见问题与排查技巧实录——把我在一线踩过的坑都翻出来4.1 常见问题速查表问题现象可能原因排查思路处理建议数据库宕机重启后已提交事务丢失redo log刷盘策略被改成0且没开启其他保护检查innodb_flush_log_at_trx_commit配置改回1数据安全优先备库收不到主库的binlog主库未开启binlog或binlog被误清查log_bin配置确认备库IO线程状态开启binlog重新同步主从复制延迟越追越大备库回放binlog的性能跟不上或单事务过大查备库的Seconds_Behind_Master观察回放线程状态排查慢SQL考虑并行复制策略误删了数据但binlog已经过期清理binlog保留时间太短检查binlog_expire_logs_seconds延长保留时间至少覆盖备份周期undo表空间持续增大长事务未提交purge线程跟不上查SHOW ENGINE INNODB STATUS中的事务列表治理应用层事务开启undo自动truncatebinlog文件占满磁盘保留策略设置不当或文件增长过快看磁盘使用率和binlog文件数量调整过期参数清理已同步文件4.2 日志文件损坏或文件找不到的恢复思路日志文件损坏是最让DBA头大的故障之一。比如redo log文件因为磁盘坏道出现损坏InnoDB启动时直接拒绝启动。遇到这种情况首先要知道它的严重性redo log损坏意味着可能丢失部分已提交事务的数据所以不要轻易直接删redo log文件重启这会破坏数据一致性。正确思路是先评估损坏程度。如果只是某一个redo log文件损坏尝试把损坏的文件移走让InnoDB基于剩余的redo log文件恢复但这会跳过部分日志可能丢失最后几秒的事务。更可靠的办法是如果有备份数据就从备份恢复并配合binlog追平到故障点。这里也就解释了为什么binlog必须配置合理保留策略——它是redo log之外第二道保险。binlog文件如果意外损坏回放数据时可能会在中途报错。此时可以用mysqlbinlog配合--stop-never或者指定--stop-position跳过损坏点先把能恢复的数据恢复出来再评估具体损失。4.3 几个少有人提但很实用的小技巧第一个小技巧用FLUSH LOGS命令手动滚动binlog。在做大版本升级或者准备做备份前先执行一次这个命令新开一个binlog文件这样备份只需要保留最新的文件历史文件可以安心清理。第二个小技巧定期用mysqlbinlog抽样查看binlog内容。我不建议频繁全量解析binlog太耗资源但可以定期在业务低峰期抽样最近一个binlog的前几百条事件看看到底有没有奇怪的DML语句比如深夜出现的UPDATE没有WHERE条件。这是一条很实用的“审计”途径操作成本极低却能尽早发现异常。第三个小技巧启动前把redo log和binlog的刷盘参数固定到配置文件中而不是靠SET GLOBAL在线修改。在线修改只对当前运行实例生效重启就恢复默认值很容易造成“改了但没完全改”的假象。第四个经验了解你的备份恢复时间目标RTO和数据恢复点目标RPO。如果你所在的公司对数据丢失零容忍那innodb_flush_log_at_trx_commit必须是1binlog保留时间必须覆盖从最近一次全量备份到当前的时间跨度。这是一个成本权衡问题但DBA有责任让所有相关方清楚知道你选择了某套配置就意味着接受了某种程度的风险。5. 最后再聊几句关于日志和架构的思考写到这里三大日志的机制和实操都梳理了一遍。我自己做了这些年数据库最大的体会是对日志机制的理解深度直接决定了一个人处理数据库故障时的底气。遇到问题不慌因为你清楚数据现在处于哪个阶段——是在内存里还没写redo log还是redo log已提交但数据页没刷盘还是binlog已写到哪个位置、备库追到了哪个位置。能回答这些问题你就有了判断故障和处理故障的基础框架。还有一个经验值得单独提定期做备份恢复演练。很多人觉得配置了备份就万事大吉从来没真正测试过一次从全量备份和binlog增量恢复的完整流程。真到出事那天才发现备份是坏的、binlog少了一段、恢复脚本有语法错误那才是真正的绝望。我建议每季度至少做一次完整的恢复演练把从备份到binlog追平再到数据校验的全过程跑通顺便把恢复文档里的坑更新一遍。最后分享一个小操作习惯每次做结构变更或者批量数据修正之前先FLUSH LOGS手动滚动一次binlog记下当前日志文件和偏移量。这样出问题时可以精准定位变更前后的日志切割点恢复操作的目标范围会小很多速度也快很多。这个习惯几乎不花成本但能在关键时刻省下大量时间。MySQL日志这块内容越往深挖越有意思也越能理解这个数据库为什么能在各种极端场景下保持数据不丢、业务不停。你可以从今天就开始检查一下所在环境的binlog格式和保留策略、redo log刷盘参数、undo膨胀风险把这些基础做扎实比学再多花哨的优化技巧都管用。
返回列表