DISTINCT对多列生效的原理是将指定列组合视为一个元组,仅当所有列值完全相同时才去重;NULL被视为相等,且必须紧跟SELECT后、不可修饰单列或混用聚合函数。

DISTINCT 对多列生效的原理是什么
DISTINCT 作用于 SELECT 后的所有列,不是对单列分别去重。它把整行看作一个元组,只有所有指定列的值完全一致时,才视为重复行。
常见误解是以为 SELECT DISTINCT a, b FROM t 会分别对 a 和 b 去重——实际是保留所有唯一的 (a, b) 组合。
- 如果某行
a=1, b=2出现两次,只保留一次 - 如果另一行
a=1, b=3存在,它和前者不冲突,也会保留 -
NULL参与比较时,NULL = NULL在DISTINCT中被视为相等(符合 SQL 标准)
如何写多列 DISTINCT 的基本语法
直接在 SELECT 后列出需要参与去重的列即可:
SELECT DISTINCT col1, col2, col3 FROM my_table WHERE status = 'active';
注意点:
-
DISTINCT必须紧跟在SELECT后,不能放在中间或末尾 - 所有非聚合列都必须出现在
DISTINCT列表中(否则报错,如 MySQL 5.7+ 严格模式) - 不能混用
DISTINCT和聚合函数(如COUNT(*))而不加GROUP BY,除非聚合是唯一字段(如SELECT COUNT(DISTINCT a))
当需要“按多列去重,但还要取其他字段”怎么办
DISTINCT 本身不支持“去重主键,顺带取最新时间”这类需求。这时本质是「每组取一行」,需改用窗口函数或子查询:
- PostgreSQL / SQL Server / Oracle / MySQL 8.0+:用
ROW_NUMBER() - 老版本 MySQL:用自连接或相关子查询模拟
例如,要对 (user_id, product_id) 去重,并保留每组 created_at 最大的那条记录:
SELECT user_id, product_id, created_at, order_id
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id, product_id
ORDER BY created_at DESC
) AS rn
FROM orders
) t
WHERE rn = 1;关键区别:
-
DISTINCT是“任意留一行”,无控制权 -
ROW_NUMBER()+PARTITION BY是“明确选哪一行” -
GROUP BY虽然也能分组,但若要取非分组字段的特定值(如最新时间),仍需配合聚合函数(MAX(created_at))或窗口函数,否则结果不确定
性能和索引注意事项
多列 DISTINCT 在大数据量下可能很慢,因为通常需要临时排序或哈希去重:
- 数据库一般会对
DISTINCT列自动利用联合索引(如果存在) - 建议为常用去重组合建联合索引,例如:
CREATE INDEX idx_user_prod ON orders(user_id, product_id); - 避免在
DISTINCT中包含大字段(如TEXT、JSON),会显著拖慢内存/磁盘排序 - 在 PostgreSQL 中,
SELECT DISTINCT可能触发Unique计划节点;在 MySQL 中,常表现为Using temporary; Using filesort—— 这些在EXPLAIN中可见
真正难的不是写法,而是想清楚:你到底要“无序去重”还是“可控择一”。很多人卡住,是因为没意识到 DISTINCT 本身不提供行选择逻辑。

















