DATETIME在DATETIME列上的搜索谓词比字符串文字谓词慢得多

时间:2016-07-25 04:14:10

标签: sql-server

我在大约1000万行的大桌子上进行搜索。我想指定一个开始和结束日期,并返回在这些日期之间创建的表中的所有记录。

这是一个直截了当的问题:

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;

Created是一个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变量之间进行比较会比将列与日期的字符串表示形式进行比较更快。我已尝试在Created列上删除并重新创建索引,但没有任何区别。我注意到我在生产系统上得到的结果与在测试系统上得到的结果相似,因此奇怪的行为似乎并不特定于特定的数据库或SQL Server实例。

1 个答案:

答案 0 :(得分:2)

所有变量都具有可识别的实例。

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数据类型,因此我们使用

的文字
  • 对象名称(在系统关键字的调用中具有相似的优先级)
  • 带有对象分隔符的名称(包括',[]
  • 或带有分隔符CHAR(39)(')。
  • 的字符串

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

  

如果安装了SSMS,请包含实际执行计划(CTRL + M),并在select语句中注意Estimated Rows是什么。这是执行计划的重点。 Estimated和Actual行之间的差异越大,查询就越有可能使用优化。在您的示例中,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