【问题标题】:Dynamic SQL - long execution time - first time only动态 SQL - 执行时间长 - 仅限第一次
【发布时间】:2014-01-21 11:59:08
【问题描述】:

我有一个存储过程,它根据输入参数构建动态 SQL 语句,然后执行它。

其中一个查询导致超时,所以我决定检查一下。第一次(也是第一次)执行 issue 语句很慢(30 秒 - 45 秒),并且每次下一次执行需要 1-2 秒。

为了重现问题,我正在使用

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

我真的很困惑问题出在哪里,因为普通的if SQL语句很慢,总是很慢。现在,它只有第一次执行时间长。

有可能,是本身速度慢需要优化还是其他原因造成的?

执行计划如下,但对我来说并没有什么奇怪的:

【问题讨论】:

  • 清除缓存,然后在 SET STATISTICS IO ON 的情况下运行查询;查看读取次数(尤其是物理读取或预读),然后在不清除缓存的情况下再次运行它并查看读取次数,如果有很多现在是逻辑读取的物理/预读读取, SQL server 很可能只需要做大量的 IO 来满足查询,一旦这些数据缓存在内存中,速度就会快很多。
  • @steoleary 你是对的。所有物理读取都变为逻辑读取。所以,这就解释了为什么会出现这种情况。我想,唯一要解决的就是尝试优化查询本身。

标签: sql sql-server tsql sql-server-2012


【解决方案1】:

从您对我的评论的回复来看,该查询第一次运行时似乎会执行大量物理读取或预读读取,这意味着需要大量 IO 才能将正确的页面放入缓冲区池来满足这个查询。

一旦页面被读入缓冲池(内存),它们通常会停留在那里,这样就不需要物理 IO 再次读取它们(您可以看到这种情况正在发生,因为您指出物理读取在第二次转换为逻辑读取运行查询的时间)。内存比磁盘 IO 快几个数量级,因此此查询的速度存在差异。

查看计划,我几乎可以看到每个读取操作都是针对表的聚集索引执行的。由于聚集索引包含该行的每一列,因此每行获取的数据可能比查询实际需要的数据多。

除非您从每个表中选择每一列,否则我建议创建满足此查询的非聚集覆盖索引(尽可能窄),这将减少查询的 IO 要求并降低查询成本第一轮。

当然,这对您来说可能是不可能的/不可行的,在这种情况下,您应该在第一次运行时接受命中而不清空缓存,或者重写查询本身以提高效率并减少读取次数.

【讨论】:

    【解决方案2】:

    这很简单,原因是第一次和第一次需要更长的时间,然后所有以后的执行都很快完成。这个谜团背后的原因是“缓存执行计划”。

    在使用存储过程时,Sql server 会执行以下操作 步骤。

    1) 解析命令的语法。
    2) 转换为查询树。
    3) 开发 执行计划。
    4) 执行。

    前两个步骤仅在您创建存储过程时发生。

    第 3 步仅发生在第 1 次执行或缓存计划已从缓存内存中刷新的情况下。

    第四步发生在每次执行时,如果计划仍在缓存内存中,这是在第一次执行之后发生的唯一步骤。

    在您的情况下,第一次执行花了很长时间,然后很快执行,这是完全可以理解的。

    要重现您执行的“问题”DBCC FREEPROCCACHE AND DBCC DROPCLEANBUFFERS commanda,它基本上会刷新 BUFFER CACHE MEMORY 并导致您的存储过程在下次执行时创建一个新的执行计划。希望这会稍微消除迷雾:)

    【讨论】:

    • 是的,这是有道理的,但是在生产现场,程序执行了很多次,一切正常。只有一个特定的输入才会出现延迟。我使用DBCC FREEPROCCACHE 只是为了重现该问题。
    • DO NOT EVER 在您的生产服务器上执行这两个 DBCC 命令,除非您有非常好的理由这样做。
    • 那么在这种情况下,您需要查看执行计划,其中包含减慢程序的特定输入和普通输入,并查看计划有何不同以及您可以进行哪些更改以获得一致和有效的执行计划。
    • 是的,当然。我在我的本地备份上执行它们:-) 给我@steoleary 的建议解释了这一切——我第一次有很多物理读取之后变成了逻辑,所以问题是查询本身。跨度>
    【解决方案3】:

    一般来说,当一个存储过程第一次被创建,或者它的统计信息等被重置时,它会将第一个传入存储过程的值作为存储过程的“默认”值。然后它将尝试基于此优化自身。

    要阻止这种情况发生,您可以做几件事。

    您可能会使用查询提示功能将某些变量标记为未知。所以,作为一个例子,在存储过程的最后,你可以写一些类似的东西:

    select * from foo where foo.bar = @myParam option (optimize for @myParam unknown)
    

    作为另一种方法,您可以强制每次重新编译 SQL 计划 - 如果您的存储过程在其生成的 SQL 类型中具有高度可变性,这可能是一个好主意。你这样做的方式是:

    select * from foo where foo.bar = @myParam option (optimize recompile)
    

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-08
      • 1970-01-01
      • 2021-06-08
      • 2017-01-16
      相关资源
      最近更新 更多