2016-07-25 36 views
2

我正在一个大约1000万行的大表上进行搜索。我想指定开始日期和结束日期,并返回在这些日期之间创建的表中的所有记录。在DATETIME列上的DATETIME搜索谓词比字符串文字谓词慢得多

这是一个直接的查询:

declare @StartDateTime datetime = '2016-06-21', 
     @EndDateTime datetime = '2016-06-22'; 

select * 
FROM Archive.dbo.Order O WITH (NOLOCK) 
where O.Created >= @StartDateTime 
    AND O.Created < @EndDateTime; 

创建是具有非聚集索引中的DATETIME列。

此查询大约需要15秒才能完成。

但是,如果我稍微修改查询,如下所示,则仅需1秒钟返回相同的结果:

declare @StartDateTime datetime = '2016-06-21', 
     @EndDateTime datetime = '2016-06-22'; 

select * 
FROM Archive.dbo.Order O WITH (NOLOCK) 
where O.Created >= '2016-06-21' 
    AND O.Created < @EndDateTime; 

唯一的变化是用一个字符串替换@StartDateTime搜索谓词。查看执行计划,当我使用@StartDateTime时,它执行了索引扫描,但是当我使用字符串时,它执行了索引查找,速度提高了15倍。

有谁知道为什么使用字符串文字是如此之快?

我原以为DATETIME列和DATETIME变量之间的比较比将列与日期的字符串表示进行比较要快。我尝试删除并重新创建创建列上的索引,它没有任何区别。我注意到我在生产系统上得到了类似于测试系统上的结果,所以奇怪的行为似乎并不特定于特定的数据库或SQL Server实例。

+1

答案是关系到SQL Server如何识别查询中的变量。如果SQL在运行查询之前无法知道该变量是什么,它可能无法使用缓存计划或无法有效猜测新的计划 –

+1

有一篇关于该主题的好文章,我认为您会发现它有帮助:[SQLmag - 优化变量和参数](http://m.sqlmag.com/t-sql/optimizing-variables-and-parameters) –

+0

@clifton_h:对不起,它需要一段时间,一直忙于工作。您链接的那篇文章回答了我的问题。如果您想将您的评论转换为答案,我会接受它。 –

回答

1

所有变量都有它们被识别的实例。

OOP语言中,我们通常从临时变量static/constant变量之间通过使用关键字区分时,或者当变量被称为入其中该实例内的变量被当作一个常数的函数,如果函数变换变量,像这样在C++如下:

void string MyFunction(string& name) 
//technically, `&` calls the actual location of the variable 
//instead of using a logical representation. The concept is the same. 

SQL Server,标准选择不同的方式实现这一点。有没有constant数据类型,所以不是我们用文字它们要么

  • 对象的名称(必须在呼叫作为系统的关键字类似的优先级)
  • 名称与对象deliminator(包括”,[]
  • 或带有分隔符的字符串CHAR(39)(')。

这是你注意到两个查询产生不同的结果,因为这些变量不是常量的优化,这意味着SQL Server将已经选择了它的执行路径提前的原因。

如果您安装了SSMS,请包含实际执行计划(CTRL + M),并在select语句中注明预计行数。这是执行计划的亮点。 “估计”行和“实际”行之间的差异越大,查询可以使用优化的可能性就越大。在您的示例中,SQL Server必须猜测有多少行,并最终超出结果,从而降低效率。

解决方案是一样的,但如果您愿意,仍然可以封装所有内容。我们使用AdventureWorks2012这个例子:

1)声明变量在Procedure

CREATE PROC dbo.TEST1 (@NameStyle INT, @FirstName VARCHAR(50)) 
AS 
BEGIN 
    SELECT * 
    FROM Person.PErson 
    WHERE FirstName = @FirstName 
    AND NameStyle = @NameStyle; --namestyle is 0 
END 

2)传递变量为Dynamic SQL

CREATE PROC dbo.TEST2 (@NameStyle INT) 
AS 
BEGIN 

DECLARE @Name NVARCHAR(50) = N'Ken'; 
DECLARE @String NVARCHAR(MAX) 
SET @String = 
    N'SELECT * 
    FROM Person.PErson 
    WHERE FirstName = @Other 
    AND NameStyle = @NameStyle'; 
EXEC sp_executesql @String 
      , N'@Other VARCHAR(50), @NameStyle INT' 
      , @Other = @Name 
      , @NameStyle = @NameStyle  
END 

两个计划会产生相同的结果。我可以用EXEC本身,而是sp_executesql可以缓存整个select语句(加,其更SQL Injection安全)

请注意,在这两种情况下允许SQL Server的变量转化为一个恒定值的情况下的水平(意思它以设定的值输入对象),然后优化器能够选择最有效的执行计划。

-- Remove Procs 
DROP PROC dbo.TEST1 
DROP PROC dbo.TEST2 

一个伟大的文章强调了在OP的注释部分,但你可以看到它在这里:Optimizing Variables and Parameters - SQLMAG