是否应该在存储过程中避免使用常量的局部变量?

时间:2016-05-30 07:07:34

标签: sql-server stored-procedures

当我编写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/)的文章。在artice中,提出的解决方法之一是"使用局部变量"。

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

这让我有点担心,因为我的解释是我可能在我的存储过程中得到一个不理想的计划,因为我使用局部变量而不是#34;幻数"。

我还认为SQL Server会自动转换"魔术数字"变量以便重用计划。

有人可以为我清楚吗?

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

1 个答案:

答案 0 :(得分:4)

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

中所述
  

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

关于你的问题......

  

我还认为SqlServer会自动将“魔数”转换为变量,以便重复使用计划。

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

  

使用“幻数”与本地之间是否有区别   变量?

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

“SQL Server 2005实用故障排除”一书就这个问题说了这个。

  

在SQL Server 2005中,语句级编译允许编译   将存储过程中的单个语句推迟到   就在第一次执行查询之前。到那时当地人   变量的值是已知的。理论上SQL Server可以采取   这样做的好处就是以同样的方式嗅探局部变量值   它嗅探参数。但是因为通常使用本地   用于在SQL Server 7.0和SQL中阻止参数嗅探的变量   Server 2000+,SQL中未启用对局部变量的嗅探   Server 2005.它可以在将来的SQL Server版本中启用

(注意:到目前为止,这在任何版本中都没有启用)

  

如果是,是仅在存储过程中还是也在存储过程中   适用于ad-hoc查询和动态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替换。)