UNION ALL比UNION快得多,因其跳过去重和隐式排序,不建临时表、不触发Using temporary和Using filesort;UNION等价于UNION ALL加DISTINCT与ORDER BY,需全量结果入内存或磁盘临时表再排序去重,100万行时耗时可达UNION ALL的11倍以上。

UNION ALL 为什么比 UNION 快这么多
因为 UNION 实际执行的是 UNION ALL + DISTINCT + 隐式 ORDER BY,它必须把所有子查询结果先写进临时表,再全量排序去重。一旦数据量超过内存限制(tmp_table_size 或 max_heap_table_size),就会刷到磁盘,触发 I/O 瓶颈。而 UNION ALL 是纯流式拼接:子查询 A 输出一行,立刻发给客户端;A 结束后无缝接 B,中间不建表、不排序、不判重。
- 100 万行数据下,
UNION耗时通常是UNION ALL的 11 倍以上——这不是配置问题,是执行引擎的硬性机制 -
EXPLAIN中看到Using temporary和Using filesort就是UNION在干活的铁证 - 即使你显式写了
ORDER BY,UNION仍可能排序两次:一次为去重,一次为你写的
哪些场景可以安全换用 UNION ALL
关键不是“能不能”,而是“要不要去重”。很多团队用 UNION 只是因为“怕重复”,但实际数据天然不重。
- 按时间分表的日志:例如
access_log_2023和access_log_2024,主键/时间戳不可能交叉重复 - 按 ID 段或哈希拆分的用户表:
users_shard_1和users_shard_2,ID 范围互斥 - 业务逻辑隔离的数据源:比如
online_orders和offline_orders,订单号生成规则不同,无重叠可能 - CTE 中做中间聚合前的原始拼接,后续会用
GROUP BY或DISTINCT统一处理
替换时必须检查的三个硬性条件
语法上看似能换,但 MySQL 会直接报错或静默出错,尤其在类型隐式转换边界上。
- 列数必须完全一致:子查询 A 返回 3 列,B 也必须是 3 列,少一个就报
ERROR 1222 (21000): The used SELECT statements have a different number of columns - 对应列类型要兼容:比如
INT和TINYINT可以,但VARCHAR(50)和TEXT在某些 MySQL 版本会因长度推导失败而报错 - 列顺序不能错:即使别名一样,
SELECT name, id FROM t1和SELECT id, name FROM t2会按位置匹配,导致字段错位
ORDER BY 写在哪才真正生效
这是最常踩的坑:UNION ALL 子查询里的 ORDER BY 全部被忽略,除非加括号 + LIMIT,否则语法都不合法。
- 错误写法:
SELECT id FROM t1 ORDER BY create_time DESC UNION ALL SELECT id FROM t2 ORDER BY create_time DESC→ 第二个ORDER BY直接失效 - 正确写法只有一种:
SELECT id FROM t1 UNION ALL SELECT id FROM t2 ORDER BY id(注意:这个外层ORDER BY会让UNION ALL也走排序,性能优势打折扣) - 更优策略:在每个子查询里靠索引保证局部有序,比如
SELECT id FROM t1 ORDER BY id LIMIT 1000,再靠外层最小化排序范围
真正难的不是语法替换,而是确认业务语义是否允许重复。很多线上慢查,换掉 UNION 后 QPS 翻倍,不是因为调优多高明,只是终于没再让数据库干“明知不会重复却还要拼命去重”的傻事。


















