union 强制去重并隐式按第一列升序排序,执行流程为先拼接再筛重;union all 仅拼接、无去重排序、性能更优。

UNION 会强制去重 + 隐式排序,不是“智能 dedup”
执行 SELECT a FROM t1 UNION SELECT a FROM t2 时,MySQL 并不会逐行哈希比对或跳过已见值。它实际走的是「先拼再筛」流程:先用 UNION ALL 拼出全量中间集,再建临时表、按第一列升序排序(Using filesort),最后逐行比较字段值(含 NULL、类型、collation)判定是否重复。这意味着:
- 即使子查询里写了
ORDER BY created_at,UNION 也会忽略——外层不加ORDER BY就按第一列排,加了才可控 - 字段类型隐式转换可能引发截断,比如
VARCHAR(10)和VARCHAR(50)合并时,按短的长度截,导致本该不同的值被当成相同 -
EXPLAIN出现Using temporary; Using filesort就是它正在干重活,10 万行以上延迟常飙升数倍
UNION ALL 只拼接,快但结果完全取决于源头
UNION ALL 不检查重复、不排序、不建临时表,I/O 和 CPU 开销极低。TPC-H 实测 100 万行下耗时约 810ms,而 UNION 达 9200ms。但它要求你对数据语义有明确判断:
- 分表场景(如
log_202401+log_202402)天然无重 → 必选UNION ALL - 后续要
GROUP BY或JOIN→ 优先UNION ALL,把去重逻辑留在聚合阶段,避免 UNION 中间排序干扰索引使用 - 字段数量、顺序、类型必须严格一致,否则直接报错
ERROR 1222 (21000): The used SELECT statements have a different number of columns
字段不匹配是硬错误,不是警告
常见写法错误不是语法松散导致的“结果不准”,而是直接中断执行:
-
SELECT id, name FROM t1 UNION SELECT id FROM t2→ 列数不等,报错,不会自动补NULL -
(SELECT a FROM t1) UNIO(手误少字母)→ 报错,错误信息可能指向下一行,实际根源在括号或拼写 - 别名写在子查询里(如
SELECT id AS uid FROM t1 UNION SELECT id AS uid FROM t2)没问题;但若只在第一个子句写别名,第二个没写,不影响,最终列名以第一个为准
去重逻辑依赖字段值全等,不是业务语义
UNION 的「重复」判断非常机械:所有对应字段值必须严格相等(NULL = NULL 算相等,但 'a ' 和 'a' 在 utf8mb4_general_ci 下可能判等,在 utf8mb4_bin 下不等)。这意味着:
- 手机号字段带空格或大小写混用,可能被当成不同记录漏去重
- 时间字段精度不一致(如
DATETIMEvsTIMESTAMP)可能导致本该合并的行保留两份 - 如果你需要按业务规则去重(比如取最新一条),UNION 无法做到——得用
ROW_NUMBER()或子查询先行过滤
Using temporary?答案清楚了,选型就落地了。











