为啥使用 OR 条件而不是 Union 会导致性能问题
Posted
技术标签:
【中文标题】为啥使用 OR 条件而不是 Union 会导致性能问题【英文标题】:Why using OR condition instead of Union caused a performance Issue为什么使用 OR 条件而不是 Union 会导致性能问题 【发布时间】:2014-08-27 06:59:54 【问题描述】:您好,我在 SP 中有以下查询
@CrmContactId 是 SP 的参数。
Select distinct A.PolicyBusinessId, A.PolicyDetailId
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where (b.CRMContactId = @CRMContactId)
我们进行了新的更改并引入了 OR 条件
Select distinct A.PolicyBusinessId, A.PolicyDetailId
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where (b.CRMContactId = @CRMContactId OR C.CRMContactId = @CRMContactId)
执行计划:
附件:Execution Plan
但是这种变化在实时服务器中引起了巨大的性能问题。 TPolicyBusiness 和 TPolicyOwner 是具有数百万条记录的重型表。 TAdditionalOwner 表是一个只有很少记录的轻型表。
解决这个问题 Union all is 而不是 OR 条件。
Select distinct A.PolicyBusinessId, A.PolicyDetailId
From
(
Select A.PolicyBusinessId, A.PolicyDetailId
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
where b.CRMContactId = @CRMContactId
union all
Select A.PolicyBusinessId, A.PolicyDetailId
from TPolicyBusiness A
Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where C.CRMContactId = @CRMContactId
) as A
执行计划:
附件 Execution Plan
有人能解释一下为什么引入 OR 会导致问题,以及为什么在这种情况下使用 Union 比 OR 更好吗?
【问题讨论】:
你看执行计划了吗?它是否会改变索引的使用,例如? 如果没有看到执行计划和你的 DDL,就很难说清楚。我的猜测是,优化器在使用 OR 时会使用索引扫描,但是在分离时每个查询的基数较小意味着当使用 UNION 时,优化器会在每个查询中使用索引查找一个或两个表。 好的,谢谢@RaphaëlAlthaus 和 GarethD 我会查看执行计划并更新问题。 我已经用执行计划更新了问题 没有涵盖 TAdditionalOwner.PolicyBusinessId 的索引。 【参考方案1】:使用UNION ALL
替换OR
实际上是众所周知的优化技巧之一。最好的参考和解释在这篇文章中:Index Union。
它的要点是 OR
谓词 可以 被两个索引搜索满足,不能被查询优化器可靠地检测到(原因是不可能从两个索引中预测不相交的集合OR 的两侧)。因此,当表达与 UNION ALL 相同的条件时,优化器可以毫无问题地创建一个执行两次短搜索并将结果合并的计划。重要的是要意识到a=1 or b=2
可能与a=1 union all b=2
不同,因为第一个查询返回一次满足两个 条件的行,而后者返回两次。当您将查询编写为 UNION ALL 时,您是在告诉编译器您理解这一点并且您对此没有任何问题。
如需进一步参考,请参阅How to analyse SQL Server performance。
【讨论】:
发布实际计划,附上 XML .sqlplan 文件,而不是图片。【参考方案2】:Query1、左连接和 where 子句结合起来意味着表 C 被有效地忽略(也没有在选择列表中引用),所以你基本上有一个 2 表内连接查询。
查询 2,左连接现在几乎是一个内连接,因为在 where 子句中引用了该表,但不允许该表中的 NULL 值 - 但由于 OR,所有条件都被考虑用于结果。因此或多或少是一个 3 表内连接查询。
查询 3,您已将内部 UNION ALL 查询简化为 2 个简单的内部联接
但我怀疑还会有更多内容,因为and A.IndigoClientId=@TenantId
表示您还没有透露完整的查询。
【讨论】:
抱歉,我删除了该条件,A.IndigoClientId=@TenantId 认为这没有用。更新了我的问题。 为时已晚 :) 我知道它现在存在。基本问题是我们可以做出一些有根据的猜测,但这就是我们所能做的。您需要检查每次运行的实际执行计划才能真正了解差异。 好的 :) 我会带着执行计划回来。【参考方案3】:到 TAdditionalOwner 的 JOIN 正在使用
TPolicyBusiness.PolicyBusinessId = TAdditionalOwner.PolicyBusinessId
到 TPolicyOwner 的 JOIN 正在使用
TPolicyBusiness.PolicyDetailId = TPolicyOwner.PolicyDetailId
检查 PolicyBusinessId 是否有相应的索引。
在 2-way JOIN 中,即 UNION 的一部分,如果 TAdditionalOwner 较小的表在 TPolicyBusiness 中没有可供引用的索引,则由于较小,将对其进行优化。服务器仍将进行表扫描,但使用较小表中的值并查看它们是否在某处的大表中。如果没有索引,随着小表的增长,这种优化会很快消失。
鉴于您没有在 SELECT 中引用 B 或 C,您可以简单地引用这个
SELECT DISTINCT A.PolicyBusinessId, A.PolicyDetailId
FROM TPolicyBusiness A
LEFT JOIN TPolicyOwner B ON a.PolicyDetailId = b.PolicyDetailId AND b.CRMContactId = @CRMContactId
LEFT JOIN TAdditionalOwner C on c.PolicyBusinessId = A.PolicyBusinessId AND C.CRMContactId = @CRMContactId
这样它将加入到任一表,与您的 UNION 相同,但没有 OUTER 选择。
无论哪种方式,都要确保使用的字段被索引。
【讨论】:
【参考方案4】:联合运算符,如果数据量很大,会花费很多时间。尽量避免联合运算符。
请在下面尝试在加入时尝试过滤数据。它会好得多。
Select distinct A.PolicyBusinessId, A.PolicyDetailId
from TPolicyBusiness A
inner join TPolicyOwner B on a.PolicyDetailId=b.PolicyDetailId
and b.CRMContactId = @CRMContactId
Left Join TAdditionalOwner C on c.PolicyBusinessId=A.PolicyBusinessId
where (1=1 OR C.CRMContactId = @CRMContactId)
【讨论】:
这不太正确。如果联系人仅来自 TAdditionalOwner,则不会加入 TPolicyOwner....因此没有结果。以上是关于为啥使用 OR 条件而不是 Union 会导致性能问题的主要内容,如果未能解决你的问题,请参考以下文章
可以/为啥在返回类型中使用 char * 而不是 const char * 会导致崩溃?