distinct只作用于select列表的字段组合,不能用于where或order by;去重基于整行值全匹配,null视为相等;多列去重非单列去重,性能开销大,替代方案包括group by或窗口函数。

DISTINCT 只能作用于 SELECT 列表中的字段,不能用于 WHERE 或 ORDER BY
很多人误以为 DISTINCT 是个“过滤器”,可以独立去重某列或配合条件筛选。其实它只修饰整个 SELECT 行——只要两行在 SELECT 列表中所有字段的组合值完全相同,就视为重复,仅保留一行。
常见错误是写成:SELECT DISTINCT name, age FROM users WHERE DISTINCT city = 'Beijing' —— 这会报语法错误,因为 DISTINCT 不是关键字修饰符,不能出现在 WHERE 子句里。
-
DISTINCT必须紧跟在SELECT后,且只能出现一次 - 如果
SELECT包含表达式(如UPPER(name)),去重基于表达式结果值,不是原始列 - 带
ORDER BY时,排序字段必须出现在SELECT列表中(除非使用位置编号,如ORDER BY 1),否则某些数据库(如 PostgreSQL)会报错
用 DISTINCT 去重多列时,空值(NULL)会被当作相同值处理
SQL 标准规定:两个 NULL 在 DISTINCT 比较中被视为相等。这意味着 SELECT DISTINCT col1, col2 FROM t 中,(1, NULL) 和 (1, NULL) 会被合并,但 (1, NULL) 和 (1, 'a') 不会。
这容易引发意外结果,尤其当业务上认为 NULL 表示“未知”而非“相同”。例如统计“不同用户-地区组合”时,多个未填地区的用户会被压成一条记录。
- MySQL、PostgreSQL、SQL Server 都遵循此行为;Oracle 略有差异(在
GROUP BY中NULL单独成组,但DISTINCT仍视其相等) - 若需把
NULL当作不同值,得用COALESCE(col, UUID())类似方式“打散”,但会失去语义,慎用 - 更稳妥的做法是先明确
NULL的业务含义,再决定是否预处理(如用'unknown'替换)
DISTINCT 性能开销大,大数据量下优先考虑 GROUP BY 或窗口函数
DISTINCT 本质是隐式 GROUP BY 所有选中列,数据库需对结果集排序或建哈希表去重。当 SELECT 字段多、数据量大、无对应索引时,I/O 和内存消耗明显上升。
比如 SELECT DISTINCT a, b, c, d FROM huge_table 没有覆盖索引时,可能触发全表扫描 + 外部排序。
- 如果只是校验某列是否唯一,用
SELECT COUNT(*) = COUNT(DISTINCT col) FROM t更轻量 - 需要聚合计算(如同时取每个分组的最新时间),直接写
GROUP BY col比DISTINCT+ 子查询更清晰、通常也更快 - 想保留某条“典型”重复记录(如最新的一条),
DISTINCT无能为力,得用ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
替代 DISTINCT 的常见场景:用 EXISTS 或 IN 做逻辑去重
当目标不是“返回不重复的行”,而是“判断某值是否存在”或“过滤主表中在关联表有匹配的记录”时,DISTINCT 往往是错的选择。例如查“有订单的用户”:
SELECT DISTINCT u.id, u.name FROM users u JOIN orders o ON u.id = o.user_id
不如用 EXISTS 更高效、语义更准:
SELECT u.id, u.name FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)
-
EXISTS可短路:找到第一个匹配就停,不用扫完所有订单 - 避免因一对多关联导致的“假重复”(一个用户多订单 → 返回多行 → 再去重)
- 部分数据库对
EXISTS有更好优化,执行计划更稳定
真正需要去重时,DISTINCT 简单直接;但一看到“JOIN 后去重”,就得停下来想想:是不是模型或写法本身已经引入了冗余?










