group by字段顺序必须严格匹配索引最左前缀,否则索引失效;where条件列须置于索引最左,接着是group by列且顺序、方向一致,函数操作或隐式转换会导致索引失效。

GROUP BY字段顺序必须严格匹配索引最左前缀
MySQL和PostgreSQL不会为GROUP BY a, b, c自动适配(b, a, c)或(a, c)索引。只有索引以a, b, c开头,才能跳过排序步骤;否则执行计划里必然出现Using filesort或Using temporary。
常见错误是把高频分组字段单独建索引,比如只建INDEX(customer_id),但查询是GROUP BY customer_id, status——这时status无法利用索引,仍要排序。
- ✅ 正确:查询
GROUP BY dept_id, status→ 建INDEX(dept_id, status, created_time) - ❌ 无效:
INDEX(status, dept_id)或INDEX(dept_id)(后者虽能过滤,但status无序) - 注意:索引字段顺序 ≠ GROUP BY书写顺序的“看起来一样”,而是物理存储顺序必须一致
WHERE条件字段必须前置在索引中
索引不是先服务分组,而是先服务过滤。如果WHERE org_id = ? AND dept_id = ? GROUP BY status,索引必须是(org_id, dept_id, status),而不是(status, org_id, dept_id)。
数据库会用前缀字段快速切出数据子集,再在这个子集内天然按status物理有序;反过来,status放最左,org_id和dept_id就失去过滤能力,等价于全表扫描后分组。
- 范围条件(如
created_at > '2024-01-01')只能放索引末尾,否则会截断后续字段的索引效力 - 等值条件(
=、IN)优先放前面,区分度高的字段更靠前(如status IN ('shipped', 'completed')比user_id = 123更适合作为第一列) -
EXPLAIN里key_len异常小,往往说明WHERE没用上索引全部前缀
避免在GROUP BY中使用函数或表达式
GROUP BY DATE(created_at)这类写法会让整个索引失效——MySQL无法对每行实时计算后再走B+树查找,只能全表扫描+内存计算。
这不是“可能慢”,是必然触发Using temporary,且无法通过加索引绕过。
- 高频固定粒度(如按天统计):新增冗余字段
created_date DATE,建索引INDEX(created_date, user_id),查询改用WHERE created_date BETWEEN ... GROUP BY created_date - 低频灵活需求:先用范围缩小数据量,例如
WHERE created_at >= '2024-06-01' AND created_at - MySQL 5.7不支持函数索引,
INDEX(DATE(created_at))完全无效
覆盖索引能省掉回表,但不改变分组方式
索引包含SELECT中所有非聚合字段(如SELECT user_id, SUM(amount), MAX(note)),可避免回表,但能否加速分组,只取决于WHERE + GROUP BY字段是否被索引前缀覆盖。
比如查询SELECT user_id, COUNT(*) FROM orders WHERE status = 'shipped' GROUP BY user_id,最优索引是INDEX(status, user_id);加amount进去(INDEX(status, user_id, amount))只对SUM(amount)有用,对分组本身没提速作用。
-
INCLUDE在SQL Server中可用,在MySQL中需靠联合索引末尾字段模拟覆盖 - 别为
COUNT(*)或MAX(user_id)额外加字段进索引——这些不需要回表,也不依赖索引内容 - 真正容易被忽略的是:索引再好,如果
SELECT *或含TEXT/JSON字段,优化器大概率放弃松散扫描,直接走临时表











