MySQL 8.0 前 GROUP BY 默认隐式排序,不加 ORDER BY NULL 会触发 filesort;8.0 起取消该行为,仅当索引支持时才自然有序,显式排序需写 ORDER BY。

MySQL 8.0 前 GROUP BY 默认触发 filesort
没加 ORDER BY NULL 时,GROUP BY 在 MySQL 8.0 之前会隐式排序,哪怕你只想要去重。执行计划里出现 Using filesort 就是典型信号——它在内存或磁盘上额外做了一次全量排序,而 DISTINCT 多数走哈希判重,不强制排序。
常见错误现象:SELECT a FROM t GROUP BY a 比 SELECT DISTINCT a FROM t 慢好几倍,尤其数据量超百万行时;EXPLAIN 显示 Extra 字段含 Using filesort,但 type 是 ALL 或 index。
- 修复方法:显式加
ORDER BY NULL,如SELECT a FROM t GROUP BY a ORDER BY NULL - 注意:优化器不一定听你的,某些嵌套或带 WHERE 的场景仍可能忽略
ORDER BY NULL - 验证是否生效:看
EXPLAIN的Extra是否消失Using filesort
有覆盖索引时 GROUP BY 更容易跳过排序
当查询字段完全被联合索引覆盖(比如 INDEX(user_id, status)),SELECT user_id, status FROM t GROUP BY user_id, status 可直接用索引顺序输出,EXTRA 显示 Using index for group-by;而 SELECT DISTINCT user_id, status FROM t 在旧版本或子查询中未必识别该路径,仍可能回表或建临时表。
使用场景:分组键固定、查询列少、能提前设计索引的报表类 SQL。
- 关键检查点:
key_len必须等于联合索引总长度,且rows接近实际分组数,不是表总行数 - 别只信
type: index——它可能只是索引全扫描,而非高效索引跳扫 -
HANDLER_READ_NEXT突增说明真在走索引;HANDLER_READ_RND_NEXT高则大概率在随机回表
DISTINCT 是整行去重,GROUP BY 是按分组键去重
DISTINCT 判定重复的依据是 SELECT 出来的**所有列值完全一致**;GROUP BY 的“去重效果”来自分组行为本身,它只保证分组键唯一,非分组字段值不可控(除非用聚合函数包裹)。
常见误用:SELECT DISTINCT id, amount FROM orders 和 SELECT id, amount FROM orders GROUP BY id 看似等价,实则危险——后者在 only_full_group_by 关闭时会从每组随机取一条 amount,金额可能错乱。
- 想按
id去重但取最新amount?必须用MAX(created_at)+ 子查询,或窗口函数 -
COUNT(DISTINCT x)合法;SELECT DISTINCT COUNT(x)是语法错误 - 多列去重时,单列索引无效:
DISTINCT a, b要求联合索引(a,b),缺一不可
混用 DISTINCT 和 GROUP BY 会直接报错或性能断崖
SQL 标准禁止同一 SELECT 中同时出现 DISTINCT 和 GROUP BY,MySQL/PostgreSQL/SQL Server 都会报类似 DISTINCT and GROUP BY are mutually exclusive 的错误。强行绕过(如低版本 + 非严格模式)会导致优化器退化:全表扫描 + 多次临时表构建 + 随机排序,26 万行查 18 秒很常见。
真正卡住性能的,往往不是关键字选哪个,而是你没看 HANDLER_READ_RND_NEXT 这类状态变量——它们暴露了是否真的在走索引,还是在磁盘上一页页翻。
- 正确解法:用子查询预过滤分组键,例如先
SELECT DISTINCT a, b FROM t WHERE cond,再JOIN原表聚合 - 分页必须下推到子查询里,否则外层仍要处理全部分组
-
COUNT(DISTINCT)在 GROUP BY 中高离散度时易爆内存,优先物化中间结果


















