最有效的是覆盖WHERE过滤列+GROUP BY列的复合索引,且严格遵循最左前缀原则;例如WHERE status = 1 GROUP BY user_id应建(status, user_id)索引,确保执行计划中type为ref/range、Extra含Using index且无Using temporary或filesort。

GROUP BY 用什么索引最有效
复合索引必须覆盖 GROUP BY 列 + WHERE 过滤列,且遵循最左前缀原则。比如查询 SELECT user_id, COUNT(*) FROM orders WHERE status = 1 GROUP BY user_id,应建 (status, user_id) 索引,而不是单独给 user_id 建索引。
常见错误是只对 GROUP BY 列建单列索引,结果执行计划里仍显示 Using temporary; Using filesort —— 这说明 MySQL 不得不额外排序+临时表,索引没被用于分组加速。
- 如果
WHERE条件有多个列(如status = 1 AND type = 'paid'),优先把选择性高的列放前面,比如type值更分散,就放(type, status, user_id)左侧 - 避免在
GROUP BY列上使用函数,如GROUP BY DATE(create_time)会让索引失效;改用范围条件 + 按原始列分组 - 若分组后还要
ORDER BY同样字段(如GROUP BY user_id ORDER BY user_id),复合索引可一并覆盖,减少排序开销
COUNT/SUM/AVG 怎么让索引真正起作用
COUNT(*) 在有 WHERE 条件时,依赖对应索引过滤后的行数统计;COUNT(column) 要求该列非空(或加 NOT NULL 约束),才能用该列上的索引跳过 NULL 行;SUM 和 AVG 本身不能“直接算”,但配合覆盖索引能避免回表,大幅减少 I/O。
例如:查询 SELECT user_id, SUM(amount) FROM orders WHERE status = 1 GROUP BY user_id,若存在索引 (status, user_id, amount),则所有字段都在索引中,执行计划会出现 Using index,否则即使有 (status, user_id),仍要回表取 amount 值。
-
MAX(id)或MIN(id)在主键或唯一索引上几乎是 O(1),无需额外优化 -
COUNT(*)无WHERE时,InnoDB 必须扫聚簇索引,此时建任何辅助索引都无效 - 不要为低选择性列(如
is_deleted TINYINT)单独建索引试图加速COUNT,优化器大概率忽略它
EXPLAIN 看什么才能确认聚合走索引
关键不是看 key 是否非 NULL,而是结合 type、Extra 和 rows 综合判断。真正有效的聚合索引利用,应同时满足:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
type是ref、range或index(避免ALL) -
Extra中出现Using index(覆盖索引)、且没有Using temporary或Using filesort -
rows显著小于表总行数(比如从千万级降到几万级)
一个典型反例:EXPLAIN SELECT user_id, COUNT(*) FROM orders GROUP BY user_id 若 rows 接近全表、Extra 含 Using temporary,说明没走索引分组,只是扫描后内存聚合。
为什么加了索引还是慢?几个隐蔽坑点
索引建了 ≠ 被用;被用了 ≠ 真高效。最容易被忽略的是数据分布和统计信息偏差。
- 长期未
ANALYZE TABLE orders,优化器基于过期的行数估算选择执行计划,可能放弃本该走的索引 - 复合索引列顺序错位:比如建了
(user_id, status),但查询是WHERE status = 1 GROUP BY user_id,因不满足最左前缀,索引无法用于WHERE过滤 - 隐式类型转换:
WHERE status = '1'(字符串)对比status TINYINT列,触发全表扫描,EXPLAIN里key为空 - 索引碎片严重:大量
UPDATE后未OPTIMIZE TABLE,B+ 树层级变深,I/O 效率下降
聚合查询的性能拐点往往不在 SQL 写法,而在索引是否真正匹配数据访问模式——别只盯着 CREATE INDEX,多跑几次 EXPLAIN 对比 rows 和 Extra 变化,才是实操关键。










