为通过 sp_executesql 调用的查询创建计划指南

db2*_*db2 5 sql-server sql-server-2012 plan-guides

长话短说,我有一个名为 vwRelatives 的视图,它使用 CTE 递归来构建家谱。它旨在一次查询一个人。

这在大约四分之一秒内运行:

SELECT * FROM vwRelatives WHERE person_id = 5
Run Code Online (Sandbox Code Playgroud)

这(从应用程序执行查询的方式)大约需要 4.5 秒:

exec sp_executesql N'SELECT * FROM vwRelatives WHERE person_id = @P1',N'@P1 int',5
Run Code Online (Sandbox Code Playgroud)

(请注意,我已经稍微简化了查询。真实的东西有一个显式的列列表和一个ORDER BY,但WHERE语义是相同的。我在任何一个版本中都得到了相同的症状。)

最有可能的是,SQL Serverperson_id = 5在为第一个查询创建执行计划时能够将其考虑在内,但对其进行参数化会导致运行整个视图,然后按 person_id进行过滤。

所以我想我会创建一个计划指南。现在我有两个问题。

这些是我正在采取的步骤,但似乎没有任何效果。

首先,运行“好”查询以将其放入计划缓存...

SELECT * FROM vwRelatives WHERE person_id = 5
Run Code Online (Sandbox Code Playgroud)

...然后执行标准步骤将其变成计划指南...

--Get the 'good' plan
SET @xml_showplan = (
    SELECT query_plan
    FROM sys.dm_exec_query_stats AS qs 
        CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
        CROSS APPLY sys.dm_exec_text_query_plan(qs.plan_handle, DEFAULT, DEFAULT) AS qp
        WHERE st.text LIKE N'SELECT * FROM vwRelatives WHERE person_id = 5'
)

--Apply a plan guide to the meat of the sp_executesql query
EXEC sp_create_plan_guide 
    @name = N'vwRelatives_Test_Plan_Guide', 
    @stmt = N'SELECT * FROM vwRelatives WHERE person_id = @P1', 
    @type = N'SQL',
    @module_or_batch = NULL, 
    @params = N'@P1 int', 
    @hints = @xml_showplan;
Run Code Online (Sandbox Code Playgroud)

这成功完成,但是当我再次运行原始 sp_executesql 语句时,仍然需要 4.5 秒。我运行了 Profiler,并且选择了计划指南成功和计划指南失败事件。这些事件都没有出现在跟踪中。

我做错了什么导致 SQL Server 无法将此计划指南视为 sp_executesql 查询的匹配项?

db2*_*db2 5

对于那些在尝试创建计划指南时遇到这个问题的人,我最初使用的语法正确的。它不起作用的原因(我怀疑 - 我在文档中找不到任何确认)是该视图使用 CTE 进行递归。显然,这使其无法使用计划指南。

我最初的问题是,当SELECT通过带有参数的 sp_executesql 发出语句(客户端是 Access 数据库)时,递归视图的性能很差。

我终于在 Stack Overflow 上遇到了这个老问题,其中有人遇到了基本相同的问题:

/sf/ask/295822481/

我有点开始怀疑我是否需要通过将递归推送到用户定义的函数来欺骗/帮助查询优化器,这证实了怀疑。我将视图中的所有 CTE 移动到内联 UDF 中,该 UDF@person_id直接在递归的锚点中使用参数,现在即使使用 sp_executesql 也非常快。

所以,不是我最初认为我需要的解决方案,但我会接受它。(这种方式也可能更直接。我不必担心将计划指南附加到 Access 可能构造的查询的每个细微变化中。)