☰
MySQL日志管理全攻略:从配置到故障排查的完整实践
2026/9/28 6:41:09 网站建设 项目流程

排查MySQL故障时,我最先做的事永远是翻日志。这句话听起来简单,但现实中太多人一遇到报错就重启服务、改参数,却忽略了错误日志里早把原因写得明明白白。MySQL日志管理这个主题,覆盖了配置、轮转、分析和故障定位一整条链路,尤其是刚入门MySQL的同学,往往分不清有哪几类日志、哪些参数控制落盘、日志文件越来越大怎么处理,更别提靠日志反推问题根因了。这篇文章按我日常操作的顺序,把MySQL日志从配置到故障排查的完整过程掰开揉碎讲一遍,基于MySQL 8.0版本,配置思路同样适用于MySQL 5.7。想看结论的直接跳到对应章节,想系统过一遍的就顺着读。

1. MySQL日志家族盘点:每类日志到底在记录什么

1.1 六类日志的功能边界

MySQL里的日志并不只有一种,很多人对着数据目录发懵,不知道哪个文件是干嘛的。先理清楚分类,后面配置和排查才不会乱:

日志类型默认文件核心配置参数典型用途
错误日志hostname.errlog_error启动异常、运行错误、连接失败记录
慢查询日志hostname-slow.logslow_query_log、long_query_time捕获执行时间超阈值的SQL
通用查询日志hostname.loggeneral_log、general_log_file记录所有连接和SQL语句,排查审计用
二进制日志binlog.000001log_bin、binlog_format数据恢复、主从复制数据源
中继日志relay-log.000001relay_log从库接收主库binlog后本地落地
InnoDB事务日志ib_logfile0innodb_log_file_size崩溃恢复,引擎层物理重做日志

这里必须先做一个常见混淆的澄清:redo log和binlog是两类完全不同的东西。redo log是InnoDB存储引擎层的物理日志,记录的是页的修改,服务崩溃后靠它恢复数据;binlog是MySQL Server层的逻辑日志,记录的是SQL语句或行变更,主从复制和备份恢复靠它。很多新手把这两个混为一谈,排查时就会找错方向。比如实例崩溃后数据丢失,你去翻binlog没有意义,得看redo log和错误日志的崩溃恢复段。

1.2 错误日志其实是故障排查的起点

错误日志是所有TCP连接失败、认证失败、启动关闭事件、复制异常、InnoDB恢复状态、主从切换等关键事件的第一落点。它的输出入口由log_error参数控制,默认位置在数据目录下以主机名命名的.err文件里。

我遇到过不少同事,my.cnf里压根没配log_error,导致错误日志被系统rsyslog接管,最后散落在/var/log/messages里,排查时还得去系统日志里大海捞针。所以配好log_error、固定错误日志的路径,是所有日志管理的第一步。

1.3 慢查询日志与通用查询日志的区别

慢查询日志记录的是超过long_query_time阈值的SQL,主要用于性能优化;通用查询日志记录的是所有客户端发来的每一条语句,包括连接建立和断开,生产环境默认关闭。两者定位完全不同:一个做体检,一个做监控录像。录像耗硬盘,所以平时别开,只有在需要审计某个具体时段、某个连接的完整SQL序列时,才临时开启并立刻关闭。

2. 日志落盘前的关键配置:my.cnf参数清单与避坑

2.1 一份可以直接参考的日志参数配置

下面的配置是我在生产环境常用的模板,按实际场景调整路径和阈值:

[mysqld] # 错误日志 log_error = /data/mysql/logs/error.log log_error_verbosity = 3 # 慢查询日志 slow_query_log = ON slow_query_log_file = /data/mysql/logs/slow.log long_query_time = 2 log_queries_not_using_indexes = ON min_examined_row_limit = 100 # 通用查询日志(排查问题临时开启) # general_log = ON # general_log_file = /data/mysql/logs/general.log # 二进制日志 server_id = 1 log_bin = /data/mysql/logs/binlog binlog_format = ROW binlog_row_image = FULL max_binlog_size = 256M binlog_expire_logs_seconds = 604800 sync_binlog = 1 # 日志时间统一用系统本地时间 log_timestamps = SYSTEM

每条说明一下为什么这么配:

  • log_error_verbosity=3 表示错误日志记录的信息级别包含Error、Warning、Note。默认值在MySQL 8.0就是3,线上日志量能接受的话不用动,但排查问题时不要手贱降成2,很多警告信息反而有助于定位。
  • log_queries_not_using_indexes=ON 会把没用索引的查询全部记进慢日志,这对发现隐藏的全表扫描非常有帮助。但这个选项特别危险,如果你库里有大量本来就不需要索引的小表查询,日志会瞬间爆炸。所以必须配合min_examined_row_limit=100,意思是只有扫描行数超过100行的查询才会被选进来,过滤掉那些本来就该全表扫的配置表查询。
  • binlog_expire_logs_seconds=604800 是7天,单位是秒,不是天。MySQL 8.0已经把expire_logs_days标记为废弃参数,这俩混着写会导致清理策略不生效。
  • sync_binlog=1 表示每次事务提交都强制刷盘,保证binlog不丢;代价是写入性能有一定下降,但对数据一致性要求高的业务必须开。

2.2 慢查询日志“查不到记录”的四个常见原因

慢查询日志开启后,执行一条明显超过阈值的SQL,结果日志里啥也没有。这个问题在排障和面试里都高频出现,99%的原因是下面几个:

  1. long_query_time默认值是10秒,线上很多SQL跑个3秒5秒的,你觉得慢,但没到默认阈值。想验证可以先SET GLOBAL long_query_time=0,再执行任何查询,确认日志能写入,再调回合理值。
  2. 慢查询参数有时效性:SET GLOBAL只是修改全局变量,当前已存在的连接仍然用旧值,必须新开连接才生效。改完my.cnf后不重启,部分版本参数也可能不生效,建议用SHOW VARIABLES LIKE 'long_query_time'验证当前实际值。
  3. 路径权限问题。slow_query_log_file指定的目录不存在或MySQL进程无写权限时,MySQL默认会忽略错误继续运行,但日志就是不写。务必手动测试目录可写,并查看错误日志里有没有“Can't create/write to file"的报错。
  4. 只有执行完成的语句才进慢日志,被kill掉的、仍在跑的、因为锁等待尚未结束的SQL不会记录。排查时别对着原本就执行失败的语句找慢日志,得用performance_schema的events_statements_current去看实时状态。

2.3 二进制日志格式选择:ROW还是STATEMENT

binlog_format有三个值:STATEMENT、ROW、MIXED。我个人的线上结论是:用ROW,不用纠结。

STATEMENT格式记录的是原始SQL,日志量小,但它在遇到NOW()、UUID()这类不确定函数时,主从执行结果可能不一致,对无主键表的更新同样存在隐患。ROW格式记录的是每一行前后镜像,对不确定函数天然安全,从库回放结果一定和主库一致。代价是日志体积成倍增长,尤其大批量UPDATE时binlog会非常大——这正好印证了为什么binlog清理策略必须单独盯着。

binlog_row_image=FULL表示记录行变更的前镜像和后镜像,对于刚开始学、或者日常排查问题,FULL信息最全。如果追求极致性能,可以改成MINIMAL(只记被修改的列),但排障时看binlog就费劲了。

3. 日志轮转与磁盘水位:日志管理最容易被忽视的战场

3.1 日志撑爆磁盘的三种典型场景

日志管理做得再好,也怕突发情况。我处理过的生产事故里,日志撑爆磁盘的基础盘现象就三类:

第一类,通用日志忘记关闭。有次排查一个连接风暴问题,开了general_log想看完整的SQL序列,问题解决后忘了关,两天后磁盘告警,一看/var/log/mysql目录占了40多GB。这类日志写满磁盘之后,MySQL会因为无法写入而直接拒绝服务,非常危险。

第二类,慢日志和无索引查询叠加。前面说的log_queries_not_using_indexes=ON如果没有min_examined_row_limit兜底,一次大量无索引扫描就能让慢日志单日增长数GB。

第三类,binlog只增不减。很多新手不知道binlog默认不会自动清理,以为删除数据后binlog就小了。实际上binlog是追加日志,没有备份任务、没有主从复制消费的情况下,它就是这个实例最大的“磁盘吞噬者”。

3.2 binlog清理策略的正确配置

MySQL 8.0里,官方推荐用binlog_expire_logs_seconds,单位秒。下面这个配置表示binlog保留7天,超过7天的自动PURGE:

binlog_expire_logs_seconds = 604800

这里必须强调两个坑:第一,这个参数对slave从库的relay log不生效,relay log由relay_log_purge=1控制自动清理;第二,binlog清理依赖MySQL自动任务,不是进程内实时扫描,高峰期日志量大的库,实际占用可能超过保留期一两个G,别等告警了才反应。

手动清理binlog的姿势是这样:

-- 查看当前正在使用的binlog SHOW MASTER STATUS; -- 清理到指定文件之前(不包含指定文件) PURGE BINARY LOGS TO 'binlog.000023'; -- 按时间清理 PURGE BINARY LOGS BEFORE NOW() - INTERVAL 3 DAY;

手动PURGE前必须先确认复制链路状态。从库还在读取某个binlog文件,你自信满满地PURGE了,从库IO线程立刻报1236错误,主从直接断裂,这个坑我踩过。判断方法:SHOW SLAVE STATUS\G里的Master_Log_File,或SHOW REPLICA STATUS\G里的Relay_Master_Log_File,PURGE到比这个文件更早的位置就是找死。

3.3 用logrotate实现日志轮转

MySQL自身不负责按天切割日志,Linux下用logrotate管理更顺手。下面是我的一台线上配置文件,保存在/etc/logrotate.d/mysql:

/data/mysql/logs/*.log /data/mysql/logs/binlog.* { daily rotate 14 missingok notifempty compress delaycompress sharedscripts postrotate /usr/local/mysql/bin/mysqladmin -uroot -p*** flush-logs endscript }

几个细节值得说明:

  • postrotate里的flush-logs是让MySQL重新生成新的binlog文件,配合compress压缩旧文件,空间友好。但注意:flush-logs对通用日志和慢查询日志同样有效,对错误日志不总是生效。
  • sharedscripts确保多个日志文件匹配时,postrotate脚本只执行一次。
  • 千万不要在logrotate里加copytruncate当作万能方案,日志文件正在被MySQL占用时强制truncate,可能导致文件空洞、写入错乱。用flush-logs才是正规姿势。

轮转完成后建议自己看一眼目录,确认新日志文件已创建、旧文件已压缩,别指望logrotate一定成功,cron执行失败、权限不对都是常见故障。

4. 从日志到故障定位:三个真实排查案例

这一章我按实际排查链路来写,不跳步。日志的价值不在于“出错了看一眼”,而在于通过日志的上下文把根因推出来。

4.1 案例一:客户端连不上MySQL,错误日志暴露的真相

现象很典型:客户端执行mysql -uroot -p,报错:

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

很多人的第一反应是“MySQL挂了”,但服务可能活得好好的。排查链路如下:

第一步,看错误日志。MySQL启动、关闭、连接失败,错误日志里都有记录。执行:

tail -100 /data/mysql/logs/error.log

如果日志末尾有:

[System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections.

说明MySQL明明正常,那问题就在客户端连接参数上。socket文件不在/tmp/mysql.sock,或者客户端指定的host是localhost时走了socket连接,但文件路径不对。

第二步,确认真实socket路径。执行:

SHOW VARIABLES LIKE 'socket';

如果结果是/tmp/mysql.sock但文件不存在,十有八九是系统清理临时目录把它删了。服务本身正常,只是Unix socket文件丢了,此时用TCP方式连接即可验证:

mysql -uroot -p -h127.0.0.1 -P3306

第三步,如果错误日志里大量出现这种记录:

[Warning] [MY-010744] [Server] Access denied for user 'root'@'localhost' (using password: YES)

那就是密码认证问题,去排查账号权限,而不是纠结socket。日志已经给了你路径和答案,顺着查就行。

这类问题的根治思路:把socket参数显式配到my.cnf里,并确保连接时明确指定socket或host。连接池、ORM框架老出这类问题的,统一改成TCP方式连接,再在配置里指明socket绝对路径,能省掉一半莫名其妙的“连不上”。

4.2 案例二:主从复制中断,binlog与relay log的交叉验证

主从同步出问题,SHOW REPLICA STATUS\G里的字段会直接告诉你原因在IO线程还是SQL线程。

现象:Slave_SQL_Running=No,Last_Errno=1032。

1032代表从库回放binlog时找不到目标行,常见于从库被手动改过、主库有删除但从库对应记录已被修改。错误日志里往往有完整SQL语句,先去翻:

grep -A 10 'Coordinator thread' /data/mysql/logs/error.log

如果错误信息里拿着具体SQL,下一步确认这条SQL到底想改哪张表、哪个主键。然后用binlog反向定位:

mysqlbinlog --base64-output=decode-rows -v /data/mysql/logs/binlog.000014 | grep -B 5 -A 10 '1032'

这样可以看到完整的行前镜像和行后镜像,明确从库缺什么。

最稳妥的修复方式不是直接跳过错误,而是把缺失的数据从主库补齐。简单场景下,可以在从库上手动INSERT缺失记录后再START REPLICA;如果错误不涉及该行的后续更新,跳过该事务也能接受,跳过命令:

STOP REPLICA; SET GLOBAL sql_replica_skip_counter = 1; START REPLICA;

MySQL 8.0里是sql_replica_skip_counter,别再用旧版本的sql_slave_skip_counter了,虽然语法兼容但容易让人记忆混淆。跳过一次必须立刻确认状态,绝不能在故障未判断的情况下连续跳,否则数据的完整性会越来越差。

中继日志损坏是另一类坑,错误日志里报1594,relay log文件破坏。这种情况把当前出问题的relay log删掉,让IO线程从主库重新拉取即可:

STOP REPLICA; RESET REPLICA ALL; CHANGE MASTER TO MASTER_LOG_FILE='binlog.000014', MASTER_LOG_POS=4; START REPLICA;

MASTER_LOG_FILE和MASTER_LOG_POS来源于SHOW MASTER STATUS,拿主库当前坐标来对。重点在于:修复前必须明确是只是relay log文件损坏,还是数据本身已经不一致,后者需要更完整的校验,不能只靠reset来解决。

4.3 案例三:业务突慢,慢查询日志怎么帮我定位具体SQL

业务反馈“接口突然从200ms变2秒”,数据库CPU 80%以上。此时我不建议先动索引,第一步永远是看慢查询日志。

执行:

tail -200 /data/mysql/logs/slow.log

重点关注两类SQL:一是执行时间突然拉长的老查询,二是新出现的、之前没见过的SQL片段。慢日志每行都有关键标注:

# Query_time: 2.452347 Lock_time: 0.000182 Rows_sent: 1 Rows_examined: 789321 SET timestamp=...; SELECT o.id, o.order_no FROM orders o WHERE o.user_id=10086...

Query_time是总执行时间,Lock_time是锁等待时间,Rows_examined是实际扫描行数。78万多行的扫描只返回1行,这基本就是索引缺失或者索引失效的明牌信号。

下一步用EXPLAIN确认执行计划:

EXPLAIN SELECT o.id, o.order_no FROM orders o WHERE o.user_id=10086\G

看type字段,如果是ALL(全表扫描)或者index(全索引扫描),并且key字段为NULL,那问题就是缺少合适索引。针对user_id这个过滤条件,建一个普通索引就够了:

ALTER TABLE orders ADD INDEX idx_user_id (user_id);

索引加完,记得再回来看慢日志,确认这个SQL不再上榜,同时用EXPLAIN确认type变成ref,扫描行数从几十万降到几十行。整个过程就是“慢日志定位候选SQL -> EXPLAIN确认执行计划 -> 加索引/改写SQL -> 慢日志验证效果”,循环两三轮,绝大多数慢SQL问题都能收敛。

5. 慢查询日志的进阶玩法:从打印到分析闭环

5.1 手动打开慢日志文件看,只能处理单条SQL

慢日志文件一大,直接用cat和grep会很难受。MySQL自带的mysqldumpslow工具可以把相同模式的SQL聚合,语法模板不同但结构相似的语句会被归为一组,比如上面的查询会被归为SELECT o.id, o.order_no FROM orders o WHERE o.user_id=N。

常用命令:

# 按平均查询时间排序,取前10 mysqldumpslow -s at -t 10 /data/mysql/logs/slow.log # 按总执行时间排序 mysqldumpslow -s t /data/mysql/logs/slow.log

输出里每组SQL都会显示Count(出现次数)、Time(平均/总时间)、Lock、Rows(返回行数/扫描行数)。这个工具的好处是快速知道“哪种SQL类型最消耗数据库资源”,坏处是它不会告诉你具体某一条SQL在哪个时间点执行过,这需要更细的工具。

5.2 pt-query-digest:分析慢日志的标准姿势

Percona Toolkit里的pt-query-digest是慢日志分析的利器。安装方式各系统略有差异,这里不展开,装好后用法很简单:

pt-query-digest /data/mysql/logs/slow.log > digest_report.txt

生成的报告怎么看?重点看三块:

  1. Profile部分,列出了所有SQL指纹的占比排名。Response time占比最高的那组SQL,就是第一优化对象,哪怕它Count不是最多的。总响应时间=单次执行时间×执行次数,高响应占比的SQL才是真凶。
  2. 每个Query group单独的执行详情,包含中位数时间、95%分位时间。中位数低但95%高的SQL,说明大多数时候快,偶尔慢,方向要往锁等待、突然的并发上引。
  3. Query_time分布直方图,有助于判断是否存在周期性卡顿。

用这个工具分析完,别只停留在“知道了哪些SQL慢”,要紧跟一步:把慢日志里的查询汇总后,和information_schema、performance_schema里的表数据量变化做比对,很多慢查询的根因是数据量增长导致旧的执行计划不再高效,而非SQL本身写错了。

5.3 把慢日志分析形成日常闭环

日志管理不是等出问题才去看,我建议把慢日志分析做成周期性动作:每周跑一次pt-query-digest,对比上周排名,新上榜的SQL优先处理;同时通过一个简单脚本检测慢日志文件是否在持续变大,超过设定阈值就触发告警。

如果不想额外装工具,MySQL自带的performance_schema里events_statements_summary_by_digest表也提供了类似统计能力,通过SQL查询就能拿到按语句指纹聚合的延迟和扫描行数。但缺点是需要启动参数performance_schema=ON,且查询语句相对SQL门槛更高。慢日志文件解析则更直观,两种方案按需取舍。

6. 日志安全与自查清单:收尾必须做的事

最后聊几个日志管理里容易被忽略的安全和实践细节。

第一个,通用日志、慢日志里会记录完整的SQL原文,如果业务SQL中拼接了用户手机号、身份证、密码等敏感信息,这些数据就会明文落在日志文件里。MySQL不会记录认证密码本身,但general_log会把INSERT INTO users(name, password) VALUES ('x', 'abc123')这样的语句原样写进文件。所以生产环境尽量别开general_log,非开不可时,设置文件权限为640,属主设置为mysql用户,避免共享账号直接读日志文件。

第二个,日志权限自查。错误日志、慢日志、binlog文件的权限建议全部设置为mysql:mysql 640,不要用root或者777。binlog是明文文件,任何人能读binlog就等于拿到全量数据变更历史。这也是安全审计最基本的自查项。

chown mysql:mysql /data/mysql/logs/*.log chmod 640 /data/mysql/logs/*.log

第三个,日志目录建议独立分盘。把日志放在根分区和binlog放在数据盘,一旦日志膨胀直接把系统盘撑满,MySQL直接无法启动,救援起来极其痛苦。独立挂载/data/mysql/logs,让日志问题只影响日志分区,不影响数据库本体。

第四个,建议定期执行SHOW VARIABLES LIKE '%log%',核对运行时参数和配置文件里是否一致。MySQL 8.0里部分参数写入my.cnf后,运行了才会动态生效;配置文件写的和实际内存里的值不一致,是排查“我明明改了怎么没用”的高频原因。以实际运行值为准,不要以文件为准。

还有一个小习惯很管用:每次变更日志相关配置后,在测试环境重启实例并执行一条慢SQL,确认慢日志、binlog确实按预期记录,再推到生产。日志管理这类低风险配置,最大的风险恰恰是“你以为配置了,实际没生效”。

MySQL日志管理没有太多玄学,就是把每类日志的责任边界理清,把参数调对,把磁盘盯紧,把“先看日志再动手”养成肌肉记忆。遇到故障别急着重启,先花几十秒读一遍错误日志,多数问题的答案已经在里面了。

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

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

立即咨询