优化慢 SQL 是数据库性能调优的核心工作之一。以下是系统性、实用的 慢 SQL 优化方法论,适用于 MySQL(尤其是 InnoDB 引擎),也部分适用于其他关系型数据库。
🔍 一、定位慢 SQL
1. 开启慢查询日志(Slow Query Log)
-- 查看是否开启
SHOW VARIABLES LIKE 'slow_query_log';
-- 设置慢查询阈值(例如超过 1 秒记录)
SET GLOBAL long_query_time = 1;
-- 开启慢日志(临时)
SET GLOBAL slow_query_log = ON;
日志文件路径:/var/log/mysql/slow.log(Linux 默认)
建议:生产环境长期开启慢日志,配合
log_queries_not_using_indexes=ON捕获无索引查询。
2. 使用 EXPLAIN 分析执行计划
EXPLAIN SELECT * FROM users WHERE age > 30 ORDER BY name;
重点关注字段:
type:访问类型(ALL表示全表扫描,最差;ref/range较好;const最佳)key:实际使用的索引rows:预估扫描行数(越小越好)Extra:是否有Using filesort(需排序)、Using temporary(建临时表)等性能杀手
💡 进阶:使用
EXPLAIN FORMAT=JSON或EXPLAIN ANALYZE(MySQL 8.0+)获取更详细信息。
二、常见慢 SQL 原因 & 优化策略
1. 缺少合适索引
- 现象 :
type=ALL,rows很大 - 优化 :
-
为
WHERE、JOIN、ORDER BY、GROUP BY涉及的字段加索引 -
遵循最左前缀原则 (复合索引
(a,b,c)可用于a=1 AND b=2,但不能用于b=2单独查询) -
覆盖索引:索引包含所有查询字段,避免回表
-- 查询只查 id 和 name,可建 (status, name) 索引实现覆盖 SELECT id, name FROM users WHERE status = 1;
-
2. 索引失效
常见导致索引失效的操作:
- 对字段使用函数或表达式:
WHERE YEAR(create_time) = 2025
→ 改为范围查询:WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31' - 隐式类型转换:
WHERE user_id = '123'(user_id 是 INT) - 使用
!=、NOT IN、LIKE '%xxx'(前导通配符) - OR 条件未全部命中索引(可用
UNION替代)
3. 大量数据排序(Using filesort)
- 原因 :
ORDER BY字段无索引,或与WHERE条件索引不匹配 - 优化 :
-
为
ORDER BY字段建索引(最好与WHERE条件组成联合索引) -
减少
SELECT *,只查必要字段(减少排序内存占用) -
分页深翻页问题:用「游标分页」替代
LIMIT 10000, 20-- 传统(慢): SELECT * FROM orders ORDER BY id LIMIT 10000, 20; -- 优化(快): SELECT * FROM orders WHERE id > 10000 ORDER BY id LIMIT 20;
-
4. 临时表(Using temporary)
- 常见于 :
GROUP BY+ORDER BY不一致、子查询不当 - 优化 :
- 确保
GROUP BY字段有索引 - 避免在
SELECT中使用非聚合字段(除非ONLY_FULL_GROUP_BY关闭) - 将复杂子查询改写为
JOIN
- 确保
5. JOIN 性能差
-
原则 :
- 小表驱动大表(MySQL 通常自动优化)
- 被驱动表(右表)必须有索引
- 避免多表 JOIN(>3 张表慎用)
-
示例 :
-- orders 表大,users 表小 → users 驱动 orders SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 1;→ 确保
orders.user_id有索引!
6. 大分页(Deep Pagination)
- 问题:
LIMIT 1000000, 10会扫描前 100 万行 - 解决方案:
- 记录上一页最大 ID,用
WHERE id > last_id LIMIT 10 - 业务允许时,限制最大翻页深度(如只允许查前 100 页)
- 记录上一页最大 ID,用
7. 锁竞争或事务过大
- 长事务会阻塞其他操作,导致"看似慢查询"
- 优化:
- 缩短事务(及时 COMMIT)
- 避免在事务中处理业务逻辑
- 使用
SELECT ... FOR UPDATE时确保有索引,否则锁全表!
三、高级优化手段
| 方法 | 说明 |
|---|---|
| SQL 重写 | 将子查询改为 JOIN,拆分复杂查询 |
| 读写分离 | 读请求走从库,减轻主库压力 |
| 分库分表 | 数据量超千万级时考虑(如用户表按 user_id 分片) |
| 缓存 | 高频查询结果缓存到 Redis(注意缓存一致性) |
| 归档历史数据 | 删除或迁移冷数据,减少表大小 |