聚合查询慢八成因索引未按“过滤→分组→排序→覆盖”顺序创建;需将where等值列置最左、范围列次之,group by列紧随其后,order by列再后,聚合字段放末尾构成覆盖索引。

聚合查询慢,八成是因为索引没按“过滤→分组→排序→覆盖”顺序建。 直接在 GROUP BY 字段上建单列索引基本没用,也别指望靠 COUNT(*) 或 SUM(amount) 字段单独建索引就能提速——数据库没法只靠聚合字段定位数据行。
WHERE 条件字段必须放在复合索引最左边
数据库用索引找数据,第一步永远是“定位范围”。如果查询带 WHERE order_date BETWEEN '2025-01-01' AND '2025-06-30',但索引是 (customer_id, order_date),那 order_date 就无法被有效利用——违反最左前缀原则。
实操建议:
- 把所有
WHERE中的等值条件列(如status = 'paid')放索引最左,高基数列优先(比如user_id比status更适合作左首列) - 范围条件列(如
BETWEEN、>)只能放在等值列之后,且只能有一个;它后面的所有列都无法用于索引查找(但可能用于排序或覆盖) - 用
EXPLAIN看key_len:数值越小,说明实际用到的索引前缀越短,大概率漏了关键过滤列
GROUP BY 字段紧接 WHERE 列之后,避免 Using temporary
当 GROUP BY 字段不在索引中,或顺序不匹配时,MySQL/PostgreSQL 会强制创建临时表做分组,Extra 列出现 Using temporary 就是明确信号。
实操建议:
- 索引顺序应为:
(where_col1, where_col2, group_col1, group_col2)—— 分组字段必须紧跟过滤字段之后 - 如果
GROUP BY a, b,索引(a, b)可以消除临时表;但(b, a)不行,除非查询也写成GROUP BY b, a - ORDER BY 字段如果和 GROUP BY 一致(如
GROUP BY customer_id ORDER BY customer_id),可一并塞进索引末尾,避免Using filesort
把聚合字段加进索引末尾,实现覆盖索引
即使索引能支撑过滤和分组,如果 SELECT SUM(amount), COUNT(*) 还得回表取 amount 值,I/O 开销依然大。覆盖索引能让所有数据从索引页直接读出。
实操建议:
- 在索引末尾追加被聚合的列,例如查询是
SELECT customer_id, SUM(amount) FROM orders WHERE status='shipped' GROUP BY customer_id,索引应为(status, customer_id, amount) - 注意:
COUNT(*)不依赖具体列,但COUNT(col)会忽略 NULL,所以若用COUNT(status),就把status放进索引即可(它已在最左) - 验证是否覆盖:执行
EXPLAIN后看Extra是否出现Using index;没有这个字样,就还没真正覆盖
最容易被忽略的一点:索引列顺序不是靠猜,而是严格对应查询中 WHERE → GROUP BY → ORDER BY → SELECT 的执行逻辑链。少一个环节错位,前面全白搭;而一旦形成覆盖,哪怕千万级表,SUM 类查询也能压到 10ms 内返回。










