UNION ALL必须满足三约束:列数完全相同、对应列类型兼容、列顺序对齐;不满足则报错;它跳过去重流程,性能显著优于UNION;ORDER BY和LIMIT仅允许出现在最后;字段名和类型以第一个SELECT为准。

UNION ALL 必须满足的结构约束
用 UNION ALL 不是简单把两个 SELECT 拼在一起就行,MySQL 会严格校验结构一致性。一旦不满足,直接报错:ERROR 1222 (21000): The used SELECT statements have a different number of columns 或类型隐式转换失败。
必须同时满足三点:
- 每个
SELECT返回的列数完全相同 - 对应位置的列数据类型兼容(如
INT和BIGINT可以,INT和VARCHAR通常不行) - 列顺序要对齐——结果集的字段名、类型、含义都以第一个
SELECT为准
比如你写 SELECT id, name FROM users UNION ALL SELECT name, id FROM admins,虽然列名一样,但顺序错位,MySQL 会把 name 当作 id 处理,导致值错乱或类型报错。
UNION ALL vs UNION:性能差在哪
UNION ALL 比 UNION 快,不是“稍微快一点”,而是少了一整套去重流程:它跳过排序、哈希构建、重复行扫描。在百万级结果合并时,耗时可能差 3–10 倍。
但代价是:它不做任何判断,原样拼接。所以你要自己确认“是否允许重复”。常见误用场景:
- 用
UNION ALL合并两个按时间分表的订单数据(order_202401+order_202402),没问题——分表天然无交集 - 用
UNION ALL合并同一张表里用不同索引条件查出的数据(比如WHERE status=1和WHERE create_time > '2024-01-01'),要小心——这两部分可能有重叠行
如果不确定是否有重复,又想保留 UNION ALL 的性能,可以外包一层:SELECT DISTINCT * FROM ( ... UNION ALL ... ) AS t,比直接用 UNION 更可控。
ORDER BY 和 LIMIT 只能用在最后
UNION ALL 是一个整体操作,中间每个子查询不能带 ORDER BY 或 LIMIT(除非包在括号里当派生表)。否则 MySQL 报错:ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' 类似提示。
正确写法只有一种:
SELECT id, name FROM t1 UNION ALL SELECT id, name FROM t2 ORDER BY id DESC LIMIT 10;
注意:ORDER BY 的字段必须出现在第一个 SELECT 的字段列表中(因为最终列名以此为准),且不能写别名之外的字段名。如果第一个 SELECT 是 SELECT user_id AS id,那 ORDER BY 就得写 ORDER BY id,不能写 ORDER BY user_id。
补默认值和兜底空结果的实用写法
业务接口常要求“不管有没有数据,都返回一行”。这时 UNION ALL 配合 NOT EXISTS 是最轻量的解法,不用改应用层逻辑。
例如查用户 ID=100 的昨日消费总额,若没数据就返回 0:
SELECT '2026-10-12' AS date, SUM(amount) AS total FROM order_info WHERE user_id = 100 AND DATE(create_time) = '2026-10-12' UNION ALL SELECT '2026-10-12', 0 FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM order_info WHERE user_id = 100 AND DATE(create_time) = '2026-10-12' );
关键点:
- 第二条
SELECT必须和第一条字段数、类型一致(这里都是VARCHAR+NUMBER) -
FROM DUAL是 MySQL 中的伪表,确保WHERE子句可执行 - 不能省略
WHERE NOT EXISTS,否则会固定多出一行 0,破坏语义
这个模式在报表 SQL、定时任务统计、API 数据兜底里非常高频,但容易被忽略的是:所有字段类型必须显式对齐,比如第一条用了 SUM()(返回 DECIMAL),第二条就不能写 0 而要写 0.0,否则 MySQL 可能因类型隐式转换失败而报错。


















