MySQL 慢查询优化做了这么多,为什么还是慢
MySQL 慢查询优化做了这么多,为什么还是慢
加了索引还是慢,EXPLAIN 看了半天看不懂,优化后没什么效果——这是很多团队做慢查询优化时的真实状态。
问题往往不是某个技巧没掌握,而是分析路径走反了。
一个典型场景
一张订单表,1000万数据,业务反馈后台导订单越来越慢:
SELECT * FROM orders
WHERE user_id = 12345
AND status = 'paid'
AND created_at > '2026-01-01'
ORDER BY created_at DESC
LIMIT 50;
执行时间 8 秒。团队开始排查:
- 查了执行计划,用了
idx_user_status_created索引 - 查了慢日志,单次查询 8 秒
- 加了复合索引
(user_id, status, created_at)
然后呢?还是 8 秒。
问题不在索引够不够多,在于分析路径走反了。
大多数人会先查 EXPLAIN,但 EXPLAIN 看什么
EXPLAIN SELECT * FROM orders
WHERE user_id = 12345
AND status = 'paid'
AND created_at > '2026-01-01'
ORDER BY created_at DESC
LIMIT 50;
结果大概是:
id select_type table type key rows Extra
1 SIMPLE orders ref idx_user_status_created 523847 Using index condition; Using where; Using filesort
type 是 ref,走了索引,rows 返回 52 万行——问题就藏在这里。
rows 不是“返回 50 行”,而是“扫描了 52 万行再过滤”。这个数字才是关键。
很多人只看 type 是否 ALL,以为不是全表扫描就没事。但索引扫描 50 万行再取 50 条,本身就是性能杀手。
真正的问题:数据分布决定了查询成本
这条 SQL 慢,不是因为索引不够,而是因为 user_id = 12345 这个用户有 52 万条订单。
MySQL 选错索引,或者被迫选了一个过滤性差的索引,根因往往是统计信息不准。
-- 查看表统计信息
SHOW TABLE STATUS FROM db LIKE 'orders';
-- 查看字段基数
SHOW INDEX FROM orders;
-- 重新统计
ANALYZE TABLE orders;
ANALYZE TABLE 是很多人忽略的一步。InnoDB 的统计信息是采样的,大数据量变化后可能严重失真,导致优化器选错索引。
怎么确认慢在哪
用 EXPLAIN ANALYZE(MySQL 8.0+)可以看实际执行成本:
EXPLAIN ANALYZE SELECT * FROM orders
WHERE user_id = 12345
AND status = 'paid'
AND created_at > '2026-01-01'
ORDER BY created_at DESC
LIMIT 50;
输出会包含:
- 预估 rows 和实际 rows
- 每一阶段的实际耗时
- 索引有没有真正被用上
如果 rows 预估和实际差距极大,先跑 ANALYZE TABLE。
另一个容易忽略的点:ORDER BY 带了 filesort
看前面的 EXPLAIN 结果,Extra 里有一项 Using filesort。
这才是真正的性能瓶颈。filesort 是 MySQL 在内存里做排序,当数据量大时:
- 排序数据超过
sort_buffer_size,会落盘 - 跨索引排序本身就要回表
如果排序字段在索引里,可以直接利用索引顺序,避免 filesort:
-- 把 created_at 加入索引尾部
ALTER TABLE orders
ADD INDEX idx_user_status_created (user_id, status, created_at);
-- 查询改为覆盖索引,无需回表
SELECT created_at, order_id, status, amount FROM orders
WHERE user_id = 12345
AND status = 'paid'
AND created_at > '2026-01-01'
ORDER BY created_at DESC
LIMIT 50;
但注意:ORDER BY created_at DESC 需要跟索引顺序一致。如果索引是 ASC 而查询是 DESC,MySQL 可能无法利用索引顺序。
还有一个常见问题:LIMIT offset 过大
后台分页常见的写法:
SELECT * FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC
LIMIT 100000, 50;
这种写法 MySQL 需要先扫描前 10 万行,再返回后面的 50 行。翻页越深越慢。
优化思路:
- 改用游标分页(keyset pagination)
-- 第一页
SELECT * FROM orders
WHERE user_id = 12345
ORDER BY created_at DESC
LIMIT 50;
-- 后续页:记住上一页最后一条的 created_at
SELECT * FROM orders
WHERE user_id = 12345
AND created_at < '2026-04-01 10:30:00'
ORDER BY created_at DESC
LIMIT 50;
- 或者限制最大翻页深度,超过后提示用户精确搜索
加了索引还是慢,常常是这几个原因
- 统计信息不准:
ANALYZE TABLE可以解决 - 选了过滤性差的索引:强制指定索引
USE INDEX - ORDER BY 无法利用索引顺序:调整索引列顺序或排序方向
- 回表成本高:改用覆盖索引
- offset 过大:改用游标分页
一个完整的排查流程
-- 1. 开启慢查询日志,看哪些查询经常超时
SHOW VARIABLES LIKE 'slow_query_log%';
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- 2. 查看慢查询记录
SHOW GLOBAL STATUS LIKE 'Slow_queries';
-- 或直接查慢日志文件
-- 3. 确认统计信息
ANALYZE TABLE orders;
-- 4. 看执行计划
EXPLAIN ANALYZE SELECT ...;
-- 5. 如果有问题,强制指定索引验证
EXPLAIN SELECT * FROM orders USE INDEX (idx_xxx) WHERE ...;
-- 6. 改写 SQL 后再验证
EXPLAIN SELECT ... LIMIT offset, count;
最关键的一条:先确认问题在哪,再动手
大多数慢查询优化失败,是因为:
- 一上来就加索引,没有先确认问题是索引选错还是统计信息不准
- 改了索引但没有重验证 EXPLAIN
- 改了 SQL 但没有确认执行计划变化
慢查询优化是个验证循环:
定位慢 SQL → 看执行计划 → 确认瓶颈 → 修改 → 验证效果
每一步都要有数据支撑,不是凭感觉加索引。
后来怎么优化的
回到开头的场景,加了索引、做了 ANALYZE TABLE、改了 SQL 结构后:
- 单次查询从 8 秒降到 200ms
- 主要改动:强制使用
(user_id, created_at)索引,改为覆盖索引,移除status条件(业务实际不需要精确过滤)
优化不是堆索引,是让查询精准命中数据。
