我试图了解我们在使用 SQL Server 2000 时遇到的问题。我们是一个中等事务的网站,我们有一个名为的存储sp_GetCurrentTransactions
过程,它接受一个客户 ID 和两个日期。
现在,根据日期和客户,此查询可以返回从零到 1000 行的任何内容。
问题:我们所经历的是,Execution Timeout Expired
当他们尝试执行该存储过程时,我们会突然为特定客户端收到许多错误(通常或类似)。所以我们检查查询,在 SSMS 中运行它,发现它需要 30 秒。所以我们重新编译存储的 proc 并 -bang- 它现在在 300 毫秒内运行。
我已经和我们的 DBA 谈过这个问题。他告诉我,当我们创建存储过程时,数据库创建了一个查询计划。他说对于那组参数来说这是一个很好的计划,但是如果你给它扔一组参数,那么这个计划就不是那个数据的最佳计划,所以你会看到它运行得很慢。
呈现给我的选项是将问题查询从存储的过程转移到动态 SQL 中,动态 SQL 在每次运行时都会创建执行计划。
这对我来说就像是退后一步,我觉得必须有办法解决这个问题。有没有其他方法来处理这个问题?
任何和所有的回应都表示赞赏。
这个问题称为参数嗅探。
更高版本的 SQL Server 为您提供了更多处理它的选项,例如
OPTION (RECOMPILE)
或OPTIMIZE FOR
提示。您可以尝试在存储过程中声明变量,将参数值分配给变量并使用变量代替参数,因为这听起来好像大多数时候您都得到了一个相当令人满意的计划。
通常,最糟糕的计划是那些为具有非常高选择性的参数编译但运行具有低选择性的参数的计划。
假设使用这种方法生成的计划更健壮并且对所有参数值都满意,那么这种方法相对于 JNK 建议的方法的优势在于它不会为每次调用产生编译成本。
缺点是,对于某些执行,运行时间可能比专门为这些参数值定制的计划要长,因此这是编译时间与执行时间的权衡。
除了使用动态 SQL,您总是可以将您的 proc 调用更改为:
EXEC Database.dbo.usp_Myprocedure 'Parameter' WITH RECOMPILE
强制(你猜对
WITH RECOMPILE
了!)执行计划在运行时重新编译。您还可以
WITH RECOMPILE
在存储过程的定义中包含:您也可以尝试决定计划使用哪个数据库,尽管您会与优化器有一点冲突,所以它比您希望的更脆弱。
该技术是这样的 - 将存储过程分成 2 个,一个用于一组参数,一个用于另一个。将 where 子句添加到每个子句,以便它们之间涵盖所有可能的情况。查看查询计划 - 一个应该针对一组参数进行优化,另一个针对另一组进行优化。您可能必须修改查询才能实现这一点,或者您的查询可能无法实现,在这种情况下,这种方法将不起作用。
现在让您的原始存储过程检查参数值并分派到上一段中的两个存储过程中的适当一个。
这可以工作,但它是一种强制优化器更有效地为您的查询工作的技巧。像所有此类黑客攻击一样,在数据库的未来版本中,这可能是不必要的,甚至会使事情变得更糟。因此,即使它有效,您也必须决定是否值得。
您还可以尝试
SET FORCEPLAN
索引提示。http://msdn.microsoft.com/en-us/library/ms188344.aspx
它基本上允许您选择加入的顺序。
您可以使用索引提示来确保 SQL 服务器使用正确的索引。
嗯...如果我们只关注这个存储过程,我会惊讶于使用缓存的执行计划会导致您看到的问题。我会要求使用一组客户参数和两个日期来查看存储过程的执行计划。我想知道更具体的索引是否会有所帮助-> 例如在 customerId 上,并且只有两个日期?
突然降低性能听起来像是一个低效的查询计划,可能是由于缺少统计信息。运行设置了“错误和警告”事件类别的 SQL Server 探查器,查看是否有关于缺少统计信息的警告。
您可能还缺少索引,或者您可能需要对索引进行碎片整理,因为它们可能过于碎片化,SQL Server 无法使用,导致它认为表扫描会产生更少的 I/O。
@JNK 提出了一个关于存储过程的重要观点——这些是预先编译的,查询计划将与存储过程一起存储。
我不一定同意使用WITH RECOMPILE,因为这样您就失去了存储和重用查询计划的好处。在某些情况下这是必要的 - 即,如果您在基础表中的分布统计数据在调用之间差异很大,但通常,一旦表中的数据成熟,表中的数据分布将变化最小。
所以,总结一下: