1

这是更大选择的一部分,但我已将其简化为基本问题:

比较这两个 SQL 查询 - 第一个使用常量,第二个使用变量,两者都具有相同的值(比如说 180)。带有常量的那个会立即显示结果(例如在几毫秒内),带有变量的那个需要几秒钟才能产生相同的结果。

问题在哪里?

查询一:

SELECT *
FROM table
WHERE field > 180

查询 2:

DECLARE @V INT
SET @v = 180
SELECT *
FROM table
WHERE field > @v
4

2 回答 2

2

关键在于参数嗅探。从提到的文章中:

“当参数化查询使用缓存的基数估计来制定查询计划决策时,就会发生参数嗅探。当第一次执行具有非典型参数值时,就会出现问题。对于每个后续执行,优化器都会假设估计是好的,即使估计可能是例如,假设您有一个存储过程,它返回 1 到 1000 之间的所有 id 值。如果使用这么大范围的参数值执行存储过程,优化器将缓存这些非典型值,这间接导致“

于 2012-07-01T10:28:42.397 回答
0

好吧-我被要求详细说明解决方案(我还看到“写好答案的技巧”)-让我尝试解释一下-尽管我只能解释效果-为了了解背景,必须自己动手进入提到的文章。

我遇到了每三分钟对某些数据进行快照的问题。(非常简化的)查询是

SELECT *
FROM table
WHERE TimeStamp > DateAdd(ss,-180,GetDate())

工作完美 - 把它放在一个函数中:

CREATE FUNCTION GetSnapshot (@ss int) RETURNS TABLE
AS
RETURN
  SELECT *
  FROM Table
  WHERE TimeStamp > DateAdd(ss,-@ss,GetDate())

只要我用常量调用它,这也很完美,例如

SELECT *
FROM GetSnapshot(180)

现在我想进一步参数化,因为 180 秒不适合所有目的。现在问题开始了:

DECLARE @v int
SET @v = 180
SELECT *
FROM GetSnapshot(@v)

运行近10 秒,而直接调用 180 需要毫秒

我还必须提到,简单的表格也有同样的效果——我调用函数的事实并没有影响结果。无论我尝试什么 - 十秒。

现在在完全绝望之前,我用标题中的问题求助于 stackoverflow 中的专家。我对编程语言中的参数传递了解很多——但在 SQL 中却一无所知。在 PL 中,如果您按值传递,编译器会生成代码以在运行时制作实际值的本地副本,并将其像常量一样传递给被调用函数 - 而按引用传递则“按原样”传递语言构造,然后被调用的过程可以一次又一次地“调用”这个参数 - 无论是变量还是函数调用或其他任何东西。因此,我的印象是,SQL 编译器通过值调用常量和通过引用调用变量——这需要被调用过程进行多次评估,并解释几万条记录的结果集的长时间运行。

提到的文章中,Erland 或多或少地以我的方式解释了它:

(开始报价)

  • 常量就是常量,当查询包含常量时,SQL Server 可以完全信任地使用常量的值,甚至可以使用这样的捷径根本不访问表,如果它可以从约束中推断出没有行会被退回。
  • 对于参数,SQL Server 不知道运行时值,但它会在编译查询时“嗅探”输入值。
  • 对于局部变量,SQL Server 根本不知道运行时值,并应用标准假设。(哪些假设取决于操作员以及可以从唯一索引的存在中推断出什么。)

(结束报价)

然后他进一步详细说明了参数嗅探和执行计划,我并不羞于承认我什么都不懂 (-:) - 但总而言之,它类似于编程语言的按值/被引用概念。

现在如何强制 SQL Server “按值调用”?

幸运的是,David 对Demystifying SQL Server 的提示:SQL Server Parameter Sniffing文章给出了我称赞的解决方案:使用 sp_executesql 包装器打包完整的“按引用”调用 - 在这种情况下,外部调用仍将是“按参考”,但由于参数解析是在包装级别完成的,因此内部调用可以“按值”完成,我们又回到了毫秒级的响应时间。

像这样使用它来制作技巧:

exec sp_executesql 
  N'SELECT * FROM GetSnapshot(@v)',
  N'@v int',
  @v=180

就是这样 - 抱歉回复晚了,但我最近几天很忙...... Meiki

于 2012-07-08T23:29:24.737 回答