为什么使用变量导致查询永远不会完成?

时间:2015-06-02 16:47:20

标签: sql-server

我有一个大约1000万bigints偏移的表,它比已知的常数大。

我想知道有多少数字在常数的范围内。 (实际的查询并不重要)。当我使用常量转换为bigint时,性能是可以接受的(1秒)。当我将常量存储在变量或参数中时,查询永远不会完成。 以下是生成示例表的脚本:

IF OBJECT_ID('bigints', 'u') IS NOT NULL
  DROP TABLE bigints;

WITH t
     AS (SELECT *
         FROM  (VALUES(1),(1),(1),
                      (1),(1),(1),
                      (1),(1),(1),
                      (1))f(n))
SELECT num = CONVERT(BIGINT, 123456789012)
             + ABS(BINARY_CHECKSUM(NEWID()))
INTO   bigints
FROM   t a,
       t b,
       t c,
       t d,
       t e,
       t f,
       t g 

以下是失败查询的示例:

DECLARE @const BIGINT = CONVERT(BIGINT, 123456789012);

WITH t
     AS (SELECT *
         FROM  (VALUES(1),(1),(1),
                      (1),(1),(1),
                      (1),(1),(1),
                      (1))f(n)),
     tally(n)
     AS (SELECT @const
                + row_number() OVER (ORDER BY (SELECT NULL))
         FROM   t a,
                t b,
                t c,
                t d)
SELECT COUNT(*)
FROM   Tally t
       JOIN bigints b
         ON t.n = b.num 

如果您使用表达式@const + row_number()替换CONVERT(BIGINT, 123456789012) + row_number(),则查询将完成。为什么引入变量会导致查询永远运行?

这是在Sql Server 2012和Sql Server 2014上完成的,都会导致同样的问题。

1 个答案:

答案 0 :(得分:3)

快速计划使用位图过滤器将{10,000}行放入bigints的散列连接中。

慢速计划获得10,000行进入嵌套循环连接到带有中间表假脱机的10,000,000表。

针对该假脱机的10,000次执行将会很慢。

看起来查询优化器不会给自己任何选择,只能使用嵌套循环。

尝试使用INNER HASH JOIN提示强制解决问题

  

由于提示,查询处理器无法生成查询计划   在此查询中定义。重新提交查询而不指定任何提示   并且不使用SET FORCEPLAN。

事实上,通过在下面添加对变量的引用(也失败),可以在更简单的查询中看到这个问题。

DECLARE @S VARCHAR(1) = '';

SELECT *
FROM   master..spt_values T1
       INNER HASH JOIN master..spt_values T2
                    ON T1.name = (T2.name + @S);

这个对变量的额外引用当然不应该阻止对具有等式谓词的查询进行散列(或合并)连接,这是一个固定的错误(相关知识库文章 - Performance issues occur when the join predicate in your query has outer reference columns in SQL Server 2005 or in SQL Server 2008

  

症状

     

您在Microsoft SQL Server 2005或Microsoft中运行查询   SQL Server 2008和查询中的连接谓词具有外部   参考栏目。在这种情况下,您可能会遇到性能问题   问题,并且查询无法完成。

     

注意Microsoft SQL Server 2000中不会发生此问题。

     

原因

     

出现“症状”部分中描述的问题   因为数据库引擎无法生成合并连接或哈希   加入。因此,使用循环连接。使用循环连接会导致   性能问题。

但是,您必须在修补程序链接(SQL Server 2005 SP3 CU7,2008 RTM CU9,2008 SP1 CU6)中描述的其中一个版本(或更高版本)上运行,并且必须在启用跟踪标志4199的情况下运行才能利用修复程序(甚至包括SQL Server 2014)。

其他不太令人满意的工作是2008年CU5 + parameter embedding optimization使用OPTION (RECOMPILE)解决问题。

以下将在每个调用处理变量时重新编译,就好像它是一个常量,并且编译成功完成所需的更快计划。

DECLARE @const BIGINT = CONVERT(BIGINT, 123456789012);

WITH t
     AS (SELECT *
         FROM  (VALUES(1),(1),(1),
                      (1),(1),(1),
                      (1),(1),(1),
                      (1))f(n)),
 tally(n)
     AS (SELECT @const
                + row_number() OVER (ORDER BY (SELECT NULL))
         FROM   t a,
                t b,
                t c,
                t d)
SELECT COUNT(*)
FROM   Tally t
       INNER HASH JOIN bigints b
                    ON t.n = b.num
OPTION (RECOMPILE);

甚至以下情况都会使问题失败,并允许主联接以一些额外的计划复杂性为代价来使用哈希或合并。

DECLARE @S VARCHAR(1) = '';

SELECT *
FROM   master..spt_values T1
       INNER JOIN master..spt_values T2
                    ON T1.name = (T2.name + (SELECT MAX(@S)));