如何按值传递sql函数参数

Posted

技术标签:

【中文标题】如何按值传递sql函数参数【英文标题】:How to pass sql function parameter by value 【发布时间】:2012-07-02 03:10:20 【问题描述】:

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

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

问题在哪里?

查询 1:

SELECT *
FROM table
WHERE field > 180

查询 2:

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

【问题讨论】:

阅读此处:sommarskog.se/query-plan-mysteries.html 感谢 Remus - 这是一个极好的指针!然而——我对大量的 SQL 细节感到有点推土机——但也有一个指向另一个 excellent article 的链接 【参考方案1】:

嗯 - 我被要求详细说明解决方案(我也看到了"tips on writing great answers") - 让我试着解释一下 - 虽然我只能解释效果 - 为了理解背景,人们必须自己投入提到的文章。

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

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 需要几毫秒

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

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

在mentioned article Erland 中或多或少地解释了我的做法:

(开始报价)

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

(结束引用)

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

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

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

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

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

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

【讨论】:

【参考方案2】:

关键在于parameter sniffing。来自提到的文章:

“当参数化查询使用缓存的基数估计来制定查询计划决策时,会发生参数嗅探。当第一次执行具有非典型参数值时,就会出现问题。对于每个后续执行,优化器将假定估计是好的即使估计值可能相差甚远。例如,假设您有一个存储过程,它返回 1 到 1000 之间的所有 id 值。如果使用如此大范围的参数值执行存储过程,优化器将缓存这些非典型值值,这间接导致优化器低估基数。问题是典型的执行可能只返回几行。这种“嗅探”可能导致查询扫描一个表反对寻找,因为优化器假设的基数估计不准确。"

【讨论】:

非常有趣,虽然我从来没有遇到过 难以置信 - 将相同的选择放入 executesql 并返回到毫秒: exec sp_executesql N'SELECT * FROM table WHERE field > @v', N'@v int', @v=180 -谢谢大卫! 虽然理论上可以回答这个问题,但我们希望您在回答中包含链接文章的基本部分,并提供link for reference。不这样做会使答案面临链接失效的风险。

以上是关于如何按值传递sql函数参数的主要内容,如果未能解决你的问题,请参考以下文章

如何理解javaSript中函数的参数是按值传递

如何理解函数的参数都是按值传递的

当作为参数传递给返回 void 并在函数中修改的函数时,如何修改 Java ArrayList?可能对按值传递感到困惑

为啥我要在 C 中按值传递函数参数?

C#在方法调用中,参数按值传递与按引用传递的区别是啥?

ECMAScript 中所以函数的参数都是按值传递