tuk*_*aef 6 performance sql-server execution-plan
我在生产环境中遇到了类似的问题,但我设法在Northwind DB上重现了这种行为。
考虑以下查询:
USE NORTHWND;
DECLARE @ids TABLE ( Id NCHAR(50) );
INSERT INTO @ids
VALUES ( N'AROUT' ),
( N'ALFKI' );
SELECT *
FROM ( SELECT c.CustomerID
FROM dbo.Customers c
WHERE c.CustomerID IN ( SELECT Id
FROM @ids )
UNION ALL
SELECT '0'
) t1
JOIN ( SELECT o.CustomerID
FROM dbo.Orders o
--LEFT JOIN dbo.[Order Details] od ON o.OrderID = od.OrderID
UNION ALL
SELECT '0'
) t2 ON t2.CustomerID = t1.CustomerID
OPTION ( RECOMPILE );
Run Code Online (Sandbox Code Playgroud)
现在,当我取消注释 line 时LEFT JOIN,计划变得不那么好(似乎查询处理器无法将CustomerID过滤器推送到数据访问运算符):

当我在FORCESEEK上添加提示时dbo.Orders,查询处理器无法生成查询计划。另一方面,当我删除其中一个时UNION ALL,查询计划变得和第一个一样好。
这是预期的行为吗?为什么查询处理器不在CustomerID连接运算符之前放置过滤器?
我认为 SQL Server 根本没有适当的优化规则来产生您在查询两侧都有Ordersa 的情况下正在寻找的查找查询。UNION ALL这样的查询计划理论上是可能的,但查询优化器无法为您的查询生成它。
为了得出这个结论,我将完整的原始查询(包括 )LEFT JOIN与查询的替代表述进行了比较,该查询的替代表述产生了低得多的估计成本(0.042与0.085)。因此,如果 SQL Server 能够为您的查询探索此计划形状,它可能会选择这种成本较低的替代方案。
原始查询计划
这是最初的计划,预计成本为0.085。
备用查询计划
此查询在语义上与原始查询等效,但估计成本为0.042,大约是原始查询成本的一半。它是通过将第二个 UNION ALL 提升到查询的顶层而形成的。这要求我们两次引用t1(客户集),但即使如此,通过允许对Orders和[Order Details]表进行搜索,也能以一半的成本产生一个计划。
替代查询
以下是您可以用来尝试此方法的完整查询:
DECLARE @ids TABLE ( Id NCHAR(50) );
INSERT INTO @ids
VALUES ( N'AROUT' ),
( N'ALFKI' );
-- Define the original t1 as a CTE
WITH customers AS (
SELECT c.CustomerID
FROM dbo.Customers c
WHERE c.CustomerID IN ( SELECT Id
FROM @ids )
UNION ALL
SELECT '0'
)
-- Join t1 to the top half of the original UNION ALL
SELECT *
FROM Customers t1
JOIN ( SELECT o.CustomerID
FROM dbo.Orders o
LEFT JOIN dbo.[Order Details] od ON o.OrderID = od.OrderID
) t2 ON t2.CustomerID = t1.CustomerID
-- And then join it again to the bottom half of the original UNION ALL
UNION ALL
SELECT *
FROM Customers t1
JOIN ( SELECT '0' AS CustomerId ) t2 ON t2.CustomerID = t1.CustomerID
OPTION ( RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
要点/注意事项
Concatenation运算符很擅长妨碍查询优化器并阻止它产生最佳计划。几个例子包括这个 Connect 问题,其中UNION ALL阻止了优化的位图过滤器以及Concatenation操作符的重复观察,特别是在嵌套的情况下,欺骗了 SQL 2012 和早期的基数估计器,并且可能由于较差的基数估计而产生次优计划。显然UNION ALL这是一个非常有用的工具,但值得注意的是它偶尔会限制查询优化器优化查询的能力。SELECT '0'。)Northwind非常小,很难得出结论,因此使用现实世界的数据集进行测试非常重要。