分组查询慢的主因是未走索引、过滤不充分或GROUP BY字段高基数;需通过EXPLAIN检查key、rows和Extra列,优先用WHERE过滤、合理设计联合索引、避免函数干扰,并控制分组粒度。

分组查询慢,八成是因为没走索引、数据没过滤干净,或者 GROUP BY 字段本身太散。先别急着调优,直接看 EXPLAIN 输出里有没有 Using temporary 和 Using filesort —— 有这两个,基本就坐实了问题根源。
怎么确认是不是索引没生效
执行 EXPLAIN SELECT ... GROUP BY ...,重点看三列:key 是否为 NULL(没用索引)、rows 是否远大于实际结果行数(扫描太多)、Extra 是否出现 Using temporary(强制临时表)或 Using filesort(额外排序)。如果 GROUP BY 字段是 user_id 这种高基数字段,哪怕有索引,rows 也可能爆表 —— 这时候索引存在但不解决问题。
- 联合索引顺序必须匹配查询逻辑:比如
WHERE dept_id = ? AND status = 'active' GROUP BY created_at,索引应建为(dept_id, status, created_at),而不是反过来 - 别在
GROUP BY字段上套函数:写成GROUP BY DATE(created_at)会直接让索引失效,改用created_at BETWEEN '2026-07-01' AND '2026-07-02'配合索引 - MySQL 8.0+ 支持函数索引,但得显式创建:
CREATE INDEX idx_group_date ON orders ((DATE(created_at))),不是所有版本都支持
为什么 WHERE 比 HAVING 更关键
HAVING 是分组后过滤,意味着数据库已经生成了全部分组结果才开始筛;WHERE 是分组前过滤,能直接砍掉大量原始行。比如查“昨天各城市的订单数”,写成 HAVING created_at > '2026-07-01' 是灾难性的 —— 它会让数据库先按城市分组全表,再剔除时间不符的组。
- 所有能提前限定范围的条件,必须塞进
WHERE:时间范围、状态码、业务分区 ID - 分区表不是银弹:只有查询能精准命中单个分区(如
WHERE sale_date = '2026-07-01'),才能跳过其他分区扫描 - 如果业务允许,用物化中间结果:比如先
INSERT INTO tmp_daily_stats SELECT city, COUNT(*) FROM orders WHERE sale_date = '2026-07-01' GROUP BY city,后续直接查这张小表
高基数字段分组到底能不能做
按 order_id 或 user_id 分组,本质上是在做“去重计数”或“逐条聚合”,这不是分组查询的设计场景。数据库会为每个唯一值开一个分组槽,内存撑不住就写磁盘临时表,速度断崖下跌。
- 检查是否真需要粒度这么细:统计“每人订单数”不如先统计“每城市订单数”,再结合用户画像抽样分析
- 替代方案优先级:窗口函数(
COUNT(*) OVER (PARTITION BY user_id))比GROUP BY user_id更可控;近似函数(APPROX_COUNT_DISTINCT(user_id))比精确去重快得多 - 如果必须精确且高基数,拆成两阶段:先用
WHERE缩小时间/区域范围,再用应用层分批拉取、合并结果,避免单次 DB 压力过大
真正卡住性能的,往往不是语法写错,而是默认把 GROUP BY 当成万能聚合工具,忽略了它背后依赖排序和内存分配的本质。索引、过滤、粒度,三者缺一不可 —— 少盯一个,优化就白做。

















