UNION ALL 直接拼接结果集,不去重不排序,但要求各子查询列数相同、类型兼容、字段顺序一致,否则报错或产生脏数据;必须用显式字段列表、统一类型和字符集,并将 ORDER BY/LIMIT 置于外层查询。

UNION ALL 能保留重复行,因为它不做去重、不排序、不建临时表,只是把各行直接拼接起来;但列数不对、类型错位或字段顺序混乱,会立刻报错或返回脏数据。
UNION ALL 报 ERROR 1222 是因为列数不一致
这个错误最常见,比如第一个 SELECT 返回 3 列,第二个只写 2 列,MySQL 就会直接中断执行。
- 检查每个子查询的
SELECT行是否真有相同数量的表达式,别漏掉NULL占位(例如:需要 4 列但某表只有 3 字段,就补NULL AS status) - 别依赖表结构“看起来一样”——
SELECT * FROM t1 UNION ALL SELECT * FROM t2很危险,万一 t2 多一列或少一列,就崩 - 用显式字段列表代替
*,哪怕多打几下键盘,也比查半天为什么报错强
字段类型不兼容导致数据截断或隐式转换失败
MySQL 允许部分隐式转换(如 TINYINT 和 INT),但遇到 VARCHAR(50) 和 TEXT、DATETIME 和字符串字面量硬拼时,容易出问题。
- 如果某列在第一个查询是
VARCHAR(100),第二个是TEXT,MySQL 会把整列提升为TEXT,影响内存使用和后续ORDER BY性能 - 日期字段混用:避免
created_at直接和'2025-01-01'拼接,统一转成DATE类型:CAST(created_at AS DATE) - 字符串长度差异大时(比如一边是
VARCHAR(10),另一边是VARCHAR(255)),建议显式CONVERT(col USING utf8mb4)对齐字符集
ORDER BY 和 LIMIT 必须套在外层查询里
直接在每个 SELECT 后加 ORDER BY id LIMIT 10 是语法错误(MySQL 不支持),而且就算能跑,也只对最后一个子查询生效。
- 正确写法是把整个
UNION ALL当作派生表,再包一层:SELECT * FROM ( SELECT id, name, 'old' AS source FROM users_old UNION ALL SELECT id, name, 'new' AS source FROM users_new ) AS combined ORDER BY id DESC LIMIT 100;
- 排序字段必须来自第一个子查询的列名(如上面用了
id,就不能写uid,哪怕第二个查询里叫uid) - 如果要按来源分组排序,可以在外层加
ORDER BY source, id,但注意source是别名,得确保它出现在所有子查询中
别为了快盲目换 UNION ALL,先确认业务是否允许重复
性能差距确实明显——两个 50 万行结果集,UNION 可能卡 1.2 秒,UNION ALL 常在 60ms 内返回。但代价是:重复行不会被合并。
- 如果两个子查询逻辑上本应无交集(比如按时间分区的订单表),那
UNION ALL安全又高效 - 但如果来源有重叠(比如用户表 + 临时导入表),且下游代码假设“每行 ID 唯一”,那就可能引发空指针或计数翻倍等隐藏 bug
- 真正难察觉的是字段对齐细节:一个子查询用
ROUND(price, 2),另一个用price原值,看着都是数字,但浮点精度差异会让UNION认为它们不同,而UNION ALL照单全收——这种重复不是数据问题,是语义失准


















