UNION ALL 是默认首选,因其无去重、排序和临时表开销;而 UNION 等价于 UNION DISTINCT,需排序去重,性能差。列数、顺序、类型必须严格对齐,ORDER BY/LIMIT 仅限末尾,别名以首个 SELECT 为准,隐式转换需谨慎。

UNION ALL 是默认首选,除非你明确需要去重
只要各查询结果天然无重复(比如按日期分表的日志、主键不重叠的线上线下用户表),就该直接用 UNION ALL。它不做去重、不排序、不写临时表,就是把结果集顺序拼接,性能几乎零开销。而 UNION 实际等价于 UNION DISTINCT,MySQL 会强制把所有数据拉进临时表、排序、逐行比对——几万行就开始抖,几十万行可能触发磁盘临时表甚至内存溢出。
列数、顺序、类型必须严格对齐,否则直接报错
最常见的错误是 ERROR 1222 (21S01): The used SELECT statements have a different number of columns。MySQL 不看字段名,只校验每个 SELECT 的列数量、位置和类型兼容性:
- 列数必须完全一致:不能靠
SELECT *蒙混,表结构一变就崩 - 顺序必须语义对齐:第一个查的是
id, name, status,后面就不能写成name, id, status - 类型要兼容:比如
INT和DECIMAL可转,VARCHAR(10)和VARCHAR(100)也能拼;但硬凑TINYINT和DATETIME很可能返回0或'0000-00-00' - 缺字段就用
NULL或CAST()占位,例如:SELECT id, name, NULL AS city FROM t1和SELECT id, NULL AS name, city FROM t2
ORDER BY 和 LIMIT 只能放在最后,中间加会语法报错
UNION ALL 是集合操作,不是嵌套结构。你不能在每个子句后写 ORDER BY created_at DESC 或 LIMIT 5,MySQL 直接拒绝解析。
- 要整体排序:写在末尾,如
UNION ALL ORDER BY 1 DESC(按第一列降序) - 要限制总条数:写
UNION ALL LIMIT 100 - 如果某一边需取最新 5 条(比如日志表),必须先包成子查询:
(SELECT * FROM log_202605 ORDER BY created_at DESC LIMIT 5),再参与UNION ALL
字段别名以第一个 SELECT 为准,后续的 AS 全被忽略
最终结果集的列名完全由第一个 SELECT 决定。后面所有子句里的 AS xxx 都无效。想统一命名,只能在第一个里定义:
- 正确:
SELECT id, real_name AS name, NULL AS phone FROM users_offline UNION ALL SELECT id, name, phone FROM users_online - 错误:以为第二个
AS name能覆盖,其实没用 - 注意:别名一旦定下,后续所有引用(比如外层
ORDER BY或GROUP BY)都得按这个名来,不能用原始字段名
实际合并时最容易被忽略的点是隐式类型转换——MySQL 看似“通融”,但 TINYINT 和 VARCHAR 拼在一起可能返回空字符串或截断,NULL 和数字混合时也可能触发意外补零。真要跨类型合并,显式 CAST(col AS CHAR) 或 COALESCE(col, '') 比依赖隐式转换更可控。


















