GROUP BY查询慢八成因未走索引分组,导致Using temporary或Using filesort;需用EXPLAIN确认,建最左前缀联合索引(如WHERE+GROUP BY字段顺序匹配),避免函数、隐式转换及单列索引。

GROUP BY 查询慢,八成是因为 MySQL 没走索引分组,硬扛排序和临时表;只要 EXPLAIN 里出现 Using temporary 或 Using filesort,就别调参、别换写法,先看索引是否匹配。
怎么一眼确认是 GROUP BY 拖慢了查询
直接跑 EXPLAIN FORMAT=TRADITIONAL SELECT ... GROUP BY ...,盯三处:
-
Extra列含Using temporary:说明 MySQL 正在用内存或磁盘临时表做分组,I/O 已成瓶颈 -
Extra列含Using filesort:默认对分组结果隐式排序,哪怕你没写ORDER BY -
type是ALL或index,且rows接近表总行数:WHERE 过滤失效,GROUP BY 只能在全量数据上算
GROUP BY 字段该建什么索引才真正起效
单列索引基本无效,必须建联合索引,且顺序不能错:
- 如果只有
GROUP BY a, b,索引必须是INDEX idx_ab (a, b);(b, a)不行,(a)单独也不行 - 如果有
WHERE status = 'paid'+GROUP BY user_id,索引应为INDEX idx_status_uid (status, user_id),过滤字段必须放最左 - 如果还查
SUM(amount),索引得扩展为INDEX idx_status_uid_amt (status, user_id, amount),否则仍要回表,可能触发Using temporary -
key_len要对得上:比如VARCHAR(255)字段实际只比前 10 字符,key_len应为 31(utf8mb4),若显示 765,说明索引定义和查询不匹配
哪些写法会让索引彻底失效
就算索引建得再准,以下写法也会让 MySQL 放弃使用:
-
GROUP BY DATE(created_at):函数包裹字段,B+ 树无法定位,全表扫描不可避免 -
GROUP BY UPPER(name):同上,改用冗余字段name_upper+ 索引,或 MySQL 8.0+ 的函数索引CREATE INDEX idx_name_up ON t ((UPPER(name))) -
HAVING COUNT(*) > 100:过滤动作发生在分组之后,意味着先算出全部分组再筛,WHERE 能前置的条件绝不要拖到 HAVING -
SELECT city, COUNT(*), MAX(address)但只建了(city)索引:MAX(address)需回表,覆盖失败,Extra仍可能出现Using temporary
索引解决不了时,还有什么现实手段
当数据量上亿、或分组字段基数极高(如 email、uuid),索引优化边际收益急剧下降:
- 加
ORDER BY NULL显式关闭默认排序,省掉一次文件排序开销 - 高频固定维度聚合(如按天统计订单总额),用定时任务写入预聚合表,查时直接
SELECT,不重算 - 对高基数字段降维:把
GROUP BY email改成GROUP BY SUBSTRING_INDEX(email, '@', -1)(取域名),再建索引 - 临时表参数(
tmp_table_size、max_heap_table_size)可调大缓解磁盘落表,但只是延缓问题,不是根治
最容易被忽略的是:GROUP BY 的性能损耗和输入行数呈非线性增长——扫 10 万行和扫 100 万行,CPU 和内存占用可能差一个数量级。所以 WHERE 前置不是“建议”,而是硬性前提;而覆盖索引里的字段顺序,错一位,整个优化就归零。


















