MySQL 5.7+ 会自动合并或删减冗余的 DISTINCT 和 GROUP BY,当语义重叠且无聚合函数时,优化器静默丢弃 DISTINCT;若 SELECT 含非 GROUP BY 列则直接报错;EXPLAIN 不显 DISTINCT 是因其被下推为 GROUP BY 等价处理。

GROUP BY 和 DISTINCT 一起写,MySQL 会直接忽略其中一个 —— 不是“失效”,而是优化器主动合并或删减。
MySQL 5.7+ 会自动消除冗余的 DISTINCT 或 GROUP BY
当你写 SELECT DISTINCT ... GROUP BY ...,MySQL 查询优化器发现两者语义重叠(比如都只涉及单列、无聚合函数、无排序需求),就会选择保留一个、删掉另一个。执行计划里看不到 DISTINCT 的痕迹,EXPLAIN 显示的 Extra 字段也通常只有 Using temporary; Using filesort 或空,不会同时标出两者。
- 如果
GROUP BY字段完全覆盖SELECT列,且没用MIN/MAX/COUNT等聚合函数,DISTINCT被静默丢弃 - 如果
SELECT中有非GROUP BY列(如SELECT id, name GROUP BY name),MySQL 5.7+ 严格模式直接报错:Expression #1 of SELECT list is not in GROUP BY clause,根本走不到“是否合并”的阶段 - 加了
ORDER BY会让行为更不可控:优化器可能保留GROUP BY做分组,再额外触发filesort,而DISTINCT彻底不生效
为什么 EXPLAIN 看不到 DISTINCT 的执行痕迹
因为 DISTINCT 在 MySQL 内部不是独立算子,它被下推为 GROUP BY 的一种等价形式处理。只要满足“仅去重、无聚合、字段一致”,优化器就统一走松散索引扫描(Loose Index Scan)路径,不会额外建临时表去 dedup。
- 验证方式:用
EXPLAIN FORMAT=JSON查看query_block下的grouping_operation字段,能看到"distinct": true仅在真正需要额外去重时出现(例如多表 JOIN 后的外层去重) - 索引存在时,
SELECT DISTINCT a FROM t和SELECT a FROM t GROUP BY a的key_len、rows完全一致 - 一旦
SELECT加了created_at这类非分组列,哪怕你写了DISTINCT,优化器也无法用索引跳扫,只能回表 + 文件排序,此时“去重”实际靠的是Using temporary的哈希去重逻辑
嵌套查询中 DISTINCT 放在外层根本救不了 GROUP BY 的重复
常见错误写法:SELECT DISTINCT * FROM (SELECT a, b FROM t1 JOIN t2 ON ...) t。如果内层 JOIN 已产生笛卡尔积,外层 DISTINCT 是最后一步,但代价极高 —— 所有中间结果都已生成并暂存,内存/磁盘压力大,且无法利用索引加速。
- 正确做法是把去重逻辑下沉:用
GROUP BY控制维度(如按用户 ID 分组),配合MAX(created_at)或ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)提前截断 - 若必须用
DISTINCT,确保内层只查必要字段,且这些字段组合本身就能唯一标识业务意义(比如SELECT DISTINCT user_id, order_status,而不是带order_id) - 注意
NULL值:多个NULL在GROUP BY中被视为同一组,但在某些 JOIN 场景下,LEFT JOIN产生的NULL可能导致意外分组,此时DISTINCT也跟着“看起来失效”
真正容易被忽略的点是:优化器是否启用 loose index scan,取决于 GROUP BY 字段顺序是否和索引定义顺序严格一致。哪怕索引是 (a,b,c),你写 GROUP BY b,a,优化器就放弃索引跳扫,转而用临时表 —— 这时候无论写不写 DISTINCT,性能都一样差。










