我有一个查询,该查询在SQL Server 2012中运行800毫秒,在SQL Server 2014中运行约170秒。我认为我已将其范围缩小到Row Count Spool
运营商的基数估计不佳。我已经读过一些关于假脱机操作符的信息(例如,here和here),但是仍然难以理解以下几点:
- 为什么此查询需要
Row Count Spool
运算符?我认为正确性不是必需的,那么它试图提供什么特定的优化? - 为什么SQL Server估计联接到
Row Count Spool
运算符会删除所有行? - 这是SQL Server 2014中的错误吗?如果是这样,我将提交Connect。但是我想先加深了解。
注意:LEFT JOIN
为了在SQL Server 2012和SQL Server 2014中都能达到可接受的性能,我可以将查询重新编写为或向表中添加索引。因此,此问题更多地是关于深入了解此特定查询和计划的,而较少涉及如何用不同的措词查询。
慢查询
有关完整的测试脚本,请参见此Pastebin。这是我正在查看的特定测试查询:
-- Prune any existing customers from the set of potential new customers
-- This query is much slower than expected in SQL Server 2014
SELECT *
FROM #potentialNewCustomers -- 10K rows
WHERE cust_nbr NOT IN (
SELECT cust_nbr
FROM #existingCustomers -- 1MM rows
)
SQL Server 2014:估计的查询计划
SQL Server相信Left Anti Semi Join
到Row Count Spool
会过滤掉10,000行到1行。因此,它LOOP JOIN
为的后续连接选择一个#existingCustomers
。
SQL Server 2014:实际的查询计划
如预期的那样(除SQL Server以外的每个人都!),Row Count Spool
没有删除任何行。因此,当SQL Server预期仅循环一次时,我们将循环10,000次。
SQL Server 2012:估计的查询计划
使用SQL Server 2012(或OPTION (QUERYTRACEON 9481)
SQL Server 2014)时,Row Count Spool
不会减少估计的行数,而是选择了哈希联接,因此可以制定更好的计划。
LEFT JOIN重新编写
供参考,这是一种我可以重新编写查询以在所有SQL Server 2012、2014和2016中获得良好性能的方法。但是,我仍然对上面查询的特定行为以及是否对该查询感兴趣。是新的SQL Server 2014基数估计器中的错误。
-- Re-writing with LEFT JOIN yields much better performance in 2012/2014/2016
SELECT n.*
FROM #potentialNewCustomers n
LEFT JOIN (SELECT 1 AS test, cust_nbr FROM #existingCustomers) c
ON c.cust_nbr = n.cust_nbr
WHERE c.test IS NULL