UNION在多结果集合并时去重,DISTINCT在单结果集内部去重;前者作用于合并后整体,后者仅作用于各自查询结果,层级与范围不同。

UNION 和 DISTINCT 的去重逻辑到底差在哪
UNION 默认就带去重,本质是 UNION DISTINCT;而 DISTINCT 是对单个查询结果做行级唯一过滤。两者作用层级不同:UNION 在「多个结果集合并时」去重,DISTINCT 在「单个结果集内部」去重。
常见误解是以为 SELECT DISTINCT * FROM A UNION SELECT DISTINCT * FROM B 比 SELECT * FROM A UNION SELECT * FROM B 更高效——其实没区别,UNION 本身就会对合并后的全部结果再跑一遍去重,前面加 DISTINCT 属于冗余计算。
真正影响性能的是字段类型和索引:如果参与 UNION 的列含 TEXT、JSON 或超长 VARCHAR,MySQL 会强制用临时磁盘表排序去重,速度骤降。
大规模数据下 UNION ALL + 外层 DISTINCT 反而更慢
有人想绕开 UNION 的默认去重开销,改用 UNION ALL 再套一层 DISTINCT,比如:
SELECT DISTINCT * FROM ( SELECT col1, col2 FROM t1 UNION ALL SELECT col1, col2 FROM t2 ) AS tmp
这在数据量大时通常更差,原因有三:
-
UNION ALL先无条件拼出全部行(可能翻倍),内存/磁盘压力更大 - 外层
DISTINCT要对整个中间结果排序或哈希,数据量越大越卡 - 优化器很难对这种嵌套结构做有效剪枝,索引利用率低
实测 500 万行 × 2 表 UNION,直接用 UNION 比套 DISTINCT 快 1.7 倍(MySQL 8.0,SSD)。
真正能提速的大规模去重合并方案
当单表超千万行,优先放弃纯 SQL 合并思路,转向分治或预处理:
- 先用
INSERT ... SELECT ... ON DUPLICATE KEY UPDATE或MERGE(SQL Server / PostgreSQL)把数据归到一张带唯一约束的临时表,依赖索引快速排重 - 对时间分区表,用
WHERE create_time > '2024-01-01'精确限定范围,避免扫全表 - 如果字段可哈希(如邮箱、手机号),建
HASH索引或用MD5(col)生成简短指纹,在应用层做布隆过滤预筛 - MySQL 8.0+ 可考虑
CTE + ROW_NUMBER() OVER (PARTITION BY ...)配合WHERE rn = 1,但需确保PARTITION BY列有索引,否则照样慢
注意:所有方案都依赖「业务允许最终一致性」——比如合并过程中新数据写入,要明确是取旧快照还是动态重算。
容易被忽略的 NULL 和类型隐式转换陷阱
UNION 要求各子查询对应列类型严格兼容,否则触发隐式转换,导致索引失效甚至去重错误:
-
INT和VARCHAR列UNION,MySQL 会把 INT 转成字符串再比对,NULL和空字符串可能被误判为相同 - 两个
DATETIME列UNION,若精度不同(如2024-01-01 12:00:00vs2024-01-01 12:00:00.000000),部分版本认为不等,部分版本截断后相等 -
UNION中NULL = NULL返回TRUE(用于去重),但WHERE col = NULL永远不成立——这个语义差异常导致调试时困惑
上线前务必用 SELECT @@sql_mode 检查是否启用了 STRICT_TRANS_TABLES,否则类型转换错误可能静默发生。

















