DISTINCT仅对SELECT整行字段组合去重,非单字段独立去重;若含id等唯一字段则必然不重复,NULL被视为相同值可合并,ORDER BY字段须出现在SELECT列表中,性能开销大需索引优化。

DISTINCT 只对整行去重,不是按单个字段“去重”
很多人写 SELECT DISTINCT name, email, id FROM users 后发现结果还是密密麻麻,以为 DISTINCT 失效了。其实它完全正常——因为 id 是主键,每行都不同,整行就不可能重复。只要 SELECT 列表里有任何一个字段值不同,这行就不会被合并。
- 真正想按
name去重?那就只选name:SELECT DISTINCT name FROM users - 想按
name和email组合去重?必须两者都出现在 SELECT 中:SELECT DISTINCT name, email FROM users - 不能写
SELECT name, DISTINCT email FROM users——语法错误,DISTINCT必须紧贴SELECT后面 - NULL 值会被视为相同:如果多行的
email都是NULL,DISTINCT只保留一行
DISTINCT 和 ORDER BY 一起用时字段必须出现在 SELECT 列表中
写了 SELECT DISTINCT name FROM users ORDER BY created_at DESC,在 PostgreSQL 或 MySQL 8.0+ 上会直接报错 ERROR 1055。因为 created_at 没出现在 SELECT 结果里,数据库无法确定排序依据。
- 正确做法是把排序字段也放进 SELECT(如果业务允许):
SELECT DISTINCT name, created_at FROM users ORDER BY created_at DESC - 如果只想按
name去重、但要取每个name对应的最新记录,DISTINCT不够用,得换窗口函数:ROW_NUMBER() OVER (PARTITION BY name ORDER BY created_at DESC) - 某些数据库(如 PostgreSQL)允许
ORDER BY引用未选字段,但这不可移植,线上环境别依赖
DISTINCT 性能开销比看起来大得多
DISTINCT 不是轻量级操作。它本质是隐式哈希或排序去重,数据一过百万,内存和磁盘临时表压力就明显上升。尤其当字段含长文本、无索引、或用了函数(比如 UPPER(name))时,优化器基本放弃走索引。
- 确保去重字段上有索引,且组合顺序匹配 SELECT 字段顺序,例如
SELECT DISTINCT a, b最好有(a, b)的联合索引 - 避免在
DISTINCT前做计算:SELECT DISTINCT CONCAT(first_name, ' ', last_name)无法利用first_name或last_name索引 - 只是校验“有没有重复”,用
GROUP BY ... HAVING COUNT(*) > 1 LIMIT 1比SELECT DISTINCT更快 - 前端展示才需要去重?优先考虑应用层 dedupe,别让数据库干这事
DISTINCT 和 GROUP BY 不是简单替换关系
虽然 SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b 返回结果常一样,但语义和约束完全不同。硬替可能踩坑。
-
DISTINCT是结果集操作,不关心聚合逻辑;GROUP BY是分组操作,后续可接MAX()、COUNT()等——想统计每个部门人数,必须用GROUP BY - MySQL 5.7+ 开启
ONLY_FULL_GROUP_BY后,SELECT dept, name FROM users GROUP BY dept会报错,因为name未聚合也未出现在GROUP BY中;而DISTINCT没这限制 - PostgreSQL 支持
DISTINCT ON (a)取每组首行,但这不是标准 SQL,跨数据库迁移时会失效
真正难的从来不是写对 DISTINCT,而是判断“业务上到底要什么去重”——是单纯列值唯一?还是要保留某条具体记录?后者 DISTINCT 根本不解决,得立刻转向窗口函数或子查询。










