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 实战建议
说了这么多,给你几个落地建议:
- 阈值设置要合理:OLTP 系统建议 1 秒,OLAP 系统可以放宽到 5-10 秒。
- 定期分析日志:我习惯每天凌晨跑一次 pt-query-digest,把结果发到团队群里。
- 保留历史数据:慢查询日志不要只保留一天,至少保留一周,方便对比趋势。
- 结合监控系统:如果发现慢查询突然增多,大概率是数据量增长或者索引失效了。
好了,慢查询日志这块就讲到这里。你想想看,如果没有这个日志,你就像在黑暗中找路。有了它,至少知道问题出在哪。下一节我们会讲怎么用 EXPLAIN 来分析这些慢查询的执行计划,到时候你就知道怎么「对症下药」了。