每个批次都会导致编译

Fre*_*gen 10 performance sql-server sql-server-2016 query-performance

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

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

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

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'
Run Code Online (Sandbox Code Playgroud)

当我监视数据库并查看批处理/秒和编译/秒时,我注意到它们总是相同的。在重负载下,这可以是 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;
Run Code Online (Sandbox Code Playgroud)

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

就像每次sp_executesql调用都会触发编译,但查询计划没有被缓存。

batches/sec 等于 compiles/sec 的原因是什么?

Pau*_*ite 12

就像每次sp_executesql调用都会触发编译,但查询计划没有被缓存。

SQL Server不会缓存仅包含sp_executesql调用的批处理的查询计划。如果没有缓存计划,每次都会进行编译。这是设计使然,也是意料之中的。

SQL Server 避免缓存批处理,编译成本低。这些年来,缓存和不缓存的细节已经改变了很多次。有关详细信息,请参阅我对跟踪标志 2861 的回答以及“零成本”计划的实际含义

总之,复用的概率(包括具体的参数值)很小,编译包含sp_executesql调用的 ad hoc 文本的成本很小。由 产生的内部参数化批次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'
Run Code Online (Sandbox Code Playgroud)

多次运行该代码。然而,在第一次,许多汇编按预期报告。第二次,不报告编译,除非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%';
Run Code Online (Sandbox Code Playgroud)

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


Han*_*non 11

您可以近似在 Performance Monitor 和 Activity Monitor 中看到的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;
Run Code Online (Sandbox Code Playgroud)

在查询窗口 2 中,在运行上述代码时运行以下命令。代码简单地执行了 100 个 T-SQL 批处理:

EXEC sys.sp_executesql N'SELECT TOP(1) o.name FROM sys.objects o;';
GO 100
Run Code Online (Sandbox Code Playgroud)

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

?????????????????????????????????????????????????????? ???????????????????????????????????????
? 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)%'
Run Code Online (Sandbox Code Playgroud)

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

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

微软的文档说,这一下sp_executesql

sp_executesql 在批处理、名称范围和数据库上下文方面与 EXECUTE 具有相同的行为。sp_executesql @stmt 参数中的 Transact-SQL 语句或批处理在执行 sp_executesql 语句之前不会被编译。然后@stmt 的内容作为与调用sp_executesql 的批处理的执行计划分开的执行计划进行编译和执行。

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