一、什么是慢查询?
慢查询通常指执行时间超过预期阈值的 SQL,比如:
- 单次执行超过 1s
- 在高并发场景下平均超过 100ms
- 导致 CPU / IO 飙高、接口超时
在 MySQL 中,可以通过 慢查询日志 来捕获这些 SQL。
sql
-- 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log';
-- 设置阈值(单位:秒)
SET GLOBAL long_query_time = 1;
二、慢查询优化的标准流程(核心)
先定位 → 再分析 → 后优化 → 最后验证
1️⃣ 开启慢查询日志,找到"真凶"
不要凭感觉优化,要用数据说话:
-
开启慢查询日志
-
使用
mysqldumpslow或pt-query-digest分析 -
重点关注:
- 执行次数多
- 平均耗时高
- 扫描行数大
2️⃣ 使用 EXPLAIN 分析执行计划
对可疑 SQL 执行:
ini
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
重点关注字段:
| 字段 | 关注点 |
|---|---|
| type | ALL(全表扫描)是大忌 |
| key | 实际使用的索引 |
| rows | 扫描行数,越小越好 |
| Extra | Using filesort / Using temporary 需警惕 |
✅ 目标:
- type 至少达到
range - key 不为 NULL
- 避免 filesort 和临时表
3️⃣ 索引优化(最常见的手段)
✅ 建索引的原则
- 高区分度字段(user_id、order_no)
- WHERE / JOIN / ORDER BY 常用字段
- 联合索引遵循最左前缀
- 控制索引数量(写多读少表尤其注意)
❌ 常见索引失效写法
- 对索引字段使用函数:
DATE(create_time) - 隐式类型转换:
phone = 13800000000 - 左模糊:
LIKE '%abc' - OR 条件中混合无索引字段
- 不符合最左前缀
✅ 覆盖索引(进阶)
sql
-- 索引 (user_id, status)
SELECT user_id, status FROM orders WHERE user_id = 123;
避免回表,显著提升性能。
4️⃣ SQL 改写优化
- 避免
SELECT * - 大分页优化:
sql
-- 避免
SELECT * FROM orders LIMIT 100000, 20;
-- 推荐
SELECT * FROM orders WHERE id > 100000 LIMIT 20;
- 减少不必要的排序和分组
- 拆分复杂 JOIN 和子查询
5️⃣ 表结构与业务层优化
- 冷热数据分离(历史数据归档)
- 读写分离(主写从读)
- 适当冗余字段,减少 JOIN
- 写多读少场景控制索引数量
三、加索引不是银弹(重要认知)
很多初学者容易陷入一个误区:
"查询慢?加索引!"
但现实情况是:
- 索引会降低 INSERT / UPDATE / DELETE 性能
- 索引占用磁盘空间
- 索引过多会增加死锁概率
- 返回大量数据时,索引效果有限
✅ 正确姿势:
索引解决的是"扫描行数过多"的问题,不是所有慢查询的解药。
四、什么时候考虑分库分表?
当以下情况出现时,索引优化已接近极限:
- 单表数据量超过千万级
- 写入成为明显瓶颈
- 硬件扩容成本过高
此时可以考虑:
- 分库分表(ShardingSphere / MyCat)
- 引入 ES / ClickHouse 处理复杂查询
⚠️ 注意:
- 分库分表会增加系统复杂度
- 非分片键查询会退化为全表扫描
- 应作为最后手段,而非首选方案
五、总结
慢查询优化不是一蹴而就的过程,而是一套组合拳:
慢查询日志定位 → EXPLAIN 分析 → 索引 + SQL 优化 → 表结构 / 架构调整
记住一句话:
索引是加速器,不是救命稻草;架构是解决扩展性问题的终极手段。
附:面试速记口诀
- 慢查先看日志
- EXPLAIN 看 type / key / rows
- 索引失效避函数、避左模、避隐转
- 覆盖索引少回表
- 分页不用大 offset
- 分表是最后一步