重用执行计划以针对具有不同 WHERE 子句的视图进行查询

Reuse execution plan for query against view with different WHERE clauses

我(由于有点奇怪的原因)最终得到了一个视图,编译需要大约 30 秒,但 运行.

不到 3 秒

这是一个位于大量嵌套视图顶部的视图,每个视图都有许多层的顺序 CTE。基础数据集并没有那么大,我想这就是查询最终 运行 非常快的原因。

如果我们总是阅读整个 table,那会很好 - 第一个查询会很慢,而以后的每个查询都会很好。 不幸的是,访问代码将要用日期 window.

读取它

SELECT * FROM myView WHERE date BETWEEN 'foo' AND 'bar'

第一次 运行 编译执行计划需要 30 秒;第二次是 1-3 秒后 运行s。

有什么办法可以防止重新编译吗? 我认识到这可能会导致最终的执行计划效率不高,因为它针对不同的子句进行了优化,但数据非常统一,所以我不希望它太糟糕,而且 30 秒的编译时间是方式太痛了。


我浏览了 these pages 这样的页面。但没有多少内容立即跳出来与我相关,而且似乎没有实现我的目标(尽管我很容易错过一些东西)


编辑:

STATISTICS TIME ON 类似视图的输出,在冷时需要 ~ 6 到 运行,或者在预编译时需要大约 1/2 秒到 运行(实际上,具体来说,它来自嵌套中下一层的其中一个视图。)

SELECT * FROM myView WHERE date_incurred < '2017-02-20'

Run with param = '2017-02-20'

SQL Server parse and compile time: CPU time = 5008 ms, elapsed time = 5184 ms.
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
(77 row(s) affected)
SQL Server Execution Times: CPU time = 452 ms,  elapsed time = 772 ms.

Run with param = '2017-02-20'

SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
(77 row(s) affected)
SQL Server Execution Times: CPU time = 437 ms,  elapsed time = 582 ms.

Run with param = '2017-02-21'

SQL Server parse and compile time: CPU time = 4618 ms, elapsed time = 4877 ms.
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
(79 row(s) affected)
SQL Server Execution Times: CPU time = 359 ms,  elapsed time = 643 ms.

Run with param = '2017-02-21'

SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms.
(79 row(s) affected)
SQL Server Execution Times: CPU time = 483 ms,  elapsed time = 559 ms.

一个可能的答案:非内联函数

执行计划缓存将使用第一次调用时的参数,然后继续使用该计划。

缺点 - 定义包装视图的函数非常冗长:( - 需要针对视图的每个不同 "type" 查询使用单独的函数(或具有大量参数的复杂函数,其中许多参数为 NULL)。

另一个可能的答案:使用存储过程

您再次获得执行计划缓存。 这次您不必像内联 table 值函数一样进行冗长的 table 定义,但是从 Sproc 检索输出不一定像 SELECT 或函数。

最佳答案 (IMO):sp_executesql/sp_execute

DECLARE @query int;  
EXEC sp_executesql
    N'SELECT * FROM vlpl_combined_costs_with_invoices WHERE date_incurred < @dateParam OPTION(OPTIMIZE FOR UNKNOWN)',
    N'@dateParam datetime',
    '2017-09-10'


EXEC sp_prepare @query output,   
    N'@dateParam datetime',  
    N'SELECT * FROM myView WHERE date < @dateParam OPTION(OPTIMIZE FOR UNKNOWN)';
EXEC sp_execute @query, '2017-09-07'
EXEC sp_unprepare @query;

我认为前者 (sp_executesql) 被认为更好,尽管我不知道具体原因。


解释:

在生成存储过程的执行计划时,是根据当时传递的参数生成的,然后重复使用相同的计划进行后续的执行不考虑参数值的存储过程——只需名称必须匹配[1]。

ad hoc SQL 批处理(即常规硬编码 SELECT)是 运行 时,它的执行计划也会被存储, 但为了使用查询的文本必须完全匹配(包括空格和大小写以及参数值)。

sp_execute 允许临时查询的查询计划独立于参数值存储,以便它可以跨不同的参数集重用(我认为文本的其余部分仍然必须相同)。也就是说,它是为我在这里得到的确切用例而设计的。

(想法和解释归功于 Andy Thomas 作为 Softwire)

[1] 和其他一些我相信的东西,但这与这里无关。