DISTINCT 作用于整行而非单字段,对所有SELECT列组合去重;需注意ORDER BY限制、NULL处理差异、性能开销大,复杂场景应优先用GROUP BY或窗口函数替代。

DISTINCT 会作用于所有 SELECT 列,不是只去重某一个字段
很多人以为 DISTINCT 是针对某个字段去重的,比如写 SELECT DISTINCT name, age FROM users,就以为只让 name 不重复。其实不是——DISTINCT 是对整行结果去重,只有当 name 和 age 的组合完全相同时,才会被合并成一条。
常见错误现象:查出来行数比预期多,或者明明 name 有重复却没被去掉。
- 如果只想看不重复的
name,得写成SELECT DISTINCT name FROM users - 如果还要附带其他字段(比如对应最新一条记录的
id),DISTINCT就无能为力了,得换用GROUP BY或窗口函数 - 在含
NULL的列上使用DISTINCT,多数数据库(如 PostgreSQL、SQL Server)会把多个NULL视为相同值;但 MySQL 在某些模式下行为略有差异,需实测确认
ORDER BY 必须引用 SELECT 中出现的列或表达式
加了 DISTINCT 后,ORDER BY 不能随便写。例如 SELECT DISTINCT name FROM users ORDER BY created_at 会报错,因为 created_at 没出现在 SELECT 列表里。
使用场景:你常想按时间排序去重后的名称列表,但又不想暴露时间字段本身。
- 正确写法是
SELECT DISTINCT name FROM users ORDER BY MAX(created_at) DESC(配合GROUP BY才合法,纯DISTINCT仍不支持聚合函数) - 更稳妥的做法是改用
GROUP BY name ORDER BY MAX(created_at) DESC,语义清晰且兼容性好 - PostgreSQL 允许
ORDER BY引用未选中的列(只要该列在GROUP BY中),但标准 SQL 不保证,别依赖
DISTINCT 性能开销不小,尤其大数据量时
DISTINCT 底层通常要排序或哈希去重,数据量一上来,CPU 和内存压力明显增加。不是语法错了才慢,是设计上就重。
容易踩的坑:在分页查询(如 LIMIT 10 OFFSET 100)前加 DISTINCT,数据库得先把全量去重结果算出来,再截取,效率极低。
- 先确认是否真需要全局去重——有时前端或应用层做一次简单去重更轻量
- 确保去重字段上有索引,尤其是多列
DISTINCT时,联合索引顺序要匹配SELECT字段顺序 - 替代方案:用
EXISTS或ROW_NUMBER()窗口函数实现“每组取一条”,往往比DISTINCT更可控、更易加条件
MySQL 中 DISTINCT 和 GROUP BY 在语义和优化器处理上越来越趋同
MySQL 8.0+ 对 SELECT DISTINCT 和 SELECT ... GROUP BY(无聚合函数)做了等价重写,执行计划常常一样。但别因此混用——语义不同,可读性和维护性差。
比如 SELECT DISTINCT a, b FROM t 和 SELECT a, b FROM t GROUP BY a, b 结果一致,但后者明确表达了“按 a 和 b 分组”,后续加 MAX(c) 就很自然;前者一旦要加聚合,就得重构成 GROUP BY。
- 团队协作中,优先用
GROUP BY表达分组意图,哪怕暂时没聚合函数 - 注意 MySQL 5.7 及以前版本对
ONLY_FULL_GROUP_BY模式的限制,DISTINCT不受此影响,但掩盖了潜在逻辑问题 - 某些 ORM(如 Django ORM)生成的
DISTINCT查询,在复杂 join 场景下可能意外放大结果集,比手写GROUP BY更难调试
实际去重逻辑越复杂,越早放弃 DISTINCT 这个“快捷键”越好。它适合一眼看清、单表、字段少的场景;稍一绕,就得靠 GROUP BY、窗口函数或子查询来锚定你要的那一条。











