Ogni batch provoca una compilazione


10

Abbiamo un'applicazione di terze parti che invia istruzioni T-SQL in batch.

Il database è ospitato su una SQL Server 2016 Enterprise SP1 CU7, 16 core e 256 GB di memoria. L'ottimizzazione per Ad-Hoc è abilitata.

Questo è un esempio fittizio delle query che vengono eseguite:

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'

Quando controllo il database e guardo batch / sec e compilazioni / sec, noto che sono sempre gli stessi. Sotto carico pesante, questo può essere 1000 lotti / sec e 1000 compilazioni / sec. Sotto carico medio, ci sono 150 lotti / sec.

Analizzo la cache delle query per i piani compilati di recente:

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;

Quando corro sopra la query vedo solo 10-20 nuovi piani di query / sec.

È come se ogni sp_executesqlchiamata innesca una compilazione ma il queryplan non viene memorizzato nella cache.

Quale può essere la causa di batch / sec pari a compilazioni / sec?

Risposte:


12

È come se ogni sp_executesqlchiamata innesca una compilazione ma il piano di query non viene memorizzato nella cache.

SQL Server non memorizza nella cache un piano di query per batch contenenti solo una sp_executesqlchiamata. Senza un piano memorizzato nella cache, ogni volta si verifica una compilazione. Questo è di progettazione e previsto.

SQL Server evita la memorizzazione nella cache di batch con costi di compilazione bassi. I dettagli di ciò che è e non è memorizzato nella cache sono cambiati molte volte nel corso degli anni. Vedi la mia risposta alla bandiera Trace 2861 e cosa significa in realtà un piano a costo zero per i dettagli.

In breve, la probabilità di riutilizzo (compresi i valori di parametri specifici) è piccola e il costo di compilazione del testo ad hoc contenente la sp_executesqlchiamata è molto piccolo. Il batch parametrico interno prodotto da sp_executesqlè ovviamente memorizzato nella cache e riutilizzato - questo è il valore di esso. Anche la sp_executesqlstessa stored procedure estesa viene memorizzata nella cache.

Per essere memorizzato nella cache e riutilizzato, la sp_executesqldichiarazione dovrebbe essere parte di un batch più grande che è considerato degno di essere memorizzato nella cache. Per esempio:

-- 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'

Esegui quel codice più volte. La prima volta, tuttavia, molte compilation vengono riportate come previsto. La seconda volta, non vengono segnalate compilazioni, a meno che non optimize for ad hoc workloadssia abilitato (quindi viene memorizzato nella cache solo uno Stub piano compilato ). Per la terza volta, in ogni caso non viene segnalata alcuna compilazione, poiché qualsiasi stub viene promosso a un piano ad hoc completamente memorizzato nella cache.

Rimuovere l' DECLARE @TCistruzione per vedere che l' sys.sp_executesqlistruzione non viene mai memorizzata nella cache senza di essa, indipendentemente dal numero di volte in cui viene eseguita.

Visualizza le voci della cache del piano associate con:

-- 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%';

Domande e risposte correlate: i trigger vengono compilati ogni volta?


11

È possibile approssimare ciò che si vede in Performance Monitor e Activity Monitor per SQL Compilations/sece Batch Requests/sec, durante l'esecuzione di alcuni batch in una finestra di query separata come test, come descritto di seguito.

Finestra query 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;

In Query Window 2, eseguire quanto segue mentre il codice sopra è in esecuzione. Il codice esegue semplicemente 100 batch T-SQL:

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

Se torni alla finestra delle query 1 vedrai qualcosa del genere:

╔═══════════════╦══════════════════════╦══════════ ══════════════╦════════════════════╗
║ ElapsedTimeMS ║ Compilazioni SQL / sec ║ Ricompilazioni SQL / sec ║ Richieste batch / sec ║
╠═══════════════╬══════════════════════╬══════════ ══════════════╬════════════════════╣
║ 10020,00 ║ 10,07984031000 ║ 0,00000000000 ║ 10,07984031000 ║
╚═══════════════╩══════════════════════╩══════════ ══════════════╩════════════════════╝

Se osserviamo questa query:

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)%'

Possiamo confermare che ci sono state 100 esecuzioni della query di test.

Nei risultati sopra, puoi vedere che stiamo ottenendo compilation ogni voltasp_executesql che viene eseguita l' istruzione. Il piano per questo è sicuramente in cache, eppure vediamo una compilation per questo; cosa dà?

I documenti Microsoft dicono questo su sp_executesql:

sp_executesql ha lo stesso comportamento di EXECUTE per quanto riguarda i batch, l'ambito dei nomi e il contesto del database. L'istruzione Transact-SQL o il batch nel parametro sp_executesql @stmt non vengono compilati fino a quando non viene eseguita l'istruzione sp_executesql. Il contenuto di @stmt viene quindi compilato ed eseguito come un piano di esecuzione separato dal piano di esecuzione del batch che ha chiamato sp_executesql.

Quindi, esso sp_executesql stesso viene compilato ogni volta che viene eseguito, anche se il piano per il testo del comando è già nella cache del piano. @PaulWhite mostra nella sua risposta che la maggior parte delle chiamate a sp_executesql non sono, di fatto, memorizzate nella cache.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.