UNION ALL 是合并大数据集时必须使用的语法,因 UNION 会触发隐式 DISTINCT 和排序,导致磁盘临时表、内存溢出及性能陡降;需手动校验列数、顺序、类型,并显式 CAST,ORDER BY 和 LIMIT 必须合理下推。

UNION ALL 是合并大数据集时最不该被绕开的语法,只要不依赖去重,它就是唯一合理的选择——不是“推荐”,而是“必须”。
为什么不能用 UNION 合并大数据集
UNION 会强制触发隐式 DISTINCT + 隐式排序,MySQL 必须把全部结果写入临时表、排序、逐行比对。一旦单边结果超 5 万行,就极大概率触发磁盘临时表;超 50 万行,内存溢出风险陡增。ERROR 1222 (21S01) 还只是表象,真正卡死的是 Using temporary; Using filesort 在 EXPLAIN 里反复出现。
- UNION 实际等价于
UNION DISTINCT,MySQL 8.0+ 对 NULL 和浮点精度的去重行为更敏感,结果反而难预测 - 即使两个子查询天然无交集(比如按日期分表的
log_202601和log_202602),用 UNION 也纯属白干一次全量扫描 - 真需要去重,应在业务层控制,或外层显式套
SELECT DISTINCT,而不是让 UNION 承担这个不可控的负担
列对齐必须手动校验,别信 SELECT *
报错 ERROR 1222 是最常见的拦路虎,但更危险的是“不报错却隐式转换”:MySQL 不看字段名,只校验位置和表达式类型兼容性。
- 每个
SELECT的列数必须完全一致;SELECT *在表结构变更后必然崩,生产环境禁止使用 - 顺序必须语义对齐:若首句是
id, name, status,后续就不能写成name, id, status - 类型要显式统一:
TINYINT和DATETIME强行合并可能返回0或'0000-00-00';VARCHAR(10)和VARCHAR(255)合并时,MySQL 按 255 分配内存,后续参与GROUP BY可能导致索引失效 - 缺字段必须用
NULL或CAST(col AS CHAR)占位,例如:SELECT id, name, NULL AS city FROM t1和SELECT id, NULL AS name, city FROM t2
ORDER BY 和 LIMIT 必须写在最后,但别直接写
UNION ALL 是集合操作,不是嵌套结构。你不能在每个子句后加 ORDER BY 或 LIMIT,MySQL 会直接报语法错误。但外层加 ORDER BY 是性能杀手——它会强制全量合并后再排序。
- 要整体排序:必须写在末尾,如
UNION ALL ORDER BY created_at DESC,但大数据量下慎用 - 要取 Top N:必须先在每个子查询内
LIMIT,再合并,最后外层再LIMIT,例如:(SELECT id, name FROM order_2025_q4 ORDER BY created_at DESC LIMIT 200) UNION ALL (SELECT id, name FROM order_2026_q1 ORDER BY created_at DESC LIMIT 200) ORDER BY created_at DESC LIMIT 100
- 如果业务允许近似有序,优先用时间范围过滤代替全局排序,如
WHERE created_at > '2026-01-01',成本远低于ORDER BY
字段别名和类型隐式转换是最大陷阱
别名以第一个 SELECT 为准,后续所有 AS xxx 全部被忽略。这本身不致命,但配合隐式转换就容易埋雷:比如首句用 CAST(phone AS CHAR),第二句用 phone(TEXT 类型),MySQL 可能截断或警告,且 TEXT 列无法用于排序索引,外层 ORDER BY 必然触发 Using filesort。
- 所有文本列务必显式
CAST(col AS CHAR)或CONVERT(col, CHAR),别依赖自动转换 - 数值列注意宽度差异:
TINYINT和INT虽可兼容,但参与排序时隐式升为INT,增大比较开销 - 字段别名只影响最终输出,不影响类型对齐逻辑;别名写错不会报错,但会让后续维护者误判数据来源
真正难的不是写对语法,而是判断“哪些子查询该加 WHERE 下推”“哪些字段必须 CAST”“什么时候该放弃全局 ORDER BY 改用时间窗口”。这些决策没有银弹,得靠 EXPLAIN 看 rows 和 Extra 字段,而不是凭感觉。


















