分组查询慢主因是未先过滤再分组,导致全表扫描;应确保WHERE条件走索引、GROUP BY字段有合适复合索引、避免函数操作及回表,用EXPLAIN检查Using temporary和filesort。

分组查询慢,八成不是 GROUP BY 本身的问题,而是它被迫在全表或大结果集上做聚合——尤其当 WHERE 条件没走索引、或子查询没提前过滤时,GROUP BY 就成了压垮性能的最后一根稻草。
先过滤再分组,别让 GROUP BY 扫描几千万行
典型错误是写成:SELECT objectid, MAX(invalidtime) FROM am_card GROUP BY objectid WHERE objectid IN (SELECT objectid FROM backup_am_card_20200603)(语法虽不合法,但逻辑常见)。问题在于:数据库先对整个 am_card 表(几千万行)执行 GROUP BY,再用子查询结果去筛——GROUP BY 阶段就已耗尽资源。
正确做法是把筛选提前到聚合前:
- 用
INNER JOIN或WHERE objectid IN (...)先拿到目标objectid列表(小表驱动) - 再对这个缩小后的结果集做
GROUP BY - 如果
backup_am_card_20200603是临时备份表,确保其objectid字段有索引
避免在 GROUP BY 字段上做函数或计算
GROUP BY YEAR(create_time) 或 GROUP BY UPPER(name) 会直接导致索引失效,强制全表扫描+内存排序。这类操作 CPU 开销极大,且无法利用 create_time 上已有的索引。
替代方案:
- 用范围条件代替函数:
WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31',再GROUP BY DATE(create_time)(若需日期粒度) - 业务允许时,冗余一个预计算字段(如
create_year),并为其建索引 - 确认
GROUP BY的列是否真的需要函数处理——很多时候前端或应用层聚合更合适
检查执行计划里有没有 Using temporary 和 Using filesort
EXPLAIN 输出中若出现 Using temporary(建临时表)或 Using filesort(磁盘排序),说明分组或排序没走索引,正在消耗大量内存和 IO。
关键看三处:
-
type是否为ref或range(走了索引),而非ALL(全表扫描) -
rows是否接近表总行数——如果是,说明过滤没生效 -
key是否显示实际使用的索引名;若为NULL,索引没被选中
特别注意:复合索引必须满足最左前缀,比如索引是 (objectid, invalidtime),那么 WHERE objectid = ? GROUP BY invalidtime 无法利用该索引做分组加速。
聚合字段过多或含大字段时,警惕回表和内存溢出
如果 SELECT 里除了聚合函数还包含非分组字段(如 SELECT objectid, MAX(invalidtime), remark FROM am_card GROUP BY objectid),MySQL 8.0+ 会报错,老版本则可能随机取值——更糟的是,若 remark 是 TEXT 类型,即使只查 100 行,也可能因临时表过大触发磁盘 spill,拖慢整条语句。
务实做法:
- 严格遵循 SQL 标准:
SELECT中所有非聚合字段必须出现在GROUP BY子句中 - 大字段(
TEXT、BLOB)绝不参与分组查询;需要时,用关联子查询或应用层二次获取 - 用
SQL_BIG_SELECTS=1或调高tmp_table_size只是掩耳盗铃,根源在逻辑设计
真正卡住的从来不是语法,而是数据规模与操作意图之间的错配——分组前没筛、索引没对上、字段没精简,这三件事做扎实了,90% 的“慢分组”当场变快。

















