何时重用非参数化、非平凡、即席查询计划

Joh*_* N. 7 sql-server execution-plan sql-server-2016

我目前正在调查一个应用程序,该应用程序似乎针对它正在查询的数据库生成 99% 的即席查询计划。我可以通过运行以下语句来检索查询计划缓存中的对象摘要来验证这一点:

抱歉无法在 SE 编辑器中输入代码,因此截图

查询 sys.dm_exec_cached_plans 的 select 语句

参考:计划缓存和优化临时工作负载(SQLSkills.com / K. Tripp) 稍作修改

上述查询的结果如下:

CacheType            Total Plans          Total MBs                               Avg Use Count Total MBs - USE Count 1                 Total Plans - USE Count 1
-------------------- -------------------- --------------------------------------- ------------- --------------------------------------- -------------------------
Adhoc                158997               5749.520042                             2             2936.355979                             126087
Prepared             1028                 97.875000                               695           46.187500                               576
Proc                 90                   69.523437                               39659         21.187500                               21
View                 522                  75.921875                               99            0.453125                                3
Rule                 4                    0.093750                                22            0.000000                                0
Trigger              1                    0.070312                                12            0.000000                                0    
Run Code Online (Sandbox Code Playgroud)

在计划缓存中的 158'997 个即席查询中,126'087 个查询只执行了一次。

在进一步检查即席查询时,我发现有些查询甚至生成了多次。我使用以下查询检查了计划缓存以检索相同的执行计划:

SELECT SUM(cplan.usecounts) AS [Unique Same Single Plans],
       qtext.text
FROM   sys.dm_exec_cached_plans AS cplan
       CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS qtext
             CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qplan
       JOIN sys.databases AS sdb
            ON  sdb.database_id = qplan.dbid
WHERE  1 = 1
       AND cplan.objtype = 'Adhoc'   -- <-- only Adhoc plans
       AND sdb.name = 'DATABASENAME' -- <-- for a certain database
       AND cplan.usecounts = 1       -- <-- with a usecounts of 1
GROUP BY
       qtext.text having sum(cplan.usecounts) > 1
ORDER BY
       1 DESC --,cplan.objtype, cplan.usecounts   
Run Code Online (Sandbox Code Playgroud)

参考资料:不记得了。让我知道它是否最初是你的,我会归于它。

这为我提供了一个即席查询列表,这些查询的查询计划与现有的相同查询计划相同,以及计划缓存中唯一相同查询计划的总和。

包含唯一相同单一计划的结果集的屏幕截图

正如您从编辑后的 ​​GUID 中看到的那样,有许多已多次创建的独特的即席查询计划。


为了证明我正朝着正确的方向前进,我从上面获取了一个唯一计数为 3 的语句,并将该语句用作我的计划缓存摘要语句中的过滤器来检索语句和查询计划:

SELECT cplan.usecounts,
       qtext.text,
       qplan.query_plan
FROM   sys.dm_exec_cached_plans AS cplan
       CROSS APPLY sys.dm_exec_sql_text(plan_handle) AS qtext
       CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qplan
       JOIN sys.databases AS sdb
            ON  sdb.database_id = qplan.dbid
WHERE  1 = 1
       AND cplan.objtype = 'Adhoc'
       AND sdb.name = 'DATABASENAME'
       AND qtext.text = 
           'SELECT description,id,name,osguid,profil FROM omitted WHERE osguid IN (SELECT osgroupguid FROM omitted WHERE osuserguid=''81C4B8_REMOVED_SOME_9DD2'')'
ORDER BY
       1 DESC
Run Code Online (Sandbox Code Playgroud)

参考资料:不记得了。让我知道它是否最初是你的,我会归于它。

这为我提供了已创建并存储在计划缓存中的唯一临时查询列表:

包含唯一相同单一计划的结果集的屏幕截图

现在上面屏幕截图中的数字表明,一个查询已经再次被重用,因为它的计数为 3。但是,所有查询都是相同的。

现在,根据我目前所读到的内容,我假设:

  • 即席查询是在其(可能很短)生命周期中第一次传递给 SQL Server 查询优化器的查询
  • 没有参数的语句被认为是唯一的,并将导致在Adhoc计划缓存中创建一个条目
  • 即席查询可能是微不足道的,这会导致为每个语句创建单独的查询计划,即使它们是相同的

我同样意识到:

  • optimize for ad hoc workloads对于仅使用一次的即席计划,打开将导致缓存中查询计划的大小略有减少
  • ALTER DATABASE [DATABASENAME] SET PARAMETERIZATION FORCED就我而言,跑步可能是个好主意,但是……
    • 有限制(见 BrentOzar 的文章)
    • 程序中的参数化会更好

问题

在阅读了所有文章以及在键入此问题时弹出的一些相关问题后,我有以下两个问题:

  1. 在哪些情况下会重用非参数化、非平凡、即席查询计划?
  2. 为什么相同的语句有多个缓存的查询计划?

我意识到我的问题是矛盾的,因为非参数化查询计划被认为是唯一的,但为什么一些非参数化即席查询计划仍然被再次重用?


回应@DenisRubashikin 的评论:

以 XML 格式保存“相同”查询的计划并比较文件,我认为可能存在一些差异(例如,在设置选项中)– Denis Rubashkin 29 分钟前

SET 选项是相同的。整个计划的唯一区别是<StatementSetOptions>CompileTime和部分之后的第二行CompileCPU。我在下面粘贴了两个相关部分:

查询计划1.xml

      <StatementSetOptions QUOTED_IDENTIFIER="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" NUMERIC_ROUNDABORT="false" />
      <QueryPlan CachedPlanSize="32" CompileTime="4" CompileCPU="4" CompileMemory="472">
Run Code Online (Sandbox Code Playgroud)

查询计划2.xml

      <StatementSetOptions QUOTED_IDENTIFIER="true" ARITHABORT="false" CONCAT_NULL_YIELDS_NULL="true" ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" NUMERIC_ROUNDABORT="false" />
      <QueryPlan CachedPlanSize="32" CompileTime="3" CompileCPU="3" CompileMemory="472">
Run Code Online (Sandbox Code Playgroud)

没有发现其他差异。


用于策划此问题的参考资料:

Dav*_*oft 3

打开临时工作负载的优化将导致缓存中查询计划的大小略有减少

...

在计划缓存中的 158'997 个即席查询中,126'087 个仅执行了一次。

我不认为删除 79% 的 AdHoc 计划只是轻微减少。

在哪些情况下会重用非参数化、重要的即席查询计划?

当连接到同一数据库的客户端在缓存中运行计划后,运行具有相同会话设置的完全相同的查询时。

所以,

为什么相同的语句有多个缓存的查询计划?

通常,具有不同设置的会话会影响查询行为。查询中的任何文本差异(包括空格)都可能导致此问题。用户的默认架构可能会导致这种情况,因为对象名称解析不同。此外,几乎同时提交的两个相同的查询可能会得到独立的优化和缓存。