【问题标题】:Should local variables for constants be avoided in stored procedures?在存储过程中应该避免常量的局部变量吗?
【发布时间】:2016-05-30 07:07:33
【问题描述】:

当我编写 SQL 时,我会尽量使其具有可读性。除其他外,我经常声明“常量”而不是使用“幻数”。

即而不是

WHERE [Order].OrderType = 3

我愿意

DECLARE @OrderType_Cash AS int = 3;
...
WHERE [Order].OrderType = @OrderType_Cash

这很好用,我没有注意到我通常使用的查询和数据大小有任何性能问题。

最近我阅读了一篇关于参数嗅探和解决方法的文章 (https://blogs.msdn.microsoft.com/turgays/2013/09/10/parameter-sniffing-problem-and-possible-workarounds/)。在文章中提出的解决方法之一是“使用局部变量”。

  1. 解决方法:使用局部变量 - 此解决方法与前一个非常相似 (OPTION (OPTIMIZE FOR (@VARIABLE UNKNOWN))) - 当 您将参数分配给本地参数 SQL Server 使用统计信息 密度而不是统计直方图 - 所以它估计相同 所有参数的记录数——缺点是一些 查询将使用次优计划,因为密度不精确 足以作为统计直方图。

这让我有点担心,因为我的解释是我可能会因为我使用局部变量而不是“幻数”而在存储过程中得到一个次优计划。

我还觉得 SQL Server 会自动将“幻数”转换为变量以重用计划。

谁能帮我解决这个问题?

  • 使用“幻数”和局部变量有区别吗?
  • 如果是,它是否仅适用于存储过程,还是也适用于即席查询和动态 SQL?
  • 像我这样使用局部变量是不是一个坏习惯?

【问题讨论】:

标签: sql-server stored-procedures


【解决方案1】:

如文章Statistics Used by the Query Optimizer in Microsoft SQL Server 2005中所述

如果您在查询谓词中使用局部变量而不是 参数或文字,优化器采用降低质量 估计,或猜测谓词的选择性。使用参数 或查询中的文字而不是局部变量

关于您的问题...

我也有这样的印象,SqlServer 会自动将“幻数”转换为变量,以便重复使用计划。

不,永远不会,它可以auto parameterise 即席查询,但参数的行为与变量不同,并且可以被嗅探。默认情况下,它只会在“安全”且不太可能引入参数嗅探问题的非常有限的情况下执行此操作。

使用“幻数”和本地有区别吗 变量?

是的,语句通常在变量值被赋值之前编译。并且即使该语句要进行延迟编译(或在赋值后恰好被重新编译)变量的值仍然永远不会被嗅探,除非您使用option (recompile)。如果您使用文字内联,SQL Server 可以在直方图中查找该文字值,并可能获得更准确的估计,而不是依靠猜测。准确的行估计对于获得正确的整体计划形状(例如连接类型或访问方法选择)以及为您的查询获得适当的内存授予非常重要。

《SQL Server 2005 实用故障排除》一书对这个问题有这样的说法。

在 SQL Server 2005 中,语句级编译允许编译 将存储过程中的单个语句推迟到 就在第一次执行查询之前。届时当地 变量的值是已知的。理论上 SQL Server 可以采取 以此来嗅探局部变量值的优势与 它嗅探参数。但是因为通常使用本地 在 SQL Server 7.0 和 SQL 中阻止参数嗅探的变量 Server 2000+,在 SQL 中未启用对局部变量的嗅探 Server 2005。不过,它可能会在未来的 SQL Server 版本中启用

(注意:迄今为止,实际上尚未在任何版本中启用此功能)

如果是,是只存在于存储过程中还是也存在 适用于即席查询和动态 sql?

这适用于变量的每次使用。参数可以被嗅探,因此如果您将外部作用域中的变量作为内部作用域中的参数传递,则允许嗅探变量值。

像我一样使用局部变量是不是一个坏习惯?

如果计划对确切的变量值敏感,那么是。但是,在某些地方它完全无害。

option (recompile) 的缺点是它每次都重新编译语句。当这样做的唯一原因是让它嗅探一个值为常量的变量时,这是不必要的。 option (optimize for) 具有特定字面值的缺点是,如果值发生更改,您也需要更新所有这些引用。

另一种方法是创建常量视图。

CREATE VIEW MyConstants
AS
SELECT 3 AS OrderTypeCash, 4 AS OrderTypeCard

然后,完全不用为那些变量使用变量,而是引用它。

WHERE [Order].OrderType = (SELECT OrderTypeCash FROM MyConstants)

这将允许在编译时解析该值,并且只需要在一个地方更新。

或者,如果您使用 SSDT 和数据库项目,您可以使用一个 sqlcmd 变量,该变量定义一次并分配给然后用它替换所有 TSQL 变量引用。部署到服务器的代码仍将具有“幻数”,但在您的源代码中它是单个 SqlCmd 变量(注意:对于这种模式,您可能需要在项目中创建一个存根过程并使用部署后脚本来实际更改它具有所需的定义并执行 sqlcmd 替换)。

【讨论】:

  • @George no 这不会有任何区别,当更新统计信息时,计划将自动标记为需要基于最优性的重新编译。 option (recompile) 不会为您更新统计信息。当统计信息与上次完全相同时,每次重新编译没有任何好处。
  • 很好的解释,谢谢。我认为OPTION (OPTIMIZE FOR (@OrderType_Cash = 3)) 是最好的解决方案,但这会损害可读性(恕我直言)并使其更难维护
  • @adrianm 这是一种解决方案,但是我认为直接使用文字 TBH 并没有多大好处,因为当它发生变化时,您现在也需要更新所有这些文字。您可以创建一个视图或内联 TVF,将这些值作为文字返回单行,然后在编译时将对其的列引用解析为文字值。
猜你喜欢
  • 2012-02-25
  • 2023-04-05
  • 1970-01-01
  • 2019-08-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多