除了显式刷新缓存或要求重新编译之外,什么会重新编译 SQL Server 2019 中的完整存储过程?

J. *_*ini 0 stored-procedures t-sql execution-plan recompile sql-server-2019

我最近读完 《SQL Server 2008 中的计划缓存》 ,我感到很困惑。看起来,除了完全刷新计划缓存或明确要求重新编译存储过程之外,从 SQL Server 2008 开始,存储过程的重新编译都是在语句级别而不是存储过程级别完成的。

那么,除了显式刷新缓存或要求重新编译(例如WITH RECOMPILE)之外,什么可以在 SQL Server 2019 中重新编译完整的存储过程,而不仅仅是重新编译单个语句呢?

举一个我感到困惑的例子,请考虑以下过程。

CREATE PROCEDURE FOO AS
BEGIN
   SELECT * INTO #temp1 FROM table1
   INSERT BAR1 SELECT * FROM #temp1
   INSERT BAR2 SELECT * FROM #temp1
END
Run Code Online (Sandbox Code Playgroud)

我可以想到很多可能导致SELECT * INTO #temp1 FROM table1重新编译的事情,但是如果没有下一行也重新编译,那么重新编译会很奇怪。这让我觉得 SQL Server 中一定有一些东西会导致整个存储过程重新编译。

Eri*_*ing 5

不多

\n

除了您的问题中列出的内容之外,存储过程确实没有常见的通用原因总是为每个语句重新编译新计划,至少对于您的示例中的语句来说是这样。

\n

SQL Server 提供了几种不同的方法来跟踪重新编译,但它们都是语句级的。也许最容易获得答案的是扩展事件。我遇到过在对象级别没有跟踪重新编译的事件。

\n
SELECT\n     d.map_value\nFROM sys.dm_xe_map_values AS d\nWHERE d.name = \'statement_recompile_cause\'\nORDER BY\n    d.map_key;\n
Run Code Online (Sandbox Code Playgroud)\n

截至 SQL Server 2022,这列出了 23 个重新编译的原因,其中包括“不是重新编译”。

\n
    \n
  • 架构已更改
  • \n
  • 统计数据发生变化
  • \n
  • 延迟编译
  • \n
  • 设置选项更改
  • \n
  • 临时表已更改
  • \n
  • 远程行集已更改
  • \n
  • 对于浏览权限已更改
  • \n
  • 查询通知环境已更改
  • \n
  • 分区视图已更改
  • \n
  • 光标选项已更改
  • \n
  • 请求选项(重新编译)
  • \n
  • 参数化计划已刷新
  • \n
  • 测试计划线性化
  • \n
  • 影响数据库版本的计划已更改
  • \n
  • 查询存储计划强制策略已更改
  • \n
  • 查询存储计划强制失败
  • \n
  • 查询存储缺少计划
  • \n
  • 交错执行需要重新编译
  • \n
  • 不是重新编译
  • \n
  • 查询存储提示已更改
  • \n
  • 查询存储提示应用程序失败
  • \n
  • 查询存储重新编译以捕获游标查询
  • \n
  • 重新编译以清理多计划调度程序计划
  • \n
\n

然而

\n

我知道有几件事会阻止计划缓存,这会使模块看起来每次都需要重新编译(尽管实际上我觉得这更像是每次都进行编译)。

\n
    \n
  1. 对称密钥
  2. \n
\n

正如保罗在他的帖子中指出的那样:

\n
\n

第二个选项是包含一条将整个批次标记为不可缓存的语句。合适的语句通常与安全相关,或者以某种方式敏感。

\n

这可能听起来不切实际,但有一些缓解措施。\n首先,不需要执行敏感语句\xe2\x80\x94,它只需要\n存在即可。当满足该条件时,运行批处理的用户\n\n\xe2\x80\x99t甚至需要执行敏感语句的权限。请注意,效果仅限于包含敏感语句的批处理。

\n
\n

请点击链接查看几个示例。

\n
    \n
  1. 不存在的物体
  2. \n
\n

我还没有发表过关于此的文章,但总体思路是,当存在未经探索的情况时IF存储过程中存在未探索的分支引用不存在的对象时,整个批次是不可缓存的。

\n

请随意跟随下面的演示。请注意,如果您尝试获取如下所示的存储过程的估计计划,或者如果您以探索具有不存在对象的分支的方式运行它,它将引发错误。

\n
/*Create a table if you need to*/\nCREATE TABLE \n    dbo.DinnerPlans\n(\n    id bigint IDENTITY,\n    name nvarchar(40) NOT NULL,\n    seat_number tinyint NULL,\n    is_free bit NOT NULL,\n);\nGO\n\n/*First example, with an object that doesn\'t exist*/\nCREATE OR ALTER PROCEDURE\n    dbo.i_live\n(\n    @decider bit = NULL\n)\nAS\nBEGIN\n    SET NOCOUNT, XACT_ABORT ON;\n    \n    IF @decider = \'true\'\n    BEGIN\n        SELECT\n            dp.*\n        FROM dbo.DinnerPlans AS dp;\n    END;\n    \n    IF @decider = \'false\'\n    BEGIN\n        SELECT\n            whatever.*\n        FROM dbo.AnObjectThatDoesntEvenPretendToExist AS whatever;\n    END;\n    \n    IF @decider IS NULL\n    BEGIN\n        SELECT\n            result = \n                \'please make a decision.\'\n    END;\nEND;\nGO \n\n/*Say goodbye!*/\nDBCC FREEPROCCACHE;\n\n/*This runs without an error*/\nEXEC dbo.i_live \n    @decider = \'true\';\n\n/*But there\'s no query plan!*/\nSELECT\n    object_name =\n       OBJECT_NAME(deps.object_id, deps.database_id),   \n    deps.type_desc,\n    deps.last_execution_time,\n    deps.execution_count,\n    dest.text,\n    query_plan =\n        TRY_CAST(detqp.query_plan AS xml)\nFROM sys.dm_exec_procedure_stats AS deps\nOUTER APPLY sys.dm_exec_sql_text(deps.plan_handle) AS dest\nOUTER APPLY sys.dm_exec_text_query_plan(deps.plan_handle, 0, -1) AS detqp;\nGO \n
Run Code Online (Sandbox Code Playgroud)\n