2. 慢查询日志分析:开启慢查询日志、日志格式解读、常用分析工具(pt-query-digest)

说到慢查询治理,第一步不是去优化 SQL,而是先找到那些「拖后腿」的查询。怎么找?靠猜肯定不行。MySQL 提供了一个非常实用的功能——慢查询日志。说白了,它就是数据库自己记的「黑名单」,把执行时间超过你设定阈值的 SQL 都记录下来。

我个人习惯,接手一个新系统,第一件事就是打开慢查询日志。为什么?因为很多线上问题,其实早就写在日志里了,只是没人去看。

2.1 开启慢查询日志

开启方式有两种:临时开启和永久配置。我建议你在测试环境先临时开启,确认没问题再写到配置文件里。

临时开启(当前会话有效)

-- 查看当前状态
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time%';

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;

-- 设置阈值,单位秒,我一般设为 1 秒
SET GLOBAL long_query_time = 1;

-- 指定日志文件路径
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';

永久配置(写入 my.cnf)

[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = ON   # 记录没走索引的查询
小提示:long_query_time 设成 0 可以记录所有查询,但生产环境千万别这么干。我曾经见过有人这么搞,结果日志文件一小时涨到 10GB,磁盘直接打满。

2.2 日志格式解读

慢查询日志长什么样?我截取一段真实的日志给你看看:

# Time: 2025-03-21T10:15:23.123456Z
# User@Host: root[root] @ localhost []  Id: 12345
# Query_time: 2.345678  Lock_time: 0.000123  Rows_sent: 100  Rows_examined: 500000
SET timestamp=1742544923;
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC LIMIT 100;

嗯,这里有几个关键字段,我一个个说:

字段 含义 我的关注点
Query_time 查询总耗时(秒) 超过 1 秒就要警惕
Lock_time 锁等待时间(秒) 如果这个值很大,说明有锁竞争
Rows_sent 返回的行数 返回太多数据,网络传输也是成本
Rows_examined 扫描的行数 这个值远大于 Rows_sent,说明索引没用好
核心判断逻辑:Rows_examined 和 Rows_sent 的比值越大,说明查询效率越低。比如扫描了 50 万行只返回 100 行,这明显是索引设计有问题。

2.3 常用分析工具:pt-query-digest

日志文件一多,肉眼根本看不过来。这时候就需要工具上场了。我最常用的是 Percona Toolkit 里的 pt-query-digest。它能把成千上万条慢查询按「总耗时」排序,帮你快速定位最需要优化的 SQL。

安装

# CentOS / RedHat
yum install percona-toolkit

# macOS
brew install percona-toolkit

基本用法

# 分析慢查询日志文件
pt-query-digest /var/log/mysql/mysql-slow.log

# 分析结果输出到文件
pt-query-digest /var/log/mysql/mysql-slow.log > slow_report.txt

# 分析最近 1 小时的日志
pt-query-digest --since=1h /var/log/mysql/mysql-slow.log

输出结果解读

运行后,你会看到类似这样的输出:

# 320ms user time, 10ms system time, 23.83M rss, 23.83M vsz
# Current date: Fri Mar 21 10:30:00 2025
# Hostname: db-server-01
# Files: /var/log/mysql/mysql-slow.log
# Overall: 1.5k total, 12 unique, 0.01 QPS, 0.01x concurrency
# Time range: 2025-03-21T09:00:00 to 2025-03-21T10:30:00
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Query time           123s      1s     12s      2s      5s      2s      1s
# Lock time             2s       0      1s      0s      0s      0s      0s
# Rows sent           1.2k       0     100      1       5      10       1
# Rows examine       500k       0   500k    333     1k     10k     100

我一般先看两个地方:

  • Query time 的 95% 值:如果这个值超过 3 秒,说明大部分用户都在等。
  • Rows examine 的 95% 值:如果这个值很大,说明索引优化空间很大。

按查询指纹分组

pt-query-digest 会把相似的 SQL 归为一组,称为「查询指纹」。比如下面两条 SQL:

SELECT * FROM users WHERE id = 100;
SELECT * FROM users WHERE id = 200;

它们会被归为同一组,因为结构一样,只是参数不同。这样你就能看到「这类查询」总共花了多少时间。

避坑指南:我曾经遇到过一个案例,pt-query-digest 分析结果显示某条 SQL 只出现了几次,但每次都很慢。我一开始没在意,后来才发现这条 SQL 是定时任务跑的,每次跑 10 分钟,把整个库都锁住了。所以,不要只看次数,还要看单次耗时和总耗时

2.4 实战建议

说了这么多,给你几个落地建议:

  1. 阈值设置要合理:OLTP 系统建议 1 秒,OLAP 系统可以放宽到 5-10 秒。
  2. 定期分析日志:我习惯每天凌晨跑一次 pt-query-digest,把结果发到团队群里。
  3. 保留历史数据:慢查询日志不要只保留一天,至少保留一周,方便对比趋势。
  4. 结合监控系统:如果发现慢查询突然增多,大概率是数据量增长或者索引失效了。

好了,慢查询日志这块就讲到这里。你想想看,如果没有这个日志,你就像在黑暗中找路。有了它,至少知道问题出在哪。下一节我们会讲怎么用 EXPLAIN 来分析这些慢查询的执行计划,到时候你就知道怎么「对症下药」了。