慢查询怎么排查?
结论:慢查询排查分四步——先让慢 SQL「被记录下来」,再按指纹聚合出 TOP 消耗,然后对单条 SQL 看执行计划与真实耗时,最后才动手改 SQL 或索引。跳过第一步,所有优化都是猜。
一、先把慢日志打开
| 参数 | 作用 | 建议值 |
|---|---|---|
slow_query_log | 开关 | ON |
slow_query_log_file | 日志路径 | 独立磁盘目录 |
long_query_time | 阈值(秒,支持小数) | 1,排查期可降到 0.2 |
log_queries_not_using_indexes | 记录未走索引的语句 | 排查期开,平时关(会刷爆日志) |
min_examined_row_limit | 扫描行数低于该值不记录 | 100,过滤噪声 |
log_slow_admin_statements | 是否记录 ALTER/OPTIMIZE | ON |
log_slow_replica_statements | 从库是否记录 | 按需 |
# my.cnf
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
min_examined_row_limit = 100
log_slow_admin_statements = 1
log_queries_not_using_indexes = 0
-- 运行期动态调整,无需重启
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 0.5; -- 只对新连接生效
SHOW VARIABLES LIKE 'slow_query%';
二、排查漏斗
① 全局:哪类 SQL 最耗资源?
performance_schema.events_statements_summary_by_digest
pt-query-digest slow.log
│
▼
② 单条:它的执行计划合理吗?
EXPLAIN / EXPLAIN ANALYZE(8.0.18+)
optimizer_trace
│
▼
③ 现场:此刻谁在跑、卡在哪?
SHOW PROCESSLIST / information_schema.processlist
SHOW ENGINE INNODB STATUS(锁等待)
│
▼
④ 定性:扫描行数、回表、排序、临时表、锁等待、网络
三、按指纹聚合
-- 按总耗时排序,找出最贵的 SQL 指纹
SELECT
DIGEST_TEXT,
COUNT_STAR AS exec_count,
ROUND(SUM_TIMER_WAIT / 1e12, 2) AS total_sec,
ROUND(AVG_TIMER_WAIT / 1e9, 2) AS avg_ms,
SUM_ROWS_EXAMINED AS rows_examined,
SUM_ROWS_SENT AS rows_sent,
SUM_NO_INDEX_USED AS no_index
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;
rows_examined / rows_sent 的比值是最有用的信号:比值远大于 1,说明扫了大量行只返回几行,典型是索引不合适或条件不可索引。
# 离线分析慢日志,输出按总耗时排序的报表
pt-query-digest /var/log/mysql/slow.log > /tmp/slow_report.txt
四、单条 SQL 的深挖
-- 8.0.18+ 给出真实执行耗时与行数,而不是估算
EXPLAIN ANALYZE
SELECT id, title FROM article
WHERE author = 'ruilin' AND ctime > '2026-01-01'
ORDER BY ctime DESC
LIMIT 20;
-- 优化器为什么选了这个索引
SET optimizer_trace = 'enabled=on';
SELECT id FROM article WHERE author = 'ruilin' LIMIT 1;
SELECT TRACE FROM information_schema.optimizer_trace\G
SET optimizer_trace = 'enabled=off';
看 EXPLAIN 时重点盯四列:type(是否 ALL/index)、key(是否用了预期索引)、rows(估算扫描量)、Extra(Using filesort、Using temporary、Using index condition)。
五、常见结论与对应动作
type=ALL且rows很大 → 缺索引或条件不可索引。Using filesort→ 排序字段不在索引顺序里,考虑联合索引覆盖排序。Using temporary→GROUP BY/DISTINCT走了临时表,缩小结果集或调整索引。rows很小但实际很慢 → 锁等待、回表随机 I/O、或结果集传输大。- 从库慢而主库不慢 → 检查
Seconds_Behind_Master、大事务、无主键表的 row 复制。
常见追问
- 慢日志里为什么有执行很快的 SQL? 开了
log_queries_not_using_indexes,或者long_query_time设得极小;也可能是锁等待时间被计入。 long_query_time改了为什么没生效? 它是会话级变量,已存在的连接仍用旧值,新连接才生效。- 没有慢日志权限怎么办? 用
performance_schema的 digest 表,或SHOW FULL PROCESSLIST抓现场。 EXPLAIN的 rows 为什么和实际差很多? 它是基于统计信息的估算,统计过期时执行ANALYZE TABLE刷新。- 慢查询优化后怎么验证? 对比优化前后的
rows_examined、Handler_read%与 P99 耗时,并观察一段时间防止回归。
小结
慢查询治理是一条流水线:日志负责发现,digest 负责排序,EXPLAIN ANALYZE 负责定性,索引与 SQL 改写负责治疗。最容易被忽略的是最后一步的验证——没有前后指标对比的优化,等于没做。