☰
MySQL六类日志核心原理:错误日志、慢查询日志、binlog与redo log
2026/10/7 11:07:45 网站建设 项目流程

先说个场景。某天凌晨,线上一个 MySQL 实例突然性能断崖式下跌,CPU 用完,连接数被打满。我接到电话后第一件事不是看监控,而是打开错误日志和慢查询日志。十分钟定位到一条因为统计信息过期导致走错索引的 select,立刻用FORCE INDEX救火,再顺手做了一次ANALYZE TABLE。事后同事问我,为什么能这么快?我说,MySQL 的日志就是它的飞行记录仪和黑匣子,你平时不研究它,出故障时它就给你颜色看。

这篇文章我想把 MySQL 日志体系里最核心的六类日志一次讲透:错误日志、慢查询日志、通用查询日志、二进制日志(binlog)、中继日志(relay log)、事务日志(redo log 和 undo log)。内容包括每类日志的产生原理、实际配置、查看方式和排障思路,以及我这些年踩过的坑。适合刚接触 MySQL 的运维和开发,也适合干了几年但一直对日志体系只有零散认识的人,按这个框架把这六类日志串起来,你会对整个数据库的运行机制有全新的理解。

1. 先把六大日志的分工理清楚

1.1 日志分层的本质

很多人把 MySQL 日志当成“出问题才看”的东西,这是最大的误解。要理解日志,先要理解 MySQL 的分层架构。MySQL 大致分两层:上面是 Server 层,负责连接管理、SQL 解析、优化、执行;下面是存储引擎层,InnoDB 是默认引擎,负责数据页的读写、事务、锁、崩溃恢复。

日志也按照这个逻辑分。Server 层产生的日志包括错误日志、通用查询日志、慢查询日志、binlog;存储引擎层产生的日志包括 redo log、undo log。这两个层面的日志解决的问题完全不一样:Server 层日志记录“谁来了、做了什么、做得慢不慢”,面向人排障用;InnoDB 层日志记录“数据到底怎么改、改了之后怎么保证不丢”,面向机器恢复用。

搞清这个分层,很多模糊的概念会自动清晰。比如 binlog 在 Server 层,不管用什么存储引擎都有;redo log 在 InnoDB 层,换成 MyISAM 就没有。所以“没有 binlog 能不能恢复数据”“redo log 能不能删”这类问题的答案,往下看自然就有了。

1.2 一张表看懂六类日志

我习惯用下面这张表快速向新人讲清楚六类日志的定位,建议你也保存一份,遇到问题先想想它属于哪一层、由谁产生、该看哪份日志。

日志类型产生位置默认状态记录内容核心用途
错误日志 error logServer 层开启启动、运行、复制过程中的错误信息排障第一入口
通用查询日志 general logServer 层关闭所有客户端请求和 SQL调试、审计
慢查询日志 slow logServer 层可配置开启超过执行阈值的 SQLSQL 性能优化
二进制日志 binlogServer 层需配置开启数据变更的逻辑或行级记录主从复制、时间点恢复
中继日志 relay logServer 层从库自动开启从主库拉取的 binlog 事件主从复制中转
事务日志 redo/undoInnoDB 层自动管理重做记录、回滚记录崩溃恢复、事务回滚、MVCC

记住这张表之后,再往下看每个日志的具体操作。实际操作中,你 80% 的排查工作会集中在错误日志、慢查询日志和 binlog 上,剩下两类日志是你理解 InnoDB 事务和主从复制的根基,绕不开。

2. 错误日志与通用查询日志:最容易被忽略的“现场记录仪”

2.1 错误日志:启动失败和运行异常的第一现场

错误日志是 MySQL 默认就开的,记录启动、停止、运行中的错误,比如端口冲突、权限不足、表损坏、主从复制异常、InnoDB 恢复失败。出问题第一步永远是看它。

启动报错时最常见的操作是:

tail -50 /var/log/mysql/error.log

比如端口被占用时,错误日志里会出现Can't start server: Bind on TCP/IP port: Address already in use;数据目录权限不对时,会出现Permission denied;InnoDB 数据文件损坏时,会出现[ERROR] InnoDB: Database page corruption on disk。

配置项是log_error,可以指定文件路径,也可以指定到系统日志。生产环境建议独立文件,方便轮转和检索。8.0 版本里还有log_error_services,可以控制错误日志的过滤和格式,比如把错误日志格式化成 JSON,方便日志采集系统接入。不过对绝大多数场景,默认格式就够用。

踩过的坑:曾经有台服务器/分区写满,mysqld 启动时报Disk is full writing './ib_logfile0',但你以为只是普通报错,清理了些无用文件却发现 MySQL 仍然启动不了。实际上错误日志也写不进磁盘了,会有部分日志丢在校验环节。后来我把错误日志单独放到一个分区,数据目录另一个分区,避免“日志把磁盘写满”和“日志无法写入导致故障排查盲区”这种雪上加霜的情况。

2.2 通用查询日志:所有请求的“监控录像”

通用查询日志和慢查询日志不一样,它不关心性能,它记录“每一个发到 MySQL 的请求”,包括连接建立、断开、每一条 SQL 的原文。默认关闭,因为记录量极大,生产环境开启会显著增加磁盘 IO。

但是它在两类场景里特别有用。一是排查“开发框架到底发了什么诡异 SQL”,比如应用日志里没打印 SQL,你怀疑 ORM 有错误逻辑,打开 general log 看几分钟,所有请求原形毕露。二是审计场景,某个账号半夜执行了什么操作,general log 全部有记录。

临时开启方式:

SET GLOBAL general_log = 'ON'; SET GLOBAL general_log_file = '/var/log/mysql/general.log';

用完立刻关掉,否则日志文件会在几小时内涨到几十 GB。我见过有同事开了 general log 忘记关,第二天磁盘报警,那个时候日志文件甚至比整个数据目录还大。注意 general log 记录的是文本 SQL,可能包含业务敏感信息,导出和分析时注意脱敏。

2.3 一个磁盘满的排查实例

有一回主库写入报错ERROR 3 (HY000): Error writing file,我通过错误日志看到No space left on device。磁盘满时第一反应不是删数据文件,而是看哪些日志可以安全清理。当时定位到三个大文件:binlog 几十 GB、慢查询日志几个 GB、错误日志也几个 GB。

处理顺序是:先确认没有从库在拽旧 binlog,然后PURGE BINARY LOGS BEFORE NOW() - INTERVAL 1 DAY清理过期 binlog,再重命名慢查询日志和错误日志并用FLUSH LOGS重新创建。全程业务无感知,磁盘马上释放。这就是为什么日志分类清晰很重要——同样名字都是“日志”,有的可以放心删,有的删了会出大事故。

3. 慢查询日志:SQL 性能优化的第一抓手

3.1 从零开启慢查询日志的完整配置

慢查询日志是性能排查里用得最多、最直接的日志。它专门记录执行时间超过阈值的 SQL,阈值由long_query_time控制,单位是秒。

最简单的开启方式:

SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = 'ON';

注意三个坑:long_query_time修改后,当前已连接的会话不生效,必须新建连接才会用新值;long_query_time最小可设到 0,0 表示记录所有 SQL,非特殊调试不要这么干;log_queries_not_using_indexes会把没有走索引的 SQL 也写进去,这个参数开启后慢日志量可能急剧增加,但能帮你发现大量被隐藏的烂 SQL。

如果要持久化,写进 my.cnf:

[mysqld] slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = 1

重启后生效。线上实例建议用SET PERSIST而不是直接改 my.cnf,避免重启时配置被覆盖。8.0 的SET PERSIST slow_query_log = 'ON'会写入mysqld-auto.cnf,即使实例重启也仍然保留。

3.2 慢查询日志的两种分析姿势

慢查询日志里每一条 SQL 都包含执行时间、锁等待时间、扫描行数、返回行数。比如下面这条:

# Query_time: 2.500 Lock_time: 0.000 Rows_sent: 10 Rows_examined: 1000000 SET timestamp=... SELECT * FROM orders WHERE user_id=123 AND status=1;

Query_time2.5 秒,Rows_examined100 万,Rows_sent只有 10,说明这条 SQL 扫描了太多行,极可能缺联合索引。分析时不能只看Query_time,重点看Rows_examined和索引使用情况。

系统自带mysqldumpslow可以简单聚合:

mysqldumpslow -t 10 /var/log/mysql/slow.log

按总耗时取前 10 条。如果你要更详细的分析,建议用 Percona Toolkit 的pt-query-digest:

pt-query-digest /var/log/mysql/slow.log > slow_report.txt

它会自动把同类的 SQL 聚合到一起,按总耗时、平均执行时间、出现次数排序,还能看每个 SQL 的响应时间分布。我平时定位线上慢 SQL,都是先跑这个工具,再针对 Top 5 逐条看执行计划,效率比手动翻日志高一个量级。

3.3 慢查询日志容易踩的坑

第一个坑是日志轮转。慢查询日志一旦开启,文件会不停变大,很多文章推荐直接把日志文件删掉,但 MySQL 进程还持有旧文件句柄,你删了以后空间不会释放,新日志也写不进去合理的位置。正确做法是重命名日志文件,然后执行FLUSH LOGS让它重新创建新文件。

第二个坑是主从环境里,在主库开log_queries_not_using_indexes会产生大量日志,如果从库也同步配置,两边的慢日志都会剧增。更严重的是,主库慢日志写盘占用了 IO,可能让本来就慢的 SQL 雪上加霜。开启后要观察磁盘 IO 和Slow_queries状态值,发现异常及时调整阈值。

第三个坑很隐蔽:有很多查询本身执行时间不长,但频繁调用,每秒几千次,单次 0.1 秒,累计对 CPU 的消耗非常可观。慢查询日志默认只认单条执行时长,抓不到这类高频短查询。要看这类问题,我建议结合 performance_schema 的events_statements_summary_by_digest看总的调用次数和平均延迟,把慢日志和 performance_schema 结合起来用,才不会被单条慢 SQL 带偏了方向。

4. 二进制日志与中继日志:主从复制和数据恢复的生命线

4.1 binlog 三种格式,生产环境为什么必选 ROW

binlog 是 MySQL 里最关键的日志之一,它记录所有数据变更操作,主从复制靠它,基于时间点恢复靠它,很多数据同步工具也靠它。binlog 有三种格式,这里我直接说结论:生产环境用 ROW,不要跟着老教程继续用 STATEMENT。

格式记录内容优点缺点
STATEMENTSQL 语句原文日志量小非确定性函数、limit 删除等易导致主从不一致
ROW每一行数据变更前后的值最精确、可回溯日志量大
MIXED自动选择 STATEMENT 或 ROW兼顾两者存在意外切换,行为不可控

为什么 STATEMENT 危险?举个经典例子:主库执行DELETE FROM t WHERE id>100 LIMIT 1,如果主从数据原本就略有差异,从库执行这条 SQL 删掉的可能是另一行。再比如UUID()、NOW()这类函数,每台机器的执行结果都不同,复制到从库就会不一致。ROW 格式不记录 SQL 原文,而是记录“某一行被改成了什么”,主从执行结果必然一致。

MySQL 8.0 默认已经是 ROW 格式。查看当前格式:

SHOW VARIABLES LIKE 'binlog_format';

解析 ROW 格式 binlog 时,直接看是看不懂的,要这样用 mysqlbinlog:

mysqlbinlog --no-defaults --base64-output=DECODE-ROWS -v mysql-bin.000123

加-v之后,会显示### UPDATE \db`.`t`` 这样的伪 SQL,展示每一行的变更前值和变更后值,极其直观。这个参数我一年得用几百次,强烈建议记下来。

4.2 binlog 管理:保留时间、自动清理与手动清理

binlog 会一直累积,所以必须设置保留时间。8.0 用参数控制:

SET GLOBAL binlog_expire_logs_seconds = 86400;

保留 24 小时。手动清理用PURGE:

PURGE BINARY LOGS TO 'mysql-bin.000123'; PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;

这里有一条铁律:不要直接用rm删除 binlog 文件。binlog 的索引文件会记录所有现存 binlog 文件名,你手动删了文件,MySQL 启动或复制时发现索引里的文件不存在,会报错。正确姿势永远是使用PURGE命令。

判断 binlog 能不能删,必须确认三点:一是没有从库还在使用要删的 binlog,可以用SHOW REPLICA STATUS看Relay_Master_Log_File字段;二是保留时间满足备份策略,比如每天凌晨做全备,binlog 至少保留一个全备周期,否则全备之后到当前时刻的数据变更没法补;三是数据同步工具(阿里 Canal、Flink CDC、canal 类的程序)是否还依赖旧 binlog,它们的位点也需要提前核对。我曾经因为只看主库、没看从库位点就 PURGE,导致从库复制断了好几个小时,最后靠重新备份恢复,那叫一个狼狈。

4.3 中继日志:复制链路里最容易忽略的一环

relay log 只在从库上存在。主从复制的原理是:从库的 I/O 线程把主库 binlog 拉下来,写入本地 relay log,然后 SQL 线程读取 relay log 并执行。所以 relay log 本质上就是“复制链路上的中转站”。

第一次搭主从复制的人,经常盯着主库的 binlog 看半天,却忘了从库的 relay log。实际上复制的延迟和中断,很多问题都出在 relay log 这一环。排查从库状态的基本命令:

SHOW REPLICA STATUS\G

重点看Replica_IO_Running、Replica_SQL_Running、Last_IO_Errno、Last_SQL_Errno这几个字段。如果Replica_SQL_Running为 No,relay log 可能一直在堆积,I/O 线程还在继续拉主库日志,最终把从库磁盘塞满。

参数relay_log_recovery建议打开,它能让从库在崩溃重启后丢弃没有执行完的 relay log,重新从主库拉取,避免因 relay log 损坏导致复制异常。处理 relay log 堆积的正确流程是先解决 SQL 线程报错,再清理;不要一上来就删 relay log,否则会丢失尚未执行的复制事件。只有当你确认从库已经不需要恢复、准备重新搭建复制的极端情况下,才用RESET REPLICA ALL重置整条链路。

5. 事务日志:redo log 与 undo log 为什么是 InnoDB 的命根子

5.1 redo log:先写流水账,崩溃也能重放

redo log 解决的核心问题是“已提交事务的数据不能丢”。InnoDB 的数据是存在磁盘上的,但修改发生在内存 Buffer Pool 中。如果每次修改都立刻写回磁盘,随机写性能会低到没法用,所以 InnoDB 采用 WAL(Write-Ahead Logging)机制:先把修改记录到 redo log,再慢慢把脏页刷回磁盘。

我用记账来打比方。公司每天大量收支,会计不会每笔业务都立刻翻总账改数字,而是先记在流水账本上,晚上再统一誊到总账。如果中途账本烧了,靠流水账还能重新誊一遍。redo log 就是这个流水账,不过它记录的是“哪个数据页的哪个位置被改成了什么”,是物理级别的日志。

redo log 是循环写入的,文件大小固定,写满后会触发脏页刷盘,推进 checkpoint。MySQL 8.0.30 之前用innodb_log_file_size和innodb_log_files_in_group配置,默认是两批每批 48MB;8.0.30 之后统一为innodb_redo_log_capacity,默认 100MB,可以动态调整。redo log 太小会导致频繁刷脏页,写性能下降;太大会让崩溃恢复时间变长。我个人的调优经验是:从默认值开始,观察SHOW ENGINE INNODB STATUS里的redo log write相关状态,如果日志写入很频繁且每次写入量都接近容量,就调大。

崩溃恢复是怎么发生的?MySQL 启动时会比较数据页的 LSN 和 redo log 里记录的 LSN,凡是已经写入 redo log 但还没刷到数据页的修改,就重新放一遍。这样即使断电,已提交事务的数据也不会丢。

5.2 undo log:回滚和 MVCC 都靠它

undo log 和 redo log 正好相反:redo log 记录“改成了什么”,undo log 记录“改之前是什么”。事务回滚时,InnoDB 通过 undo log 把数据恢复成修改前的样子;更重要的是,MVCC 多版本并发控制也靠 undo log。其他事务做普通查询时,如果需要读取历史版本的数据,就从当前版本沿着 undo log 构建出旧版本。

同样可以用生活类比:redo log 像微信聊天记录里的“对方撤回了一条消息”之后的重新补发机制,数据能恢复;undo log 更像文档编辑器的撤销历史,一步一步往前倒,还能看到每个历史版本。

实际运维中,长事务是 undo log 的最大杀手。一个事务长时间不提交,它创建的 undo log 就不能被 purge 线程清理,undo 表空间会一直膨胀。表现是磁盘占用不断上涨,但你又不能随便 kill 这个事务,因为它可能正在执行大查询或者锁定重要数据。排查长事务用:

SELECT * FROM information_schema.innodb_trx\G

看trx_started和trx_rows_modified,找到启动时间最早、修改行数最多的事务,再定位到具体会话去处理。8.0 默认使用独立的 undo 表空间,支持自动截断,开启innodb_undo_log_truncate可以帮助回收。

5.3 两阶段提交:redo log 和 binlog 的一致性锁

前面说了,redo log 在 InnoDB 层,binlog 在 Server 层,两者都会在事务提交时写入。如果不协调好,会出现一个极端但必须避免的情况:binlog 里有这条 SQL,但 redo log 里没有;或者反过来。主从复制和崩溃恢复时,两个日志各说各话,数据库就乱了。

InnoDB 用两阶段提交解决这个问题。事务提交时:先写 redo log 并标记为 prepare 状态,然后写 binlog,最后再写 redo log 并标记为 commit 状态。崩溃恢复时,如果发现 redo log 是 prepare 状态,就去查 binlog 里有没有对应的记录:有则说明事务已经完整写入 binlog,认为它已提交;没有则回滚。这套机制保证了 binlog 和 InnoDB 数据的一致性,主从复制链路上每个从库看到的数据变更才不会有缺口。

参数上,控制刷盘节奏的是innodb_flush_log_at_trx_commit和sync_binlog。两者都设成 1,是最安全的配置,每次事务提交都把 redo log 和 binlog 刷到磁盘,性能损耗最大;网络环境和磁盘可以接受的话,我建议保持这个配置。如果追求性能,可以把innodb_flush_log_at_trx_commit设为 2,表示每秒刷一次盘,性能提升明显,但极端断电可能丢失最后一秒的事务,这个 trade-off 一定要想清楚。

6. 日志相关的常见问题与排查经验

6.1 日志文件过大、删不掉怎么办

日志过大的问题几乎每个 MySQL 实例都会遇到。我按日志类型给一套标准处理流程:慢查询日志、错误日志、通用查询日志都是文本日志,可以先mv重命名,再执行FLUSH LOGS让 MySQL 重建新文件;binlog 用PURGE BINARY LOGS BEFORE;relay log 先确认复制正常再处理,不要硬删。

很多人在 binlog 上出问题,都是因为误信了“直接删除 .000001 文件就能释放空间”的说法。真删了之后,MySQL 启动时检查 binlog 索引或者复制读取时发现文件不存在,错误日志直接报Could not find first log file name in binary log index。而且你自己手动删过的文件,用PURGE命令也没办法修复索引,只能把对应条目从 index 文件里手工剔除,风险极高。所以说到底,binlog 清理认准PURGE。

如果是从库重启后一直报 relay log 相关错误,优先检查relay_log_recovery有没有开启。打开这个参数后,从库启动时会自动丢弃旧 relay log 并重新拉取,能省去很多人工处理的时间。

6.2 主从复制异常时怎么快速定位

复制异常是 DBA 最常遇到的事故之一。快速定位的路径我总结为三步:

第一步看SHOW REPLICA STATUS\G,确认Replica_IO_Running和Replica_SQL_Running的状态。IO 线程断了,大概率是网络问题或者主库 binlog 被清理,从库的位点缺失;SQL 线程断了,大概率是正在执行的 SQL 在从库上报错,比如主键冲突、表不存在。

第二步看错误日志。复制错误往往会同步写到 MariaDB 容器、监控系统等?其实错误日志就在 error log 里,直接tail -100 error.log通常能找到Got fatal error 1236之类的原因。Got fatal error 1236经常对应“从库请求的 binlog 位点已经不在主库上”,这时需要重新对齐主从数据,从库几乎不可能从断点自动恢复。

第三步看Performance_schema里的复制线程表:

SELECT * FROM performance_schema.replication_applier_status_by_worker\G

能精确看到 SQL 线程在哪个 GTID、哪个事务上报错。处理完报错原因后,执行START REPLICA恢复。千万不要一上来就RESET REPLICA ALL重建,那会丢掉从库已经应用的事务,轻则重新初始化,重则引发数据不一致。

6.3 我用得最多的几条排障经验

文章最后,分享几条我在实际维护中沉淀下来的经验。五六年下来,我发现日志排障最大的敌人不是“看不懂日志”,而是“不知道这份日志属于哪一层、解决什么问题”。熟悉六类日志的分工后,故障处理速度会有质的提升。

日常巡检我习惯重点看三个信号:一是错误日志里有没有 InnoDB 报错和复制报错,它们是最严重的前置信号;二是慢查询日志的 Top 10 有没有出现新的 SQL 模式,很多性能恶化都是缓慢累积的;三是 binlog 的保留时间与备份策略是否匹配,这决定了你数据恢复能力的底线。

一个小习惯救过我很多次:每次修改日志相关参数,都要用SHOW VARIABLES确认生效,同时把配置写入 my.cnf 或SET PERSIST。光用SET GLOBAL修改而忘记持久化,实例重启后配置会静悄悄回到旧值,等你下次排查同一个问题时,才发现参数根本没生效,白白浪费几个小时。

另外,线上日志文件的权限也值得注意。mysqld 用户对日志目录必须有写权限,否则启动或轮转时会莫名失败。很多权限问题在错误日志里只会反馈一句Permission denied,跟数据目录权限混在一起,不看上下文很容易走弯路。

日志系统是整个 MySQL 生态里最基础也最强大的一部分。把它研究透,不只能救急,还能让你理解事务原理、复制机制、性能优化的底层逻辑。希望这篇梳理能帮你把这六类日志彻底串起来,下次遇到线上问题的时候,心里有底,手里有招。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询