临时表和常量语句重新编译
Temporary tables and constant statement recompilation
我正在对一个在动态 SQL 中使用临时 table 的系统进行压力测试。 table 是在事务的早期创建的,由多个存储过程中的多个动态 SQL 语句填充,这些存储过程使用以下形式的语句作为批处理的一部分执行:
INSERT #MyTable (...)
SELECT ...
其中 SELECT
语句相当复杂,因为它可能包含 UNION ALL
和 UNPIVOT
语句并引用多个 UDF。使用 sp_executesql
执行所有字符串并启用参数嗅探。
我注意到在负载下,我看到很多 RESOURCE_SEMAPHORE_QUERY_COMPILE 等待,其中存在正在重新编译的查询文本,并且 相同 同时在多个等待中并出现在整个压力测试中,持续约 5 分钟。服务器上的内存消耗通常在 60% 左右,并且 SQL 服务器可以消耗多少没有限制。限制因素似乎是 CPU,在测试期间始终保持 >95%。
我在测试期间分析了服务器以观察 SQL:StmtRecompile 事件,突出显示重新编译的原因是:
5 - Temp table changed
但是 temp table 每次都是相同的,并且一旦创建 table 就没有对它执行 DDL 语句,除了在批处理结束时删除它.
到目前为止,我已经尝试过:
- 启用 "optimize for ad hoc workloads" 选项
OPTION(KEEPFIXED PLAN)
- 将动态语句更改为仅
SELECT
然后使用 INSERT ... EXEC
因此临时 table 不在执行的字符串中
所有这些都没有改变,等待仍然存在。
为什么 SQL 认为每次执行时都需要重新编译这些相同的查询,我怎样才能让它保留和重用它正在创建的缓存计划?
注意:我无法将临时 table 更改为内存中 table,因为有时使用它的存储过程可能必须查询同一实例上的另一个数据库。
这是使用 SQL Server 2016 SP1 CU7。
似乎删除动态 SQL 字符串中临时 table 的插入可以显着提高性能。例如,更改此:
EXEC sp_executesql
N'INSERT #tempTable (...) SELECT ... FROM ...'
其中 SELECT
语句不平凡,为此:
INSERT #tempTable (...)
EXEC sp_executesql
N'SELECT ... FROM ...'
大大减少了编译期间创建的块数。不幸的是,查询的重新编译并没有避免,只是重新编译的查询现在变得更加简单,因此 CPU 密集程度降低了。
我还发现创建一个与临时 table 具有相同列的内存中 Table 类型,执行复杂的插入 table 变量的性能更高该类型并在最后执行从 table 变量到临时 table 的单个插入。
我正在对一个在动态 SQL 中使用临时 table 的系统进行压力测试。 table 是在事务的早期创建的,由多个存储过程中的多个动态 SQL 语句填充,这些存储过程使用以下形式的语句作为批处理的一部分执行:
INSERT #MyTable (...)
SELECT ...
其中 SELECT
语句相当复杂,因为它可能包含 UNION ALL
和 UNPIVOT
语句并引用多个 UDF。使用 sp_executesql
执行所有字符串并启用参数嗅探。
我注意到在负载下,我看到很多 RESOURCE_SEMAPHORE_QUERY_COMPILE 等待,其中存在正在重新编译的查询文本,并且 相同 同时在多个等待中并出现在整个压力测试中,持续约 5 分钟。服务器上的内存消耗通常在 60% 左右,并且 SQL 服务器可以消耗多少没有限制。限制因素似乎是 CPU,在测试期间始终保持 >95%。
我在测试期间分析了服务器以观察 SQL:StmtRecompile 事件,突出显示重新编译的原因是:
5 - Temp table changed
但是 temp table 每次都是相同的,并且一旦创建 table 就没有对它执行 DDL 语句,除了在批处理结束时删除它.
到目前为止,我已经尝试过:
- 启用 "optimize for ad hoc workloads" 选项
OPTION(KEEPFIXED PLAN)
- 将动态语句更改为仅
SELECT
然后使用INSERT ... EXEC
因此临时 table 不在执行的字符串中
所有这些都没有改变,等待仍然存在。
为什么 SQL 认为每次执行时都需要重新编译这些相同的查询,我怎样才能让它保留和重用它正在创建的缓存计划?
注意:我无法将临时 table 更改为内存中 table,因为有时使用它的存储过程可能必须查询同一实例上的另一个数据库。
这是使用 SQL Server 2016 SP1 CU7。
似乎删除动态 SQL 字符串中临时 table 的插入可以显着提高性能。例如,更改此:
EXEC sp_executesql
N'INSERT #tempTable (...) SELECT ... FROM ...'
其中 SELECT
语句不平凡,为此:
INSERT #tempTable (...)
EXEC sp_executesql
N'SELECT ... FROM ...'
大大减少了编译期间创建的块数。不幸的是,查询的重新编译并没有避免,只是重新编译的查询现在变得更加简单,因此 CPU 密集程度降低了。
我还发现创建一个与临时 table 具有相同列的内存中 Table 类型,执行复杂的插入 table 变量的性能更高该类型并在最后执行从 table 变量到临时 table 的单个插入。