慢 SQL 怎么定位?给出从查看到优化的完整流程。
结论先行:标准流程是“开启慢查询日志 → 抓取分析 → EXPLAIN 看执行计划 → 按索引/改写/结构/参数四层优化 → 上线后对比基线”。没有日志与基线的优化都是盲目的。
一、第一步:把慢 SQL 记录下来
SET GLOBAL slow_query_log = ON; -- 开启慢查询日志
SET GLOBAL long_query_time = 1; -- 超过 1 秒才记录
SET GLOBAL log_queries_not_using_indexes = ON; -- 顺带记录没走索引的
mysqldumpslow -s at /var/log/mysql/mysql-slow.log # 按平均耗时排序
pt-query-digest /var/log/mysql/mysql-slow.log # 更完整的分析报告
二、第二步:用 EXPLAIN 定位瓶颈
| 现象 | 可能原因 | 应对 |
|---|---|---|
| type=ALL | 全表扫描 | 建合适索引 / 改写条件 |
| rows 巨大 | 扫描范围过大 | 增加过滤、缩小范围 |
| Using filesort | 排序未走索引 | 联合索引覆盖排序列 |
| Using temporary | 分组/去重吃紧 | 调整索引或拆分 SQL |
| 锁等待 | 行锁/间隙锁冲突 | 查锁监控,错峰执行 |
三、第三步:常见优化手段
- 索引层:为高频 where/order by/group by 设计联合索引,遵循最左前缀。
- 改写层:去掉 SELECT *,深分页用延迟关联,子查询改 join,杜绝循环查库。
-- 慢:LIMIT 深分页要数过百万行
SELECT * FROM t ORDER BY id LIMIT 1000000, 20;
-- 快:先只取主键再关联回原表
SELECT t.* FROM t JOIN (SELECT id FROM t ORDER BY id LIMIT 1000000, 20) x ON t.id = x.id;
- 结构层:大字段拆表、冷热数据分离、必要时加汇总表。
- 参数层:调大 buffer pool、优化临时表内存,前提是前面几层已做完。
常见追问与记忆点
- 追问:优化完怎么证明有效?对比前后 EXPLAIN 的 rows/Extra 与真实耗时,并跑压测基线。
- 追问:慢查询日志有副作用吗?有,长期开启要控制 long_query_time 与采样,避免额外 IO。
- 记忆点:先抓(日志)→ 再看(explain)→ 再改(索引 → 改写 → 结构 → 参数)→ 最后验。