为什么用局部变量替换参数会加快查询速度

Ton*_*llo 4 sql t-sql sql-server

我有一个包含两个 DATE 参数的查询,如下所示:

@startDate DATE,
@endDate DATE
Run Code Online (Sandbox Code Playgroud)

在开发存储过程时,它非常棒(< 1 秒)。小时候把它移到存储过程中,当我再次运行它时,需要几分钟才能运行(确切地说是 2 分钟)。

我之前遇到过这个问题(我认为这是我当时没有追究的一些异常现象),所以我尝试了最后一个有效的“黑客”:

DECLARE @sDate DATE = CAST(@startDate AS DATE);
DECLARE @eDate DATE = CAST(@endDate AS DATE);
Run Code Online (Sandbox Code Playgroud)

果然,返回时间小于 1 秒。

我已经尝试了一切来解决这个问题,但似乎没有任何效果。我找不到任何改变任何东西的差异。无论我尝试以多少种不同的方式对其进行切片,这些值都是完全相同的。

我也尝试过:

SET @startDate = CAST(@startDate AS DATE);
SET @startDate = CONVERT(date, @startDate, 101)
Run Code Online (Sandbox Code Playgroud)

我尝试在父存储过程中重新声明它们(使用任何方法)。

仅当我重新声明子存储过程中的变量时,它才有效。

那么,为什么重新声明相同类型的变量会导致性能如此巨大的差异呢?

更新 - 这参数嗅探

我最初并不这么认为,但所有证据都表明事实确实如此,尽管我无法使用通常有效或有助于识别它的正常方法来修复它。除了用局部变量替换它之外,在下面所有海报的帮助下,它表明它必须是参数嗅探。


第一次更新

我不认为这是参数嗅探——这是我的第一个想法。这就是我所做的测试:

  • 更改的参数(添加/删除)
  • 向查询添加了附加条件
  • 添加了重新编译选项
  • 设置 ARITHABORT 为 ON
  • 删除/创建旧索引和新索引

上述更改对查询没有影响。

Luk*_*zda 5

这是一种“参数嗅探”解决方法。我建议阅读:应用程序慢,SSMS 快?

参数和变量

考虑 Northwind 数据库中的 Orders 表,以及以下三个过程:

CREATE PROCEDURE List_orders_1 AS
   SELECT * FROM Orders WHERE OrderDate > '20000101'
go
CREATE PROCEDURE List_orders_2 @fromdate datetime AS
   SELECT * FROM Orders WHERE OrderDate > @fromdate
go
CREATE PROCEDURE List_orders_3 @fromdate datetime AS
   DECLARE @fromdate_copy datetime
   SELECT @fromdate_copy = @fromdate
   SELECT * FROM Orders WHERE OrderDate > @fromdate_copy
go
Run Code Online (Sandbox Code Playgroud)

在第一个过程中,日期是一个常量,这意味着 SQL Server 只需要考虑这种情况。它询问 Orders 表的统计信息,该统计信息表明没有 OrderDate 为第三个千年的行。(Northwind 数据库中的所有订单都是从 1996 年到 1998 年。)由于统计数据就是统计数据,SQL Server 无法确定查询根本不会返回任何行,因此它只估计一行。

在 List_orders_2 的情况下,查询针对变量,或更准确地说是针对参数。执行优化时,SQL Server 知道该过程是使用值 2000-01-01 调用的。由于它不执行任何流分析,因此无法确定执行查询时参数是否具有该值。尽管如此,它还是使用输入值来进行估计,这与 List_orders_1 相同:一行。这种在优化存储过程时查看输入参数值的策略称为参数嗅探。

在最后一个过程中,一切都不同了。输入值被复制到局部变量,但是当 SQL Server 构建计划时,它对此并不了解,并且对自己说我不知道​​这个变量的值是什么。

...

关键点

在本节中,我们学到了三件非常重要的事情:

- 常量就是常量,当查询包含常量时,SQL Server 可以完全信任地使用该常量的值,甚至可以采用这样的快捷方式根本不访问表,如果它可以从约束推断出没有行将被退回。

- 对于参数,SQL Server 不知道运行时值,但它在编译查询时“嗅探”输入值。

-对于局部变量,SQL Server 完全不知道运行时值,并应用标准假设。(哪些假设取决于运算符以及可以从唯一索引的存在中推断出什么。)

第二篇好文章参数嗅探问题和可能的解决方法

  • 我通常通过重新声明参数来修复参数嗅探,所以我认为你是100%正确的。 (2认同)