我正在编写一个使用重型select语句的存储过程。存储过程接受大约15个参数作为过滤器,所有这些参数都是NULLable。
参数通常有两件事 - 检查x是否在高和低之间或检查列值是否在y中。
我主要担心的是我如何编写where子句。
示例:动态SQL非常慢,因此我不想编写where子句,然后将其传递给exec。
我不想做if High = null then High = max
,因为那时我仍然会有一个处理能力并没有用处的声明。
我不想做(if High = null or X <= High)
,因为仍然会为每一行处理空检查,而且我听到传言会混淆索引。
简而言之,我正在寻求考虑性能的最佳实践指南。
答案 0 :(得分:7)
动态SQL过去很慢,因为没有缓存动态生成的SQL的执行计划。这不再是这种情况,只要查询文本相同,动态SQL查询的执行计划就会被缓存。这意味着你应该:
只要你这样做,那么你的查询计划应该被缓存(每个可能的查询变体一个),动态SQL不会比任何普通查询慢。
您应避免使用其他建议(将各种参数设置为NULL)并且实际上可能执行得非常糟糕 - 语句只能有一个缓存查询计划,但最佳查询计划将取决于根据提供的值,参数可能会有很大差异。
例如,一组参数可能导致返回大部分表,在这种情况下,表扫描可能是最佳的。另一组参数可能导致返回单行,在这种情况下,行查找可能是最佳的。 SQL Server必须选择缓存这两个计划中的一个(可能是基于第一次运行查询时提供的参数的最佳计划),但无论选择哪种计划,查询都可能在相反的情况下执行得很糟糕。 (这是一种过度简化,但是我已经看到了这种情况的变化,并且会对性能产生非常显着的影响。)
最终这意味着:
您的替代方法的另一个缺点是它会导致更复杂的查询,这会使查询优化器难以正确优化查询。
由于这些原因,我认为动态SQL肯定是更好的选择。
答案 1 :(得分:3)
如果将动态SQL与占位符一起用于参数而不是将参数合并到语句中,并在打开游标时绑定参数,则可以缓存该语句并且不会很慢。
从下面评论:没有那么多(是的,可能是2 ^ 15但实际上更少,可能更像是2 ^ 4通常使用的)当前和缺失参数的组合;这些都可以缓存。如果实际参数值包含在WHERE子句中(我已经看过),则每个查询都是唯一的,不会被缓存。
答案 2 :(得分:1)
在where
子句表达式中处理无效性检查的一种方法是执行以下操作:
declare @myParameter int
select *
from dbo.foo t
where t.someColumn = coalesce( @myParameter , t.someColumn )
优化器仍然可以在t.someColumn
上使用索引,并且您可以避免使用OR
运算符(这通常是使用索引的过程。
这是一件值得关注的事情。
另一件事:我不得不在以前的工作中做同样的事情。天真编码的问题在于,由于以下原因中的任何一个原因,您可能会遇到性能不佳的问题:
如果执行的第一个查询是使用您可能描述为“非标准”参数执行的,则缓存的执行计划可能会在更普通的情况下表现不佳。
对于所有这些变量,优化器可以选择一个查询计划,该计划为大多数时间都不会使用的参数拨打。
列表继续......
我最后做的是检测存储过程,记录它的调用方式。一旦我得到了一些基线数据,一点分析就向我展示了它使用的4种或5种最常见的方式。
这让我可以为这些最常见的方式添加场景,因此90%的呼叫者都获得了很好的性能,其余大多数都获得了良好的性能,并且曾经有一些特殊情况我们无法做任何事情(除非DBA愿意重新选择参与选择的一些表格......这似乎不太可能)。
此外,您应该将存储过程参数分配给存储过程中的局部变量。如果不这样做,则传递的参数会影响执行计划的缓存方式。通过这样做,参数的值成为表达式,不再影响执行计划的cachin。
此外,请注意存储过程重新编译。在繁忙的系统中,重新编译会对性能产生有害影响。如果存储过程在每次存储过程调用中被重新编译一次或多次,则重新编译会取出编译锁(A)阻止其他人执行存储过程,直到重新编译完成,并且(B)锁定涉及的各种资源/依赖项在重新编译中。在繁忙的系统中,您的DBA不太可能在阻止方面受到青睐。
这是Execution Plan Caching and Reuse
上的MSDN希望这有帮助。