union 隐式触发 distinct + order by,强制全字段排序且不可禁用,导致 using temporary 和 using filesort;视图中子查询 order by 无效,仅外层生效;null 视为相等,去重与排序行为因库而异。

UNION 会隐式触发 DISTINCT + ORDER BY
UNION 不是“先拼再删重”,而是数据库引擎直接按 DISTINCT + ORDER BY 的语义执行。哪怕你没写 ORDER BY,它也会为去重强制排序——因为大多数数据库(MySQL、PostgreSQL、SQL Server)实现去重的底层方式就是先排序后相邻比较。这意味着:结果集必然经历一次全字段排序,且该排序不可跳过、不可禁用。
EXPLAIN 里总能看到 Using temporary 和 Using filesort
执行 EXPLAIN 查看计划时,只要用了 UNION,几乎一定会看到这两项标记:
-
Using temporary:说明引擎必须把所有子查询结果暂存到临时表,才能做后续去重 -
Using filesort:说明排序已脱离内存,开始走磁盘文件排序,I/O 开销陡增
而 UNION ALL 的执行计划干净得多,通常只有 ALL 或 index 扫描,没有临时表和文件排序节点。
视图定义里加 ORDER BY 是无效的
有人试图在视图中每个子查询里写 ORDER BY id 来“控制顺序”,但这是徒劳的——SQL 标准规定,子查询中的 ORDER BY 在 UNION / UNION ALL 上下文中被忽略(除非配合 LIMIT 或窗口函数)。真正生效的排序只有一处:整个 UNION 结构最外层的 ORDER BY。而这个外层排序,又会和 UNION 自带的隐式排序叠加,导致重复排序,进一步拖慢性能。
NULL 值会让排序和去重更不可控
UNION 把所有 NULL 视为相等,所以在去重时,含 NULL 的行可能被任意保留一条,且排序时 NULL 的位置因数据库而异(MySQL 默认排最前,PostgreSQL 默认排最后)。如果你的视图字段有可空列,又依赖顺序或唯一性,这种不确定性会直接传导到业务逻辑里,比性能问题更难排查。
真正麻烦的不是“多一次排序”,而是它绑定了去重、临时表、磁盘 I/O 和 NULL 处理这四件事——只要用 UNION,这四个环节就一个都逃不掉。










