文章目录
-
- [《MySQL 慢查询优化:从定位、分析到索引调优的完整流程》](#《MySQL 慢查询优化:从定位、分析到索引调优的完整流程》)
-
- 一、前言:为什么会有慢查询
- 二、第一步:开启慢查询日志
- 三、第二步:分析慢查询日志
- [四、第三步:Explain 执行计划分析](#四、第三步:Explain 执行计划分析)
- 五、第四步:确认索引命中逻辑
- [六、第五步:使用 SQL Profile 与实际耗时验证](#六、第五步:使用 SQL Profile 与实际耗时验证)
- [七、第六步:改写 SQL 与缓存优化](#七、第六步:改写 SQL 与缓存优化)
-
- [① 避免 SELECT *](#① 避免 SELECT *)
- [② 用 EXISTS 替代 IN](#② 用 EXISTS 替代 IN)
- [③ 合理使用 LIMIT](#③ 合理使用 LIMIT)
- [④ 引入缓存层](#④ 引入缓存层)
- [八、SQL 优化流程总结图](#八、SQL 优化流程总结图)
- 九、面试高频问题与答题模板
- 十、总结
《MySQL 慢查询优化:从定位、分析到索引调优的完整流程》
一、前言:为什么会有慢查询
大家好,我是程序员卷卷狗。
在业务量快速增长的系统中,数据库性能问题往往出现在:
- 表数据量过大;
- SQL 没有走索引;
- 索引设计不合理;
- 统计信息失真导致优化器误判。
慢查询优化的本质:
找出执行慢的 SQL,分析原因,减少不必要的扫描与排序。
二、第一步:开启慢查询日志
MySQL 内置了慢查询日志(slow_query_log),
用于记录执行时间超过阈值的 SQL。
(1)开启方式
sql
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1; -- 超过1秒记录
SET GLOBAL log_output = 'TABLE'; -- 输出到mysql.slow_log表
(2)查看慢查询数量
sql
SHOW GLOBAL STATUS LIKE 'Slow_queries';
(3)查看慢日志内容
sql
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 5;
建议:
- 线上可设置
long_query_time=0.5;- 日志开启后结合定期分析工具(如 pt-query-digest)。
三、第二步:分析慢查询日志
使用官方或 Percona 工具进行日志聚合分析。
示例命令:
bash
pt-query-digest /var/lib/mysql/slow.log > slow_report.txt
输出指标:
| 指标 | 含义 |
|---|---|
| Count | SQL 执行次数 |
| Exec time | 总耗时 |
| Lock time | 锁等待时间 |
| Rows sent | 返回行数 |
| Rows examine | 扫描行数 |
| Query ID | SQL 模板标识 |
通过分析聚合结果,找出:
- 执行最慢的 SQL;
- 执行最频繁但扫描量大的 SQL;
- 存在重复逻辑或未命中索引的语句。
四、第三步:Explain 执行计划分析
拿到慢 SQL 后,先用 EXPLAIN 查看执行计划。
示例:
sql
EXPLAIN SELECT * FROM orders WHERE status='PAID' AND create_time > '2025-01-01';
输出:
type: ALL
rows: 500000
Extra: Using where
分析:
- type=ALL → 全表扫描;
- rows=50 万 → 扫描过多;
- Extra=Using where → 没有使用索引。
解决方案:
sql
CREATE INDEX idx_status_time (status, create_time);
再执行:
type: range
key: idx_status_time
rows: 1200
Extra: Using index condition
性能显著提升。
五、第四步:确认索引命中逻辑
优化索引时要遵守"查询模式匹配原则"。
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 仅范围条件 | WHERE age > 18 |
建单列索引 idx_age |
| 组合条件 | WHERE status='PAID' AND user_id=100 |
建联合索引 (status, user_id) |
| 排序字段 | ORDER BY create_time |
为排序列建索引 |
| 分组查询 | GROUP BY status |
联合索引中包含分组列 |
索引优化的核心目标:减少 I/O,避免 filesort 与临时表。
六、第五步:使用 SQL Profile 与实际耗时验证
可以使用 SHOW PROFILE 命令查看 SQL 各阶段耗时。
示例:
sql
SET profiling = 1;
SELECT * FROM orders WHERE user_id=10000;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
输出:
| Stage | Duration | Detail |
|--------|-----------|--------|
| parsing sql | 0.0001 | SQL解析 |
| optimizing | 0.0023 | 优化器分析 |
| executing | 0.1105 | 扫描行/返回结果 |
若执行阶段耗时占比过高,说明索引或过滤逻辑仍可优化。
七、第六步:改写 SQL 与缓存优化
① 避免 SELECT *
只查询必要字段,减少 I/O 与网络传输。
sql
SELECT id, name FROM user WHERE id=1;
② 用 EXISTS 替代 IN
sql
-- ❌ 慢
SELECT * FROM user WHERE id IN (SELECT user_id FROM orders);
-- ✅ 快
SELECT * FROM user WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id=user.id);
③ 合理使用 LIMIT
分页时配合索引字段:
sql
SELECT * FROM orders WHERE id > ? ORDER BY id LIMIT 20;
④ 引入缓存层
- Redis 缓存热点数据;
- 使用 Query Cache(8.0 已废弃)或应用层缓存。
八、SQL 优化流程总结图
┌────────────────────────────┐
│ 1️⃣ 开启慢查询日志 │
├────────────────────────────┤
│ 2️⃣ 分析日志找慢 SQL │
├────────────────────────────┤
│ 3️⃣ Explain 看执行计划 │
├────────────────────────────┤
│ 4️⃣ 建索引 / 改写 SQL │
├────────────────────────────┤
│ 5️⃣ SHOW PROFILE 验证耗时 │
├────────────────────────────┤
│ 6️⃣ 实测压测对比效果 │
└────────────────────────────┘
这套流程就是数据库调优工程师的日常操作路径。
九、面试高频问题与答题模板
| 问题 | 答案要点 |
|---|---|
| Q1:如何开启慢查询日志? | SET GLOBAL slow_query_log=1; |
| Q2:慢查询阈值怎么设置? | long_query_time=1 表示超 1 秒记录。 |
| Q3:慢查询如何分析? | 使用 pt-query-digest 聚合分析。 |
| Q4:Explain 重点看什么? | type、key、rows、Extra。 |
| Q5:如何判断 SQL 是否走索引? | 看 type≠ALL 且 key 不为空。 |
| Q6:如何降低 rows 数量? | 优化索引设计,减少扫描。 |
| Q7:慢 SQL 优化的一般步骤? | 开日志 → 分析 → Explain → 索引优化 → 验证。 |
十、总结
慢查询优化并不是单点问题,而是一整套系统性流程。
一句话记住:
"先找出谁慢,再分析为什么慢,最后让它变快。"
掌握这套流程后,你不仅能在面试中答得漂亮,
还能在真实项目中定位瓶颈、精准调优。
下一篇(第 17 篇),我将写------
《MySQL 索引失效的十大原因与优化方案》 ,
总结最常见的索引失效场景(函数、隐式转换、范围中断等)及优化思路。