每一批都会导致编译


10

我们有一个第三方应用程序,它可以批量发送T-SQL语句。

该数据库托管在SQL Server 2016 Enterprise SP1 CU7、16核和256GB内存上。启用即席优化。

这是正在执行的查询的虚拟示例:

exec sp_executesql N'
IF @@TRANCOUNT = 0 SET TRANSACTION ISOLATION LEVEL SNAPSHOT

select field1, field2 from table1 where field1=@1
option(keep plan, keepfixed, loop join)

select field3, field4 from table2 where field3=@1
option(keep plan, keepfixed, loop join)', N'@1 nvarchar(6)',@1=N'test'

当我监视数据库并查看批处理/秒和编译/秒时,我注意到它们始终相同。在高负载下,这可以是1000批/秒和1000编译/秒。在平均负载下,有150个批次/秒。

我分析了最近编译的计划的查询缓存:

SELECT TOP (1000) qs.creation_time
    , DatabaseName = DB_NAME(st.dbid)
    , qs.execution_count
    , st.text
    , qs.plan_handle
    , qs.sql_handle
    , qs.query_hash 
FROM sys.dm_exec_query_stats qs
    CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) AS st
ORDER BY creation_time DESC;

当我在上面的查询中运行时,我只会看到10-20个新的查询计划/秒。

就像每个sp_executesql调用都会触发编译,但不会缓存queryplan。

批处理/秒等于编译/秒的原因是什么?

Answers:


12

就像每个sp_executesql调用都会触发编译,但查询计划未缓存。

SQL Server 不会为仅包含一个sp_executesql调用的批次缓存查询计划。如果没有缓存的计划,则每次都会进行编译。这是设计使然,预期的。

SQL Server避免了以低成本编译批处理。多年来,缓存内容和未缓存内容的细节已发生了许多次更改。有关详细信息,请参见我对跟踪标志2861的回答以及“零成本”计划的实际含义

简而言之,重用的可能性(包括特定的参数值)很小,并且编译包含该sp_executesql调用的临时文本的成本也很小。产生的内部参数化批处理sp_executesql当然会被缓存和重用-这就是它的价值。扩展存储过程sp_executesql本身也被缓存。

要缓存和重用,该sp_executesql语句必须是一个较大的批处理的一部分,该批处理被认为值得缓存。例如:

-- Show compilation counter
SELECT
    DOPC.[object_name],
    DOPC.cntr_value
FROM sys.dm_os_performance_counters AS DOPC
WHERE
    DOPC.counter_name = N'SQL Compilations/sec'
GO
-- This is only here to make the batch worth caching
DECLARE @TC integer =
(
    SELECT TOP (1) @@TRANCOUNT 
    FROM master.dbo.spt_values AS SV
);

-- Example call we are testing
-- (use anything for the inner query, this example uses the Stack Overflow database
EXECUTE sys.sp_executesql 
    N'SELECT LT.Type FROM dbo.LinkTypes AS LT WHERE LT.Id = @id;', 
    N'@id int', 
    @id = 1;
GO
-- Show compilation counter again
SELECT
    DOPC.[object_name],
    DOPC.cntr_value
FROM sys.dm_os_performance_counters AS DOPC
WHERE
    DOPC.counter_name = N'SQL Compilations/sec'

多次运行该代码。但是,第一次,许多汇编都按预期报告。第二次没有报告任何编译,除非optimize for ad hoc workloads已启用(因此仅缓存了一个已编译的计划存根)。第三次,在任何情况下都不会报告编译,因为任何存根都被提升为完全缓存的临时计划。

删除该DECLARE @TC语句以查看没有该sys.sp_executesql语句就永远不会被缓存,而不管它执行了多少次。

通过以下方式查看关联的计划缓存条目:

-- Show cached plans
SELECT
    DECP.refcounts,
    DECP.usecounts,
    DECP.size_in_bytes,
    DECP.cacheobjtype,
    DECP.objtype,
    DECP.plan_handle,
    DECP.parent_plan_handle,
    DEST.[text]
FROM sys.dm_exec_cached_plans AS DECP
CROSS APPLY sys.dm_exec_sql_text(DECP.plan_handle) AS DEST
WHERE 
    DEST.[text] LIKE N'%sp_executesql%'
    AND DEST.[text] NOT LIKE N'%dm_exec_cached_plans%';

相关问答:触发器是否每次都编译?


11

你可以接近你在性能监视器和活动监视器看到SQL Compilations/secBatch Requests/sec,在运行在单独的查询窗口,部分批次的测试,具体如下。

查询窗口1:

DECLARE @t1 datetime;
DECLARE @t2 datetime;
DECLARE @CompVal1 int;
DECLARE @CompVal2 int;
DECLARE @ReCompVal1 int;
DECLARE @ReCompVal2 int;
DECLARE @BatchVal1 int;
DECLARE @BatchVal2 int;
DECLARE @ElapsedMS decimal(10,2);

SELECT @t1 = GETDATE()
    , @CompVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Compilations/sec                                                                                                            '
        )
    , @ReCompVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Re-Compilations/sec                                                                                                         '
        )
    , @BatchVal1 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'Batch Requests/sec                                                                                                              '
        );

WAITFOR DELAY '00:00:10.000';

SELECT @t2 = GETDATE()
    , @CompVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Compilations/sec                                                                                                            '
        )
    , @ReCompVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'SQL Re-Compilations/sec                                                                                                         '
        )
    , @BatchVal2 = (
        SELECT spi.cntr_value
        FROM sys.sysperfinfo spi
        WHERE spi.counter_name = 'Batch Requests/sec                                                                                                              '
        );

SET @ElapsedMS = DATEDIFF(MILLISECOND, @t1, @t2);
SELECT  ElapsedTimeMS = @ElapsedMS
    , [SQL Compilations/sec] = (@CompVal2 - @CompVal1) / @ElapsedMS * 1000 
    , [SQL Recompilations/sec] = (@ReCompVal2 - @ReCompVal1) / @ElapsedMS * 1000
    , [Batch Requests/sec] = (@BatchVal2 - @BatchVal1) / @ElapsedMS * 1000;

在上面的代码运行时,在查询窗口2中,运行以下命令。该代码仅执行100个T-SQL批处理:

EXEC sys.sp_executesql N'SELECT TOP(1) o.name FROM sys.objects o;';
GO 100

如果切换回查询窗口1,您将看到类似以下内容:

╔═══════════════╦══════════════════════╦══════════ ══════════════╦════════════════════╗
lapse ElapsedTimeMS║SQL编译/秒║SQL重新编译/秒║批处理请求/秒║
╠═══════════════╬══════════════════════╬══════════ ══════════════╬════════════════════╣
║10020.00║10.07984031000║0.00000000000║10.07984031000║
╚═══════════════牛皮══════════════════════牛皮══════════ ══════════════牛皮════════════════════╝

如果我们看这个查询:

SELECT dest.text
    , deqs.execution_count
FROM sys.dm_exec_query_stats deqs
    CROSS APPLY sys.dm_exec_sql_text(deqs.plan_handle) dest
WHERE dest.text LIKE 'SELECT TOP(1)%'

我们可以确认测试查询有100次执行。

在上面的结果,你可以看到我们正在编译每一次sp_executesql语句执行。当然,该计划已被缓存,但是我们看到了它的编译结果。是什么赋予了?

微软的文档说,这一下sp_executesql

对于批处理,名称范围和数据库上下文,sp_executesql具有与EXECUTE相同的行为。在执行sp_executesql语句之前,不会编译sp_executesql @stmt参数中的Transact-SQL语句或批处理。然后,将@stmt的内容编译并作为一个执行计划,与名为sp_executesql的批处理的执行计划分开执行。

因此,即使命令高速缓存中已经存在命令文本的计划,sp_executesql 也会在每次运行时进行编译。@PaulWhite在他的回答中表明,实际上对sp_executesql的大多数调用都没有缓存。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.