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

GROUP BY字段顺序必须严格匹配索引最左前缀
MySQL 和 PostgreSQL 都不会为乱序的 GROUP BY 字段自动重排索引访问路径。比如查询是 GROUP BY status, region,但你建了 (region, status) 索引,那这个索引基本无效——优化器无法跳过排序阶段,大概率触发 Using temporary; Using filesort。
真正起效的索引必须满足:WHERE 条件列(如有)在最左,接着是 GROUP BY 列,且顺序、方向(ASC/DESC)完全一致。例如:
-
SELECT dept_id, COUNT(*) FROM orders WHERE created_at > '2024-01-01' GROUP BY dept_id, status;→ 推荐索引:(created_at, dept_id, status) - 若还带
ORDER BY dept_id,无需额外处理;若ORDER BY status DESC,则索引末尾需显式声明status DESC(MySQL 8.0+ 支持)
WHERE 条件列必须放在索引最左侧
索引不是“只要包含 GROUP BY 字段就行”,而是要让数据库先快速定位数据子集,再在这个子集上分组。如果 WHERE 条件没走索引,哪怕 GROUP BY 字段有索引,也得扫全表。
常见错误包括:
- 对
WHERE user_id = '123'中的user_id(INT 类型)传字符串 → 触发隐式类型转换,索引失效 -
WHERE YEAR(create_time) = 2024→ 函数操作使时间索引完全不可用 - 条件选择率太高(如
WHERE status IN ('a','b','c')返回 40% 行数),优化器可能主动放弃索引走全表
正确做法是把函数逻辑下推:用 create_time >= '2024-01-01' AND create_time 替代 <code>YEAR()。
COUNT(*) 和覆盖索引能避免回表
COUNT(*) 是聚合中最轻量的操作,尤其当它和 WHERE + GROUP BY 共同出现时,如果所有涉及字段都在一个索引里,InnoDB 可能直接走聚簇索引或二级索引的叶子节点完成统计,不读数据页。
例如:
SELECT customer_id, COUNT(*) FROM orders WHERE status = 'shipped' GROUP BY customer_id;
建索引 (status, customer_id) 后,执行计划中 Extra 显示 Using index,说明全程只查索引树;若 SELECT 还加了 MAX(order_amount),而 order_amount 不在索引里,就会回表——这时应扩展索引为 (status, customer_id, order_amount)。
执行计划里出现 Using temporary 就该立刻干预
Using temporary 意味着 MySQL 正在内存或磁盘建临时表来存分组中间结果,这是性能拐点。它常和 Using filesort 成对出现,根本原因就是索引没覆盖好分组或排序需求。
验证方式很简单:
- 运行
EXPLAIN FORMAT=TREE(MySQL 8.0+)或EXPLAIN ANALYZE(PostgreSQL) - 看
type是否为ref/range,而非ALL或index - 检查
rows预估扫描行数是否远大于最终分组数(比如扫描 500 万行,只返回 200 个分组)
此时别急着调大 tmp_table_size,先确认索引设计是否漏掉了 WHERE 字段,或 GROUP BY 顺序是否错位——这些才是根因。
真正容易被忽略的是 NULL 值和统计信息。即使索引结构完全正确,WHERE col IS NOT NULL 有时能激活索引(尤其当该列 NULL 比例高时),而过期的统计信息会让优化器误判选择率,导致弃用本该生效的索引。定期 ANALYZE TABLE 不是可选项,是上线后必做动作。


















