group by字段顺序必须严格匹配索引最左前缀,否则索引失效;where等值条件应置于索引最左,再接group by列,且asc/desc方向须一致,如group by dept_id, status对应索引(created_at, dept_id, status)。

GROUP BY字段顺序必须严格匹配索引最左前缀
数据库不会为乱序的 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 dept, COUNT(*) FROM emp WHERE status = 'active' GROUP BY dept;
推荐索引:(status, dept) —— 过滤后数据天然按 dept 有序,分组无需额外排序;若 SELECT 中还含 MAX(salary),则索引应扩展为 (status, dept, salary),否则仍需回表。
- 非聚合字段(如
MAX(col)、SUM(val))若不在索引中,大概率触发回表 - 长字符串或
TEXT类型分组字段会显著拖慢哈希构建,优先用INT或短VARCHAR
检查执行计划确认是否真用上索引
务必用 EXPLAIN FORMAT=TREE(MySQL 8.0+)或 EXPLAIN ANALYZE(PostgreSQL)验证:
- 看
type是否为ref/range/const(非ALL或index) - 看
Extra是否出现Using temporary; Using filesort—— 出现即说明分组未走索引排序 - 观察
rows预估扫描行数,是否远超实际返回的分组数
如果发现没走索引,优先检查字段类型一致性、NULL 值处理(WHERE col IS NOT NULL 有时能激活索引)、以及统计信息是否过期(可运行 ANALYZE TABLE 更新)。
最常被忽略的一点:索引顺序错位比没建索引更危险——它看起来“有索引”,却因顺序不匹配导致全表扫描,而你可能很久都察觉不到。










