MySQL 如何优化 GROUP BY 和 ORDER BY?(收藏级指南)
在 SQL 优化中,GROUP BY 和 ORDER BY 是最容易导致性能“暴跌”的两个关键字。
一旦使用不当,轻则创建临时表,重则出现 filesort、磁盘 I/O 暴增。
-
• 为什么 GROUP BY / ORDER BY 慢? -
• 如何写索引让它们“飞起来”? -
• 避免哪些坑?
一、为什么 GROUP BY / ORDER BY 很慢?
最本质的原因:
GROUP BY 和 ORDER BY 都需要排序(Sort)。
如果无法使用索引,就得用 filesort(排序算法)+ 临时表。
排序 + 临时表 = 直接慢成 PPT。
出现以下情况必慢:
1、无法使用索引排序
2、排序字段不连续(索引失效)
3、分组字段没有合适索引
4、排序字段与条件字段冲突
5、GROUP BY 使用隐式排序
6、使用 DISTINCT + ORDER BY 多字段排序
7、返回太多数据,临时文件写入磁盘
想优化,就是想办法:
尽可能让排序走索引,减少排序量,避免临时表。
二、GROUP BY 的优化策略
1)给 GROUP BY 字段建索引(最有效)
示例:
SELECT age, COUNT(*)
FROM user
GROUP BY age;
为 age 建索引:
CREATE INDEX idx_user_age ON user(age);
MySQL 会利用索引的有序性,直接完成分组,而不需要额外排序。
2)WHERE 条件字段 + GROUP BY 字段做联合索引
SELECT city, COUNT(*)
FROM user
WHERE status = 1
GROUP BY city;
此时应建:
CREATE INDEX idx_status_city ON user(status, city);
原因:
-
• WHERE 过滤 first -
• GROUP BY 分组 second
顺序必须和 SQL 一致,否则无效。
3)使用 GROUP BY 时关闭默认排序
MySQL 默认会对 GROUP BY 做排序。其实很多场景不需要。
可以加:
GROUP BY city ORDER BY NULL;
减少一次排序,性能有显著提升。
4)避免 GROUP BY + ORDER BY 字段不一致
GROUP BY city
ORDER BY age
那 MySQL 必须再排一次序 → 非常慢。
如果业务允许,将其统一为同一字段。
三、ORDER BY 的优化策略
1)最关键:ORDER BY 尽量使用索引排序
要使用索引排序必须满足:
排序字段必须是同一个索引中的最左前缀
并且
排序方向一致(全部 ASC 或全部 DESC)
示例:
SELECT *
FROM user
ORDER BY age, id;
索引:
CREATE INDEX idx_age_id ON user(age, id);
排序会直接利用 B+Tree 的顺序,无需 filesort。
2)WHERE 条件字段与 ORDER BY 字段冲突,可能导致排序失效
❌ 错误示例:
WHERE city = 'beijing'
ORDER BY age
如果索引是:
(city, age)
理论上应该能用索引排序,但如果 city 过滤后只剩很少数据,优化器可能判断“排序成本更低”,而不用索引 → filesort。
解决:
✔ 强制优化器使用索引(不推荐)
✔ or 更合理的方式:减少扫描量,如分页/索引覆盖
3)减少排序字段数量
避免多字段排序,尤其是 3 个以上字段。
⚠ 越多字段 → 越不可能用上索引。
4)避免 ORDER BY RAND()
这是性能灾难!
会导致:
-
• 生成随机值 -
• 对全表随机排序 -
• filesort 最慢模式
优化方法:使用主键随机 ID + limit
5)避免大数据量 ORDER BY,使用条件过滤 + 分页优化
比如:
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
这会排序十万行,再丢掉 99980 行 → 极慢。
优化方法:
SELECT *
FROM orders
WHERE id > 上一次最大ID
ORDER BY id
LIMIT 20;
利用主键范围减少排序量 → 性能可提升百倍。
四、GROUP BY + ORDER BY 同时使用时(重灾区)
最常见 SQL:
SELECT city, COUNT(*)
FROM user
GROUP BY city
ORDER BY COUNT(*) DESC;
COUNT(*) 是动态生成值 → 无法走索引 → 必然 filesort
优化:
-
• 改成子查询(外层排序数据量小)
SELECT *
FROM (
SELECT city, COUNT(*) AS cnt
FROM user
GROUP BY city
) tmp
ORDER BY cnt DESC;
减少排序行数,通常可快 30%~90%。
五、总结:写 SQL 时必须记住的 8 条原则
1、 GROUP BY / ORDER BY 能走索引最优先
2、 索引顺序必须与 SQL 一致:WHERE → GROUP BY → ORDER BY
3、 GROUP BY 加 ORDER BY NULL(如果不需要排序)
4、ORDER BY 必须同向排序(都 ASC 或都 DESC)
5、多字段排序必须是最左前缀
6、避免 GROUP BY 和 ORDER BY 字段不一致
7、 大数据分页必须做分页优化(ID 范围筛选)
8、避免 filesort + 临时表(性能噩梦)

