Каждая партия вызывает компиляцию

10

У нас есть стороннее приложение, которое отправляет операторы T-SQL партиями.

База данных размещается на SQL Server 2016 Enterprise SP1 CU7, 16 ядрах и 256 ГБ памяти. Оптимизация для 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'

Когда я наблюдаю за базой данных и смотрю пакеты / сек и компилирует / сек, я замечаю, что они всегда одинаковы. Под большой нагрузкой это может быть 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вызов вызывает компиляцию, но план запроса не кэшируется.

Что может быть причиной того, что количество пакетов в секунду равно числу компиляций в секунду?

Фредерик Вандерхаген
источник

Ответы:

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

Связанные вопросы и ответы: триггеры компилируются каждый раз?

Пол Уайт 9
источник
11

Вы можете аппроксимировать то, что вы видите в Performance Monitor и Activity Monitor для SQL Compilations/secи Batch 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

Если вы переключитесь обратно в Query Window 1, вы увидите что-то вроде этого:

╔═══════════════╦══════════════════════╦══════════ ══════════════╦════════════════════╗
La ElapsedTimeMS ║ Компиляции SQL / с ║ Перекомпиляции SQL / с ║ Пакетные запросы / с ║
╠═══════════════╬══════════════════════╬══════════ ══════════════╬════════════════════╣
20 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выполняется оператор. План для этого, безусловно, кэшируется, но мы видим компиляцию для него; что дает?

В Docs Microsoft сказать о sp_executesql:

Процедура sp_executesql имеет такое же поведение, что и EXECUTE, в отношении пакетов, области имен и контекста базы данных. Оператор или пакет Transact-SQL в параметре sp_executesql @stmt не компилируется, пока не будет выполнен оператор sp_executesql. Содержимое @stmt затем компилируется и выполняется как план выполнения, отдельный от плана выполнения пакета, который называется sp_executesql.

Таким образом, sp_executesql сама программа компилируется при каждом запуске, даже если план для текста команды уже находится в кэше планов. @PaulWhite показывает в своем ответе, что большинство вызовов sp_executesql фактически не кэшируются.

Макс Вернон
источник