
很多 DBA 都有过这种时刻——半夜被报警电话吵醒数据库连接数飙满或者干脆直接起不来了。你第一件事做什么不是慌着重启而是翻日志。MySQL 日志文件就是数据库自带的“黑匣子”从启动到崩溃、从正常请求到慢查询它把数据库的完整过程都记了下来。这篇文章围绕错误日志、查询日志、慢查询日志这三类最常用也最实用的日志再加上二进制日志、事务日志等幕后角色把各自的作用、配置方法和排查套路一次讲清楚。不管你刚装好 MySQL 正在折腾配置还是已经上过生产环境被慢查询坑过都值得花十分钟把日志体系过一遍。1. 先搞清楚日志体系六类日志各管什么1.1 为什么 MySQL 需要这么多日志很多人一开始跟我一样以为日志就是“出错了记一笔”的东西。真去翻 MySQL 文档会发现它至少有错误日志、通用查询日志、慢查询日志、二进制日志、事务日志和中继日志六类每类日志解决的是完全不同的问题。把日志理解成三组角色就通了故障排查组错误日志记录发生了什么、什么时候发生、为什么起不来。性能体检组慢查询日志告诉你哪条 SQL 拖了后腿通用查询日志告诉你谁在什么时候执行了什么语句。数据恢复与复制组二进制日志和事务日志负责保证数据不丢中继日志负责在主从复制中传递变更。这三组缺一不可。没有错误日志故障只能靠猜没有慢查询日志性能优化就是玄学没有二进制日志和事务日志数据库遇到断电几乎等于数据灾难。1.2 六类日志的角色分工日志类别记录内容核心用途默认状态错误日志 error log启动/关闭、严重错误、告警、连接异常故障定位默认开启通用查询日志 general log所有客户端执行的 SQL、连接/断开事件审计、调试、行为分析关闭慢查询日志 slow log执行超过阈值的 SQL、未走索引的 SQL性能优化关闭二进制日志 binlog所有数据变更操作语句或行主从复制、时间点恢复官方默认关生产建议开事务日志 redo logInnoDB 物理页面的修改记录崩溃恢复InnoDB 默认开启中继日志 relay log从库接收到的 binlog 事件主从复制复制环境中自动产生从归属来分错误日志、通用查询日志、慢查询日志、二进制日志属于 MySQL Server 层redo log 属于 InnoDB 存储引擎层relay log 是复制组件专属。理解这层归属很重要比如你不知道 redo log 是 InnoDB 的就会误以为“我关了 binlog崩溃恢复也应该没了吧”实际上恰恰相反redo log 和 binlog 是两套独立机制。1.3 日志越多越好先算成本账日志本身要写磁盘每一次写入都消耗 IO。默认状态只开了错误日志其他日志按需打开这不是偷懒而是 MySQL 在设计上就默认了你不会没事把所有日志全开。之前接手过一台测试机前人把 general log 开了一年没关数据目录直接被一个几十 GB 的 general 日志文件塞满磁盘告警。所以日志不是越多越好而是要用的时候开、用完就关或者用日志轮转控制文件大小。这一点后面配置部分会展开。2. 三类“排查日志”深度拆解2.1 错误日志大多数故障的第一现场错误日志记录 MySQL 从启动到关闭的全过程包括正常启动信息、InnoDB 初始化、复制线程状态、连接数告警、权限问题、磁盘满、表损坏、SSL 握手失败等等。生产环境里遇到“数据库起不来”“半夜突然挂了”第一步一定是看错误日志。先弄清楚日志在哪SHOW VARIABLES LIKE log_error;如果没有显式指定日志一般会放在数据目录下命名为主机名.err比如mysql-01.err。Linux 发行版和云厂商往往会在配置文件里把路径指到/var/log/mysql/error.log或/var/log/mysqld.log所以用 SQL 查一下最靠谱。MySQL 8.0 的错误日志格式类似这样2025-06-20T02:15:23.123456Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.0.36) starting as process 12345 2025-06-20T02:15:24.623456Z 0 [ERROR] [MY-010584] [InnoDB] InnoDB: Assertion failure in thread 140242...每一行的结构依次是时间、线程 ID、级别、错误码、所属子系统、内容。时间默认是 UTC如果看着别扭可以设置参数让日志使用本地时间log_timestampsSYSTEM级别控制用log_error_verbosity1 只记错误2 记错误加警告3 再加通知信息。日常建议设为 2 或 3排障时信息量足够又不会被大量 note 淹没。我个人踩过的一个坑systemd 环境下很多人习惯用journalctl -u mysqld看日志但 MySQL 8.0 的日志输出路径取决于log_error配到了文件还是 stderr。如果配了文件journal 里可能只有零星几条真正的完整错误还是在 error log 文件里。两个都看互相印证才能快速定位。2.2 通用查询日志审计利器但别长期开通用查询日志记录客户端发来的每一条 SQL以及连接和断开事件。打开它你就等于给数据库装了一个“录音笔”任何人对数据库做的任何操作都能被回溯。相关配置general_logON general_log_file/var/log/mysql/general.log它长这样2025-06-20T10:30:00.123456Z 18 Query SELECT * FROM user WHERE id 1 2025-06-20T10:30:00.456789Z 18 Quit第一列时间第二列连接线程 ID第三列是命令类型。Query 代表执行了 SQLQuit 代表连接关闭还有 Connect、Init DB、Prepare 等类型。为什么平时别开因为每条 SQL 都写盘压力测试时开 general log吞吐量可能直接掉几个百分点磁盘写入量也肉眼可见地增长。我建议在以下场景临时开启用完立刻关排查应用连接池是不是频繁建立连接排查某条“幽灵 SQL”到底从哪来的开发环境调试 ORM 生成的 SQL 是否符合预期安全审计需要完整执行记录。动态开启和关闭也很简单SET GLOBAL general_log ON; SET GLOBAL general_log OFF;值得注意的是日志输出有两种方式参数log_output可以写成FILE、TABLE或FILE,TABLE。选TABLE时通用查询日志会写进mysql.general_log表可以直接 SQL 查询但表会越涨越大需要定期清理。生产环境我一般只用FILE配合日志轮转简单直接。2.3 慢查询日志让性能问题从“玄学”变“清单”慢查询日志是性能优化里性价比最高的工具。它记录了执行时间超过long_query_time阈值的 SQL还可以记录未使用索引的 SQL。默认阈值是 10 秒但对绝大多数应用来说10 秒才报警已经太晚了生产环境建议压到 1 秒甚至更低。典型配置slow_query_logON slow_query_log_file/var/log/mysql/slow.log long_query_time1 log_queries_not_using_indexesON log_slow_admin_statementsON log_slow_extraON日志内容长这样# Time: 2025-06-20T10:30:00.123456Z # UserHost: app_user[app_user] [10.0.3.28] Id: 88 # Query_time: 3.567891 Lock_time: 0.000123 Rows_sent: 1 Rows_examined: 2356781 SET timestamp1705290600; SELECT * FROM orders WHERE order_no NO20250620001;重点看最后一行前的统计信息Query_timeSQL 总执行时间真正需要优化的数字。Lock_time等待表锁/行锁的时间锁等待过长通常说明并发写有冲突。Rows_sent最终返回给客户端的行数。Rows_examined为了执行这条 SQLInnoDB 扫描了多少行。上面这个例子扫描了 235 万行只返回 1 行典型全表扫描。优化方式无非是加索引、改写 SQL、拆大事务。具体怎么分析后面实战章节会详细说。这里先提醒一个常见误区有人为了“抓得更细”把long_query_time设成 0结果慢查询日志瞬间变成第二个 general log每小时几个 GB最后把磁盘写满。阈值要结合业务一般 OLTP 系统从 1 秒起步如果业务压力小可以调到 0.5 秒不要一味追求“零容忍”。另外long_query_time动态修改后已有连接可能不会立即生效需要重连才会读到新值改完记得验证。3. 数据安全与复制背后的日志binlog、redo log、relay log3.1 二进制日志复制的根基恢复的保险二进制日志也就是 binlog记录所有可能导致数据变更的操作比如 INSERT、UPDATE、DELETE、DDL。它有两种主要格式STATEMENT记录原始 SQLROW记录每行数据的变化MIXED混合两者。生产环境建议用ROW虽然占空间更大但复制更可靠遇到 NOW()、随机数这类语句也能保证主从一致。相关参数log_bin/var/log/mysql/mysql-bin binlog_formatROW binlog_row_imageFULL sync_binlog1 binlog_expire_logs_seconds604800 max_binlog_size256Mlog_bin一旦在配置文件里指定MySQL 就会开启二进制日志。文件以mysql-bin.000001这种编号滚动配套的还有一个索引文件mysql-bin.index。查看当前日志状态和文件列表SHOW MASTER STATUS; SHOW BINARY LOGS;查看某个 binlog 里具体有什么事件SHOW BINLOG EVENTS IN mysql-bin.000001;线上排查时更常用的是mysqlbinlog工具可以直接解析成可读 SQLmysqlbinlog --start-datetime2025-06-20 09:00:00 --stop-datetime2025-06-20 10:00:00 /var/log/mysql/mysql-bin.000001binlog 的过期时间在 MySQL 8.0 里由binlog_expire_logs_seconds控制默认 2592000 秒30 天。如果你的数据目录较小可以调小但别小于你备份周期的最长间隔否则你没法利用 binlog 做时间点恢复。清理 binlog 不要手动删文件而是用PURGE BINARY LOGS TO mysql-bin.000005;这样的命令或者在服务里依赖自动过期手动删文件会导致索引错乱。3.2 InnoDB 的 redo log断电恢复的最后防线redo log 是 InnoDB 特有的物理日志记录的是“某个数据页的某个偏移被改成了什么”。数据库不会每次提交都立刻把数据页落盘而是先写 redo log再在后台慢慢刷脏页。这样即使系统突然断电数据页没来得及落盘重启时 InnoDB 也能靠 redo log 把修改重放一遍保证已提交事务不丢。这个机制跟生活里“先记账、后付钱”很像先写下一笔“待办修改”等有空了再真正完成。只要账本没丢钱就不会乱。MySQL 8.0.30 之前redo log 是固定大小的ib_logfile0、ib_logfile1文件大小靠innodb_log_file_size控制。8.0.30 开始改成了#innodb_redo目录下的#ib_redo*系列文件容量由innodb_redo_log_capacity控制默认 100MB并且支持在线调整。这个改进让“redo 太小导致频繁刷盘”和“redo 太大导致启动恢复慢”这两头问题都更容易处理。崩溃恢复时错误日志里会看到类似 “Starting recovery...” 和 “Recovery completed” 的记录。看到这两个关键字说明 InnoDB 已经通过 redo log 把数据恢复到了一个一致状态不用过度恐慌。真正要警惕的是错误日志里出现 “Database page corruption” 这类信息那才需要立即介入。3.3 中继日志主从复制里的“中转站”中继日志不是独立生成的内容它本质上是从库把主库的 binlog 事件拉取到本地后的临时存储。主从复制流程是这样的主库写 binlog → 从库 IO 线程读取 → 写入 relay log → 从库 SQL 线程读取 relay log 并按顺序执行。配置相关参数relay_log/var/log/mysql/mysql-relay-bin relay_log_recoveryON relay_log_purgeONrelay_log_purgeON表示执行完的 relay log 会自动清理建议保持开启。relay_log_recoveryON则能在从库异常重启后自动丢弃未完成的中继日志并重新拉取减少因中继日志损坏导致复制中断的概率。实际运维中 relay log 出问题最常见的是磁盘空间被撑满。在从库上如果发现磁盘使用率持续上涨先du -sh /var/log/mysql/看看是不是 relay log 没被清理再确认 SQL 线程是不是因为某个大事务卡住了把 relay log 的执行进度堵住。4. 配置实操从改一行参数到确认生效4.1 配置文件在哪、怎么改Linux 上 MySQL 配置文件常见位置有/etc/my.cnf、/etc/mysql/my.cnf以及/etc/my.cnf.d/、/etc/mysql/conf.d/这些扩展目录。Windows 上一般是C:\ProgramData\MySQL\MySQL Server 8.0\my.ini。同名参数在多个配置文件中都会生效后面的文件可能覆盖前面的配置所以改之前先确认实际加载了哪个文件。可以在 Linux 下用命令查看默认读取顺序mysqld --verbose --help | grep -A 5 Default options日志相关参数全部放在[mysqld]段下。改完配置文件后需要重启才能让静态参数生效systemctl restart mysqld重启是最容易出问题的操作我一般会先做一次语法检查mysqld --validate-config如果配置有误它会直接报错不会影响当前运行中的实例。4.2 错误日志、查询日志、慢查询日志配置示例一份可以直接参考的配置[mysqld] # 错误日志几乎必须配 log_error/var/log/mysql/error.log log_error_verbosity3 log_timestampsSYSTEM # 通用查询日志默认关排查时再临时开 # general_logON # general_log_file/var/log/mysql/general.log # 慢查询日志默认关生产建议开 slow_query_logON slow_query_log_file/var/log/mysql/slow.log long_query_time1 log_queries_not_using_indexesON log_slow_admin_statementsON log_slow_extraON # 日志输出方式 log_outputFILElog_outputFILE表示日志写入文件也可以写成TABLE写进系统表日常建议FILE。注意log_error_verbosity3会记录通知级信息如果你嫌日志太吵改成 2 就够用。配置完成后记得确保日志目录存在且 MySQL 系统用户有写权限mkdir -p /var/log/mysql chown mysql:mysql /var/log/mysql chmod 750 /var/log/mysql目录权限不对是最常见的“日志没生成”原因很多人折腾半天参数结果就是没建目录。4.3 二进制日志配置示例log_bin/var/log/mysql/mysql-bin binlog_formatROW binlog_row_imageFULL max_binlog_size256M sync_binlog1 binlog_expire_logs_seconds604800log_bin后面的路径是前缀实际文件会变成mysql-bin.000001、mysql-bin.000002。如果担心 binlog 占空间把过期时间压到 7 天也就是 604800 秒。sync_binlog1表示每次事务提交都把 binlog 落盘数据安全性最高但会牺牲一点性能。对主库来说这个值一般不要设成 0否则掉电可能丢 binlog。4.4 不重启也能改的动态参数日志相关的参数不是全都要重启才生效。慢查询日志、通用查询日志、long_query_time、log_queries_not_using_indexes都是动态参数可以在线改SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL log_queries_not_using_indexes ON; SET GLOBAL general_log OFF;但general_log_file、slow_query_log_file、log_error、log_bin这类文件路径或开关必须写进配置文件并重启。在线改之前确认一下当前值SHOW VARIABLES LIKE slow_query_log%; SHOW VARIABLES LIKE log_error%; SHOW VARIABLES LIKE general_log%;一个容易被忽略的细节SET GLOBAL long_query_time 1之后当前会话不生效需要重新连接才会使用新阈值。很多人在命令行里改了参数当场执行一条慢 SQL发现没进日志就以为配置失败其实是没重连。4.5 日志轮转别让日志把磁盘撑爆日志每天增长不做轮转迟早出事。Linux 上最省事的做法是用 logrotate。在/etc/logrotate.d/mysql里写一份配置/var/log/mysql/*.log { daily rotate 7 missingok create 640 mysql mysql sharedscripts postrotate mysqladmin flush-logs endscript }mysqladmin flush-logs作用是让 MySQL 重新打开日志文件相当于执行了FLUSH LOGS。否则 logrotate 把日志文件改名之后MySQL 还握着旧文件描述符继续写你会看到一个文件明明删了但磁盘空间不释放的诡异现象。binlog 不依赖 logrotate它靠max_binlog_size滚动到下一个编号文件靠binlog_expire_logs_seconds自动清理过期文件。所以 binlog 的磁盘管理核心就是设置合理的过期时间定期监控数据目录容量。5. 实战用日志还原一次故障现场5.1 案例一MySQL 启动失败错误日志直接给出答案某次我在新机器上部署 MySQL 8.0执行systemctl start mysqld等了半天起不来。用systemctl status mysqld只能看到 “Active: failed” 和一行笼统的错误真正有用的信息全在错误日志里。打开错误日志末尾几行是这样2025-06-20T02:15:23.123456Z 0 [ERROR] [MY-010262] [Server] Cant create/write to file /var/run/mysqld/mysqld.pid 2025-06-20T02:15:23.123456Z 0 [ERROR] [MY-010119] [Server] Aborting两个关键信息PID 文件无法写入目录/var/run/mysqld可能不存在或者权限不对。解决办法很简单mkdir -p /var/run/mysqld chown mysql:mysql /var/run/mysqld systemctl start mysqld类似的问题还有磁盘满、my.cnf里写错的参数、数据目录权限不对错误日志里都会有一行明确的[ERROR]比你在网上搜半天“mysqld 起不来”高效太多。5.2 案例二一条慢 SQL 把 CPU 打满一次线上告警某核心接口 P99 延迟突然从 200ms 涨到 5 秒数据库 CPU 长期 90% 以上。第一时间打开慢查询日志马上看到一条统计# Query_time: 3.567891 Lock_time: 0.001203 Rows_sent: 20 Rows_examined: 12000345 SELECT * FROM product_sku WHERE category_id 123 AND status 1 ORDER BY stock DESC;扫描了 1200 万行只返回 20 行。用EXPLAIN看执行计划possible_keys: idx_category_status key: NULL type: ALL索引idx_category_status存在但没被使用原因通常是查询里的排序字段不在索引里优化器评估后发现走索引还要回表排序索性全表扫。解决办法是把索引调整成(category_id, status, stock)让排序字段也覆盖进索引。改完索引后Rows_examined 从 1200 万降到不到 1 万接口延迟恢复正常。如果没有慢查询日志这条 SQL 可能永远只是“偶尔超时”的悬案。这里分享一个我自己的习惯慢查询日志不要开了就不管。我每周会用mysqldumpslow跑一次 Top SQL汇总最近一周里执行次数多、平均耗时长、扫描行数大的语句形成一份待优化清单。mysqldumpslow -s at -t 20 /var/log/mysql/slow.log-s at表示按平均执行时间排序-t 20显示前 20 条。如果公司允许安装 Percona Toolkit用pt-query-digest /var/log/mysql/slow.log分析更直观它会自动按指纹聚合同类 SQL。5.3 案例三误更新了整张表靠 binlog 定位和补救某个下午业务方过来说“有批数据被 UPDATE 语句更新错了”。这类事故最怕的不是恢复而是不知道当时执行了什么。好在开启了 binlog用时间范围定位即可mysqlbinlog --start-datetime2025-06-20 13:50:00 --stop-datetime2025-06-20 14:10:00 /var/log/mysql/mysql-bin.000145解析出来很快就能看到误操作 SQL 和它前后的 binlog 位置点。如果有多套备份可以结合备份把误改的行恢复出来。但这里必须强调误操作恢复是高风险动作第一步永远是先备份当前状态再根据 binlog 提取出“反向操作”在小实例上验证无误后才能在生产执行。千万别在网上找一段“闪回”脚本直接对着生产库跑。5.4 案例四服务器异常断电数据库还好吗很多人遇到过电脑莫名其妙关机或者服务器突然断电的情况重启后第一时间关心数据是否完好。此时不要急着操作先看错误日志。正常掉电重启后错误日志里会有 InnoDB 恢复的记录2025-06-20T02:20:00.123456Z 0 [System] [MY-013422] [InnoDB] Recovering after a crash 2025-06-20T02:20:02.654321Z 0 [System] [MY-013423] [InnoDB] Recovery completed看到这两行基本可以放心。InnoDB 通过 redo log 把已提交事务都恢复回来了。如果错误日志里出现 “Database page corruption” 或者 “Page [page 123] in the doublewrite buffer”那才是真正的坏消息需要评估是否启用innodb_force_recovery以只读模式导出数据。记住innodb_force_recovery是最后手段不是第一手段设得过高会导致 InnoDB 拒绝写入别一上来就开。6. 常见问题速查与避坑清单现象可能原因解决办法修改配置后日志没生成日志目录不存在或权限不足建目录并chown mysql:mysql重启前用--validate-config检查不知道错误日志在哪没配置log_error执行SHOW VARIABLES LIKE log_error查看实际路径慢查询日志始终没有内容long_query_time过大或改完没重连调低阈值确认使用新连接执行 SQL慢查询日志文件暴涨long_query_time0或log_queries_not_using_indexesON且大量无索引查询调高阈值结合业务设置合理值general log 严重拖慢性能生产环境长期开启只在使用场景下临时开启用完立刻SET GLOBAL general_logOFFbinlog 占满磁盘binlog_expire_logs_seconds过长或未配置调整过期时间用PURGE BINARY LOGS TO手动清理删了日志文件但空间不释放MySQL 进程仍持有旧文件句柄执行FLUSH LOGS或重启 MySQL日志时间和本地时间差 8 小时默认使用 UTC配置log_timestampsSYSTEM后重启从库中继日志不断增长SQL 线程卡住或未开启自动清理检查复制状态确认relay_log_purgeON几个额外的经验写在这里供参考第一日志配置要进部署模板不要每次手敲。我在新装 MySQL 时会把错误日志、慢查询日志、binlog 的配置直接放进初始化脚本或 Ansible 模板里这样每台机器行为一致排障时不用先猜“这台机器开没开慢日志”。第二开慢查询日志之前先想清楚查询量。一个 QPS 很高的库开log_queries_not_using_indexesON可能瞬间产生海量日志。我会先用半小时观察确认慢 SQL 量级再决定阈值和是否长期开启那个“未用索引”选项。第三定期做“日志体检”。我一般每周随机看一次错误日志哪怕没有告警。很多问题在爆发前是有前兆的比如频繁的Aborted connection、复制线程反复重连、临时表空间增长都在日志里留下了线索。等你收到告警再去看往往已经晚了一步。日志文件不会主动跟你说话但只要你按时看、按需开、配好轮转它就是你排查数据库问题最可靠的伙伴。我自己从“出事才翻日志”到“没事也翻日志”之后数据库事故的平均定位时间至少缩短了一半。你可以从今天的这篇文章开始先打开自己的 MySQL 错误日志看一遍它到底记录了些什么——这一步比装任何监控工具都更接近问题的本质。