我有一个存储过程,它根据输入参数构建动态SQL语句,然后执行它。
其中一个查询导致超时,所以我决定检查一下。执行问题语句的第一次(也是第一次)它很慢(30秒-45秒),每次执行都需要1-2秒。
为了重现这个问题,我正在使用
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
我真的很困惑,问题出在哪里,因为普通的如果SQL语句很慢,它总是很慢。现在,它只是第一次执行时间很长。
是否可能,本身变慢,需要优化或问题可能是由其他原因造成的?
执行计划如下,但对我来说没有什么奇怪的:
答案 0 :(得分:2)
非常简单的原因是第一次和第一次需要更长的时间,然后所有后来的执行都相当快。这个谜团背后的原因是“CACHED执行计划”。
使用存储过程时,Sql server采用以下方法 步骤。
1)解析命令的语法。
2)转换为查询树。
3)开发 执行计划。
4)执行。
只有在创建存储过程时才会执行前两个步骤。
第3步仅在第一次执行时或者如果已从CACHE MEMORY刷新CACHED PLAN。
第四步发生在每次执行时,如果计划仍在缓存中,这是第一次执行后发生的唯一步骤。
在你的情况下,非常容易理解,第一次执行需要很长时间,然后它会很快执行。
要重现您执行DBCC FREEPROCCACHE
AND DBCC DROPCLEANBUFFERS
commanda的“问题”,它基本上会刷新BUFFER CACHE MEMORY
并导致您的存储过程在下次执行时为其创建新的执行计划。希望这会有点清除雾气:)
答案 1 :(得分:2)
从您对我的评论的回复看来,第一次运行此查询时,它会执行大量物理读取或预读取读取,这意味着需要大量IO才能将正确的页面放入缓冲区池来满足这个查询。
一旦将页面读入缓冲池(内存),它们通常会保留在那里,因此不需要物理IO来再次读取它们(您可以看到这种情况正在发生,因为您指示物理读取转换为逻辑读取第二个查询运行的时间)。内存比磁盘IO快几个数量级,因此该查询的速度差异。
查看该计划,我可以看到每个读取操作都是针对表的聚簇索引完成的。由于聚簇索引包含该行的每个列,因此每行可能会获取比查询实际所需的更多数据。
除非您从每个表中选择每一列,否则我建议创建满足此查询的非聚集覆盖索引(尽可能地缩小),这将减少查询的IO要求并使其更便宜第一次。
当然这对你来说可能是不可行/可行的,在这种情况下你应该只是在第一次运行时获取命中而不是清空缓存,或者重写查询本身以提高效率并执行更少的读取
答案 2 :(得分:1)
通常,当首次创建存储过程或重置统计信息等时,它将传递到存储过程的第一个值作为存储过程的“默认”值。然后它将尝试基于此进行优化。
要阻止这种情况发生,你可以做几件事。
您可以使用“查询提示”功能将某些变量标记为“未知”。因此,作为一个例子,在存储过程结束时,您可以添加以下内容:
select * from foo where foo.bar = @myParam option (optimize for @myParam unknown)
作为另一种方法,您可以强制每次重新编译SQL计划 - 如果您的存储过程在其生成的SQL类型中高度可变,这可能是个好主意。你这样做的方式是:
select * from foo where foo.bar = @myParam option (optimize recompile)
希望这有帮助。