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

标签 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;

创建的是一个具有非聚集索引的 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 实例。

最佳答案

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

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中,标准选择了稍微不同的实现方式。没有常量数据类型,因此我们使用的文字是

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

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

If you have SSMS installed, include the Actual Execution Plan (CTRL + M), and notice in the select statement what the Estimated Rows are. This is the highlight of the execution plan. The greater difference between the Estimated and Actual rows, the more likely your query can use optimization. In your example, SQL Server had to guess how many rows, and ended up overshooting the results, losing efficiency.

解决方案是相同的,但如果您愿意,您仍然可以封装所有内容。我们在此示例中使用 AdventureWorks2012:

1) 在过程中声明变量

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) 将变量传递到动态 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 注入(inject) 更安全)

请注意,在这两种情况下,实例级别如何允许 SQL Server 将变量转换为常量值(意味着它以设定值进入对象),然后优化器能够选择可用的最有效的执行计划。

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

OP 的评论部分突出显示了一篇很棒的文章,但您可以在这里看到它:Optimizing Variables and Parameters - SQLMAG

关于sql-server - DATETIME 列上的 DATETIME 搜索谓词比字符串文字谓词慢得多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38559617/

相关文章:

sql - 如何有效的版本存储程序?

SQL Server - 按不同列的值分组

sql - 在 SQL Server 2008R2 上以毫秒为单位返回 DATEDIFF

sql-server - 使用索引排序

c# - 在事务中获取 SCOPE_IDENTITY

c# 查询 ms 对 sql server 的 Access

sql-server - 从 Azure VM 使用 SQL AZure 进行复制

c# - 从网站主机连接到专用网络上的安全数据库

c# - 系统.ComponentModel.Win32Exception : The network path was not found Error

sql - 如何建立外键关系