GROUP BY 仅在仅返回去重字段时可安全替代 DISTINCT;因有索引时 MySQL 更倾向为 GROUP BY 启用“Using index for group-by”优化,避免临时表与排序,而 DISTINCT 在旧版或嵌套查询中可能仍建临时表。

GROUP BY 替代 DISTINCT 的适用场景
只有当查询只返回去重字段、不涉及其他非分组列时,GROUP BY 才能安全替代 DISTINCT。比如 SELECT DISTINCT city FROM users 可以换成 SELECT city FROM users GROUP BY city;但 SELECT DISTINCT city, created_at FROM users 如果直接改成 GROUP BY city,就会丢失 created_at 的语义(MySQL 5.7+ 严格模式下会报错),必须明确写成 GROUP BY city, created_at 或改用窗口函数。
为什么 GROUP BY 有时更快?关键看索引和执行计划
核心不是语法本身快,而是 MySQL 在有合适索引时更愿意为 GROUP BY 启用 Using index for group-by 优化路径——即直接按 B+Tree 索引顺序扫描,边读边分组,跳过排序和临时表。
- 如果
city列上有单列索引或联合索引的最左前缀,SELECT city FROM users GROUP BY city很可能走索引扫描,Extra字段显示Using index for group-by - 而同条件下的
SELECT DISTINCT city FROM users在旧版本或嵌套子查询中,优化器可能仍选择建哈希表或临时表,EXPLAIN显示Using temporary - 验证方法:对两条语句分别执行
EXPLAIN FORMAT=TREE,重点看是否出现grouping_operation节点,以及key_len是否匹配索引长度
容易踩的坑:别让 GROUP BY 变成性能黑洞
GROUP BY 不是万能加速器,没配好反而更慢。
- 无索引字段上用
GROUP BY,MySQL 8.0 之前默认触发Using filesort,比DISTINCT的哈希去重更耗 I/O - 写了
ORDER BY却没加ORDER BY NULL,即使你不需要排序,优化器也可能保留排序逻辑 - ORM 框架(如 Django ORM 的
.values('city').distinct())可能底层翻译成GROUP BY,但字段没索引时查得极慢,你以为是语法问题,其实是索引缺失 -
GROUP BY a, b要求联合索引(a, b)才高效;只建了(a)索引,依然全表扫描
真正该盯住的指标,不是关键字,是 Handler_read_*
别只盯着 EXPLAIN 的 Extra 字段就下结论。真正暴露瓶颈的是运行时状态变量:
-
Handler_read_next > 0且接近总行数 → 真正在走索引扫描(好信号) -
Handler_read_rnd_next显著升高 → 正在大量回表或随机读磁盘(危险) -
Created_tmp_tables或Created_tmp_disk_tables持续增长 → 临时表已溢出内存,正在写磁盘
这些值比 “用 GROUP BY 还是 DISTINCT” 更真实地告诉你:去重到底卡在哪一步。


















