为何SQL UNION性能极差,除非重复核心WITH子句?
兄弟,我太懂你这种踩坑的感觉了!你遇到的这个问题其实是SQL优化里常见的公共表达式(CTE)执行策略导致的,咱们一步步拆解清楚:
为什么原写法会慢到离谱?
很多主流数据库(比如PostgreSQL、MySQL 8+)对WITH子句的处理有两种核心模式:
- 一种是把CTE当成临时视图:每次引用它的时候,都会重新执行一遍CTE里的全部逻辑;
- 另一种是物化临时表:只执行一次CTE,把结果存在临时表里,后续引用直接复用这个表。
当你在UNION里两次引用同一个DATA1时,数据库大概率没选择「物化」,反而偷偷把DATA1的逻辑执行了两遍——虽然你以为是复用,但实际上相当于跑了两次耗时的主查询,叠加起来就慢成2.5分钟了。
为什么复制成DATA2就快了?
当你把DATA1的逻辑复制成DATA2后,两个CTE变成了独立的公共表达式。数据库会对它们分别做执行计划优化,要么其中一个的结果被自动复用,要么数据库判断这两个独立CTE适合物化,最终只执行了一次核心逻辑(或者两次但因为优化变得极快),所以总耗时回到了1秒。
为什么给DATA1改别名没用?
你把DATA1别名为DATA2,本质上还是引用同一个CTE,数据库会识别出这是同一个公共表达式,执行策略完全没变——别名只是换了个称呼,根本没改变数据库对它的执行判断,所以性能还是拉胯。
不用重复子句的解决办法
1. 强制物化CTE
很多数据库支持在WITH子句里加MATERIALIZED关键字,强制数据库先把CTE的结果物化到临时表里,后续引用直接复用:
WITH DATA1 AS MATERIALIZED ( -- 你的主查询逻辑 ) SELECT ... FROM DATA1 UNION SELECT ... FROM DATA1;
这样DATA1只会被执行一次,性能和复制CTE的写法一样,但不用重复写逻辑。
2. 改用临时表
如果你的数据库不支持物化CTE,可以先把DATA1的结果存入临时表,再在UNION里查询这个临时表:
CREATE TEMP TABLE DATA1 AS -- 你的主查询逻辑; SELECT ... FROM DATA1 UNION SELECT ... FROM DATA1; -- 临时表会话结束后会自动删除,不用手动清理
这种方式也能保证核心逻辑只执行一次,完全避免重复计算。
3. 检查执行计划定位问题
跑一下EXPLAIN ANALYZE(不同数据库语法可能略有不同),看看原写法里DATA1是不是被执行了两次。如果确认是重复执行,用上面两种方法强制复用结果就行。
内容的提问来源于stack exchange,提问作者Mike

