UNION ALL 性能远高于 UNION,因其不执行去重和隐式排序,避免临时表与文件排序;UNION 等价于 UNION ALL 后加 DISTINCT,触发 Using temporary 和 Using filesort,导致 I/O 与 CPU 开销剧增,且结果行数和顺序不可控。
MySQL 执行 UNION 时,会隐式做两件事:先合并所有子查询结果,再对整个结果集去重(等价于加了 DISTINCT),这通常意味着排序或哈希扫描;而 UNION ALL 就是把各分支结果直接拼起来,不碰数据内容。实测中,两个各返回 50 万行的子查询,UNION ALL 可能在 200ms 内流式返回,UNION 却卡在 Using temporary; Using filesort 上,耗时数秒甚至 OOM。
常见错误现象包括:
Using temporary 或 Using filesort
tmp_table_size / max_heap_table_size)被频繁打满只要任意两行在所有列上完全相等,UNION 就会干掉一个——哪怕它们来自不同业务含义的表(比如「正式员工」和「外包人员」都叫张三、部门相同),也会被当成重复行过滤掉。这不是 bug,是设计行为。
更隐蔽的是顺序问题:UNION 去重过程通常伴随隐式排序(尤其 MySQL 8.0 以前),导致结果顺序不可控;而 UNION ALL 至少能保持各子查询的原始输出顺序(除非你显式加 ORDER BY)。
使用场景举例:
log_20260501, log_20260502…)→ 不需要去重,用 UNION ALL
UNION ALL
UNION ALL 加默认分类)→ 明确要保留所有行UNION 和 UNION ALL 都不是“智能拼接”,它们只认位置,不认字段名。下面这些写法都会报错:
SELECT name, id FROM t1 UNION SELECT id, name FROM t2 → 列顺序错,第一列是 t1.name 和 t2.id 拼的,语义混乱SELECT created_at FROM orders UNION SELECT order_time FROM history → 类型不兼容(比如 DATETIME vs TIMESTAMP 在某些版本会失败)SELECT x FROM a ORDER BY x LIMIT 10 UNION SELECT y FROM b ORDER BY y LIMIT 10 → 语法非法,MySQL 直接抛 ERROR 1221
正确做法:
CAST() 或 CONVERT(... USING utf8mb4) 显式转类型SELECT id AS uid, name AS fullname FROM ...
ORDER BY 只能放在最后,且只能引用列名或位置序号:... UNION ALL ... ORDER BY fullname
(SELECT ... ORDER BY x LIMIT 10) UNION ALL (SELECT ... ORDER BY y LIMIT 10)
只有当以下**全部满足**时,才考虑 UNION:
WHERE 或 JOIN 提前排重如果只是“怕有重复所以保险起见”,那反而容易埋坑——比如某天上游数据逻辑变更,导致本不该去重的行被意外合并,问题很难回溯。更稳妥的做法是:先用 UNION ALL 查出全量,再用 SELECT DISTINCT 包一层(虽然性能略差,但语义清晰、可调试)。
最容易被忽略的一点:即使两个表结构一模一样,UNION 也会把 NULL 和 NULL 当作相等去重——而很多业务场景里,NULL 的语义是“未知”,不是“相同”。这点在统计类查询中极易引发偏差。