关于 sql server:为什么 T-SQL 变量比较比基于 GETDATE() 函数的比较慢?

Why is a T-SQL variable comparison slower than GETDATE() function-based comparison?

我有一个 T-SQL 语句,我正在对一个包含许多行的表运行。我看到一些奇怪的行为。将 DateTime 列与预先计算的值进行比较比将每一行与基于 GETDATE() 函数的计算进行比较要慢。

以下 SQL 耗时 8 秒:

1
2
3
4
5
6
7
8
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
DECLARE @TimeZoneOffset int = -(DATEPART("HH", GETUTCDATE() - GETDATE()))
DECLARE @LowerTime DATETIME = DATEADD("HH", ABS(@TimeZoneOffset), CONVERT(VARCHAR, GETDATE(), 101) + ' 17:00:00')
SELECT TOP 200 Id, EventDate, Message
FROM Events WITH (NOLOCK)
WHERE EventDate > @LowerTime
GO

这个交替奇怪地立即返回:

1
2
3
4
5
6
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
SELECT TOP 200 Id, EventDate, Message
FROM Events WITH (NOLOCK)
WHERE EventDate > GETDATE()-1
GO

为什么第二个查询这么快?

已编辑:我更新了 SQL 以准确反映我正在使用的其他设置


经过大量阅读和研究,我发现这里的问题是参数嗅探。 Sql Server 尝试根据 where 子句确定如何最好地使用索引,但在这种情况下它做得不是很好。

请看下面的例子:

慢版本:

1
2
3
4
5
declare @dNow DateTime  
Select @dNow=GetDate()  
Select *  
From response_master_Incident rmi  
Where rmi.response_date between DateAdd(hh,-2,@dNow) AND @dNow

快速版本:

1
2
3
Select *  
From response_master_Incident rmi  
Where rmi.response_date between DateAdd(hh,-2,GetDate()) AND GetDate()

"快速"版本的运行速度比慢速版本快 10 倍左右。 Response_Date 字段已编入索引并且是 DateTime 类型。

解决方案是告诉 Sql Server 如何最好地优化查询。如下修改示例以包含 OPTIMIZE 选项导致它使用与"快速版本"相同的执行计划。这里的 OPTMIZE 选项明确告诉 sql server 将本地 @dNow 变量视为日期(好像将其声明为 DateTime 是不够的:s)

在执行此操作时应小心,因为在更复杂的 WHERE 子句中,您最终可能会使查询的性能比 Sql Server 自己的优化更差。

1
2
3
4
5
6
7
8
9
10
11
12
declare @dNow DateTime

SET @dNow=GetDate()

Select ID, response_date, call_back_phone
from response_master_Incident rmi
where rmi.response_date between DateAdd(hh,-2,@dNow) AND @dNow

-- The optimizer does not know too much about the variable so assumes to should perform a clusterd index scann (on the clustered index ID) - this is slow

-- This hint tells the optimzer that the variable is indeed a datetime in this format (why it does not know that already who knows)
OPTION(OPTIMIZE FOR (@dNow = '99991231'));


执行计划必须不同,因为 SQL Server 在执行时创建执行计划时不会计算变量的值。因此,它使用了可以存储在表中的所有不同日期的平均统计数据。

另一方面,函数 getdate 是在执行时间中评估的,因此执行计划是使用该特定日期的统计数据创建的,这当然比以前的更现实。

如果你创建一个以@LowerTime为参数的存储过程,你会得到更好的结果。