GROUP BY字段顺序必须与索引最左前缀严格一致才能命中索引;如GROUP BY a, b仅能使用(a, b)索引,(b, a)或(a, c, b)均无效,否则触发全表扫描或filesort。

GROUP BY列顺序必须严格匹配索引最左前缀
索引不会因为“包含所有分组字段”就自动生效。MySQL 和 PostgreSQL 都要求 GROUP BY 字段的顺序与复合索引定义顺序完全一致,且方向(ASC/DESC)最好也一致。比如业务固定查 GROUP BY status, region,却建了 INDEX(region, status),那这个索引对分组毫无帮助——优化器只能全表扫描后内存排序。
常见错误现象:EXPLAIN 显示 type: ALL 或 Extra: Using temporary; Using filesort,说明分组没走索引排序。
- WHERE 条件列应放在索引最左侧(用于快速过滤)
- GROUP BY 列紧随其后(用于天然有序分组)
- 若还有
ORDER BY,可追加到索引末尾,但优先保证 GROUP BY 顺序 - 示例:查询
SELECT dept, COUNT(*) FROM emp WHERE status = 'active' GROUP BY dept→ 推荐索引INDEX(status, dept)
避免在分组字段上用函数或表达式
一旦对 GROUP BY 字段做计算,索引基本失效。数据库无法用原始索引值直接匹配函数结果,只能回退到逐行计算再分组。
典型失效写法:
-
GROUP BY YEAR(create_time)→ 时间索引无法命中 -
GROUP BY UPPER(name)→ 字符索引失效 -
GROUP BY DATE(created_at)→ 同样绕过索引,应改用范围条件预过滤
正确做法是把逻辑前移到 WHERE:比如想按年分组,先限定时间范围 WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01',再 GROUP BY YEAR(create_time);虽然函数仍在,但数据量已大幅压缩,且部分引擎(如 MySQL 8.0+)可能对常量范围内的函数做优化。
覆盖索引能跳过回表,但必须显式列出所有 SELECT 字段
如果 SELECT 中有非聚合字段(如 MAX(note)、ANY_VALUE(tag)),又希望不回表,就得让这些字段也出现在索引中——这就是覆盖索引。但它不是“建个索引就行”,而是要精确匹配查询所需列。
- 错误尝试:
INDEX(status, dept)无法支撑SELECT status, dept, MAX(updated_at) FROM t GROUP BY status, dept - 正确索引:
INDEX(status, dept, updated_at)—— 注意updated_at必须在最后,且不能有WHERE条件依赖它(否则顺序冲突) - PostgreSQL 支持函数索引,可建
INDEX ON t (status, dept, (MAX(updated_at))),但 MySQL 不支持聚合函数索引,别试
覆盖索引对 COUNT(*) 特别友好,多数引擎会直接统计索引页行数,比扫表快得多。
视图封装复杂分组时,索引必须建在底层表,而非视图名上
视图本身不存数据,也不自带索引(MySQL 完全不支持视图索引,PostgreSQL 仅支持带规则的物化视图)。很多人以为给视图建索引就能加速,其实只是白忙一场。
真正有效的做法是:
- 确保视图定义里没有
ORDER BY、LIMIT、CURRENT_DATE这类阻止下推的语法 - 在底层表上建好对应索引(比如视图基于
orders表分组,索引就得建在orders上) - 用
EXPLAIN ANALYZE确认执行计划里是否还出现Subquery Scan on xxx_view—— 出现就说明视图没被内联,索引再好也白搭 - MySQL 用户必须显式声明
CREATE ALGORITHM=MERGE VIEW,否则默认用 TEMPTABLE,必然多一层临时表
复杂点在于:视图里用了 date_trunc('day', order_time) 这种表达式,即使底层 order_time 有索引,视图查询仍大概率无法利用——除非你额外在该表达式上建函数索引(PG 支持,MySQL 不支持),或者改用生成列 + 普通索引。

















