大表查询中添加UNION带来的反常性能提升原因咨询
咱们先结合你的Zip表结构拆解问题:这张500万行的表,聚集主键是(Zip, Coun_Code),意味着数据在磁盘上是先按Zip排序、再按Coun_Code排序存储的。这种结构下,针对Zip前缀/精确匹配的查询效率极高,但涉及State的过滤就比较被动——因为State不在聚集索引键里,优化器没法直接通过索引定位符合条件的行。
通常出现“UNION比单查询更快”的情况,核心原因都是单查询里的复杂条件(比如OR)让优化器陷入了执行计划选择的困境,而拆成UNION后,每个子查询都能利用更高效的索引操作。举个最常见的场景来分析:
假设你的第一个查询是:
SELECT * FROM Zip WHERE Coun_Code = 'US' AND (Zip LIKE '123%' OR State = 'NY')
而第二个带UNION的查询是:
SELECT * FROM Zip WHERE Coun_Code = 'US' AND Zip LIKE '123%' UNION SELECT * FROM Zip WHERE Coun_Code = 'US' AND State = 'NY'
1. 单OR查询的性能瓶颈
优化器看到OR条件时,会评估能否用索引覆盖两个过滤规则。但你的聚集索引是(Zip, Coun_Code),State不在索引键中——这意味着优化器没法同时满足Zip LIKE '123%'和State='NY'的索引seek操作。此时优化器很可能会选择全聚集索引扫描(它误以为扫描整个表比分别处理两个条件再合并的开销更小,但实际并非如此),全表扫描500万行的IO开销是极大的。
另外还有一种可能:优化器对OR条件的基数估计错误。比如它高估了符合条件的行数,从而选择了低效的执行计划(比如哈希匹配而非合并连接),进一步拖慢了查询速度。
2. UNION拆分后的性能优势
拆成两个子查询后,每个查询的过滤逻辑都足够简单,优化器能为它们选择最优的执行计划:
- 第一个子查询
WHERE Coun_Code='US' AND Zip LIKE '123%':直接利用聚集索引seek。因为Zip是索引首列,前缀匹配可以快速定位到所有Zip以123开头且Coun_Code='US'的行,这部分操作几乎是直接定位到目标数据页,IO开销极低。 - 第二个子查询
WHERE Coun_Code='US' AND State='NY':虽然State没有索引,但优化器可以单独评估这个条件的基数——如果State='NY'的行数占比很低,它可能会选择更高效的扫描范围;即使是全扫描,单独执行时优化器也可能启用并行扫描策略,而且两个子查询的总IO通常远小于单查询的全表扫描。
最后,UNION会自动去重(如果存在重复行),但这个去重的开销通常远小于全表扫描的开销,所以整体性能反而得到了提升。
其他可能的原因
- 如果你的单查询涉及更复杂的逻辑(比如JOIN+OR),拆成UNION后可能避免了优化器的“执行计划妥协”——比如原本JOIN和OR的组合让优化器只能选择低效的嵌套循环,而拆成UNION后每个子查询的JOIN都能选择哈希匹配或合并连接。
- 统计信息过时:如果表的统计信息未及时更新,优化器对单查询的基数估计偏差极大,但拆成UNION后每个子查询的统计信息更准确,从而生成更优的执行计划。
内容的提问来源于stack exchange,提问作者ExnorMark

