数据库慢查询排查的基本思路
数据库慢查询排查的基本思路

引言
"系统变慢了"是最难排查的一类故障,因为慢的原因可能有十几种。但只要按固定顺序缩小范围,绝大多数问题都能在半小时内定位到具体语句。
下面是一套通用的排查顺序,适用于 MySQL,思路同样适用于其他关系型数据库。
第一步:先确认"慢"在哪里
不要一上来就查 SQL。先分层排除:
- 是数据库慢,还是应用慢? 看接口各阶段耗时,别让连接池等待背锅
- 是单条语句慢,还是整体慢? 整体慢可能是锁等待、连接打满、磁盘打满
- 是偶发还是持续? 偶发多半是资源竞争,持续更可能是执行计划问题
一个关键动作:先看数据库所在机器的 CPU、内存、磁盘 IO。如果 CPU 或 IO 已经接近瓶颈,优化 SQL 之前得先扩容或限流。
第二步:找出真正的"罪魁祸首"
靠猜是找不到的,要拿数据。常用手段:
- 慢查询日志:设置合理的阈值(如 1 秒),让它记录候选
- 用聚合视角看:按总耗时排序,而不是按单次耗时——一条跑 0.1 秒但一天执行百万次的语句,危害远大于跑 5 秒但一天三次的报表
排序时一定看"总时间 = 平均耗时 × 执行次数",这是最容易搞错的地方。
拿到可疑 SQL 后,再逐个分析。
第三步:看执行计划
拿到慢 SQL,第一件事是看它到底怎么执行的。重点看三项:
| 关注项 | 说明 |
|---|---|
| type | 访问类型。出现 ALL(全表扫描)就要警惕 |
| key | 实际用到的索引。为空说明没走索引 |
| rows / Extra | 预估扫描行数,以及是否出现临时表、文件排序 |
常见的危险信号:
- type = ALL:大表全扫
- Extra 出现 Using filesort / Using temporary:排序或分组没走索引
- rows 远大于返回行数:扫了大量无用数据
要注意:执行计划是预估,必要时用实际执行的统计信息交叉验证。
第四步:索引是不是真的生效了
建了索引不等于用上了。几种典型的"索引失效":
- 对列做了运算或函数:如
WHERE DATE(created_at) = ... - 隐式类型转换:字符串列用数字比较,可能导致全扫
- 违反最左前缀:联合索引没从最左列开始用
- 前导通配查询:
LIKE '%abc'用不上普通索引 - 区分度太差:性别、状态这类低选择性字段单独建索引收益很低
另外要检查冗余索引:同一个前缀重复建、区分度低的索引,只会拖慢写入、占用空间。
第五步:改完必须验证
优化的闭环是对比而不是感觉:
- 记录优化前的耗时、扫描行数
- 修改后再测同样的语句与数据量
- 确认执行计划确实变了(而不是"好像快了点")
同时要评估副作用:加索引会拖慢写入、占用磁盘。高频写入的表,索引数量要克制。
一个容易忽略的方向:数据量本身
如果索引没有问题,执行计划也合理,那要问:这张表是不是该瘦身了?
- 历史数据归档:把冷数据迁走,热表数据量降一个数量级,效果立竿见影
- 大字段拆表:把 TEXT/BLOB 拆到独立表,减少主表 IO
- 分页深翻优化:
LIMIT 100000, 20这种深分页要换成基于主键的游标方式
很多时候,归档比任何 SQL 优化都有效。
结语
慢查询排查的顺序总结一句:先分层定位范围 → 慢日志找最耗时的语句 → 看执行计划 → 检查索引是否真的生效 → 改完对比验证 → 数据量过大就归档。
这套流程的价值在于可重复。哪怕对某个数据库不熟,按这个顺序走,也不会乱。


