Database
 sql >> Base de Dados >  >> RDS >> Database

Parametrização Simples e Planos Triviais - Parte 3

Planos de execução


É mais complicado do que você imagina a partir das informações fornecidas nos planos de execução se uma instrução SQL usa parametrização simples . Não é surpresa que mesmo usuários altamente experientes do SQL Server tendam a errar, dadas as informações contraditórias que muitas vezes nos são fornecidas.



Vejamos alguns exemplos usando o banco de dados Stack Overflow 2010 no SQL Server 2019 CU 14, com compatibilidade de banco de dados definida como 150.

Para começar, precisaremos de um novo índice não clusterizado:
CREATE INDEX [IX dbo.Users Reputation (DisplayName)] 
ON dbo.Users (Reputation) 
INCLUDE (DisplayName);

1. Parametrização Simples Aplicada


Este primeiro exemplo de consulta usa parametrização simples :
SELECT U.DisplayName 
FROM dbo.Users AS U 
WHERE U.Reputation = 999;

A estimativa (pré-execução) tem os seguintes elementos relacionados à parametrização:

Propriedades de parametrização do plano estimado

Observe o @1 O parâmetro é introduzido em todos os lugares, exceto no texto da consulta mostrado na parte superior.

O real (pós-execução) plano tem:

Propriedades de parametrização do plano real

Observe que a janela de propriedades agora perdeu o ParameterizedText elemento, enquanto obtém informações sobre o valor do tempo de execução do parâmetro. O texto da consulta parametrizada agora é mostrado na parte superior da janela com ‘@1 ' em vez de '999'.

2. Parametrização Simples Não Aplicada


Este segundo exemplo não use parametrização simples:
-- Projecting an extra column
SELECT 
    U.DisplayName, 
    U.CreationDate -- NEW
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;

A estimativa plano mostra:

Plano não parametrizado estimado

Desta vez, o parâmetro @1 está faltando na Busca de Índice dica de ferramenta, mas o texto parametrizado e outros elementos da lista de parâmetros são os mesmos de antes.

Vejamos o real plano de execução:

Plano real não parametrizado

Os resultados são os mesmos do real parametrizado anterior plano, exceto agora o Index Seek dica de ferramenta exibe o valor não parametrizado '999'. O texto da consulta mostrado na parte superior usa o @1 marcador de parâmetro. A janela de propriedades também usa @1 e exibe o valor de tempo de execução do parâmetro.

A consulta não é uma instrução parametrizada apesar de todas as provas em contrário.

3. Falha na parametrização


Meu terceiro exemplo também é não parametrizado pelo servidor:
-- LOWER function used
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;

A estimativa plano é:

Falha na parametrização do plano estimado

Não há menção a um @1 parâmetro em qualquer lugar agora, e a Lista de Parâmetros está faltando uma seção da janela de propriedades.

O real plano de execução é o mesmo, então não vou me incomodar em mostrá-lo.

4. Plano Parametrizado Paralelo


Quero mostrar mais um exemplo usando paralelismo no plano de execução. O baixo custo estimado das minhas consultas de teste significa que precisamos reduzir o limite de custo para paralelismo para 1:
EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 1;
RECONFIGURE;

O exemplo é um pouco mais complexo desta vez:
SELECT 
    U.DisplayName 
FROM dbo.Users AS U 
WHERE 
    U.Reputation >= 5 
    AND U.DisplayName > N'ZZZ' 
ORDER BY 
    U.Reputation DESC;

A estimativa plano de execução é:

Plano parametrizado paralelo estimado

O texto da consulta na parte superior permanece sem parâmetros enquanto todo o resto é. Existem dois marcadores de parâmetro agora, @1 e @2 , porque a parametrização simples encontrou dois valores literais adequados.

O real plano de execução segue o padrão do exemplo 1:

Plano parametrizado paralelo real

O texto da consulta na parte superior agora está parametrizado e a janela de propriedades contém valores de parâmetro de tempo de execução. Este plano paralelo (com a Classificar operador) é definitivamente parametrizado pelo servidor usando parametrização simples .

Métodos confiáveis


Há razões para todos os comportamentos mostrados até agora, e mais alguns. Tentarei explicar muitos deles na próxima parte desta série, quando abordar a compilação do plano.

Enquanto isso, a situação com o showplan em geral, e o SSMS em particular, está abaixo do ideal. É confuso para as pessoas que trabalharam com o SQL Server durante toda a carreira. Em quais marcadores de parâmetro você confia e quais você ignora?

Existem vários métodos confiáveis ​​para determinar se uma determinada instrução teve uma parametrização simples aplicada com sucesso ou não.

Repositório de consultas


Vou começar com um dos mais convenientes, o repositório de consultas. Infelizmente, nem sempre é tão simples quanto você imagina.

Você deve habilitar o recurso de armazenamento de consultas para o contexto do banco de dados onde a instrução é executada e o OPERATION_MODE deve ser definido como READ_WRITE , permitindo que o repositório de consultas colete dados ativamente.

Depois de atender a essas condições, a saída do plano de exibição pós-execução contém atributos extras, incluindo o StatementParameterizationType . Como o nome sugere, contém um código que descreve o tipo de parametrização usado para a instrução.

É visível na janela de propriedades do SSMS quando o nó raiz de um plano é selecionado:

StatementParameterizationType

Os valores estão documentados em sys.query_store_query :
  • 0 – Nenhum
  • 1 – Usuário (parametrização explícita)
  • 2 – Parametrização simples
  • 3 – Parametrização forçada

Este atributo benéfico só aparece no SSMS quando um real plano é solicitado e falta quando um estimado plano é selecionado. É importante lembrar que o plano deve ser armazenado em cache . Solicitando uma estimativa plano do SSMS não armazena em cache o plano produzido (desde o SQL Server 2012).

Depois que o plano é armazenado em cache, o StatementParameterizationType aparece nos locais habituais, inclusive via sys.dm_exec_query_plan .

Você também pode confiar que o tipo de parametrização de outros lugares é registrado no repositório de consultas, como o query_parameterization_type_desc coluna em sys.query_store_query .

Uma ressalva importante. Quando a consulta armazena OPERATION_MODE está definido como READ_ONLY , o StatementParameterizationType atributo ainda está preenchido no SSMS real planos, mas é sempre zero — dando uma falsa impressão de que a declaração não foi parametrizada quando bem poderia ter sido.

Se você estiver feliz em habilitar o armazenamento de consultas, tiver certeza de que é leitura-gravação e observar apenas os planos de pós-execução no SSMS, isso funcionará para você.

Predicados de plano padrão


O texto da consulta mostrado na parte superior da janela do plano de exibição gráfico no SSMS não é confiável, como os exemplos mostraram. Você também não pode confiar na ParameterList exibido nas Propriedades janela quando o nó raiz do plano é selecionado. O Texto parametrizado atributo mostrado para estimado planos apenas também não é conclusivo.

Você pode, no entanto, confiar nas propriedades associadas a operadores de planos individuais. Os exemplos fornecidos mostram que eles estão presentes nas dicas ao passar o mouse sobre um operador.

Um predicado contendo um marcador de parâmetro como @1 ou @2 indica um plano parametrizado. Os operadores com maior probabilidade de conter um parâmetro são Index Scan , Pesquisa de índice e Filtrar .

Predicados com marcadores de parâmetro

Se a numeração começar com @1 , ele usa parametrização simples . A parametrização forçada começa com @0 . Devo mencionar que o esquema de numeração documentado aqui está sujeito a alterações a qualquer momento:

Alterar aviso

No entanto, este é o método que uso na maioria das vezes para determinar se um plano estava sujeito à parametrização do lado do servidor. Geralmente é rápido e fácil verificar visualmente um plano para predicados contendo marcadores de parâmetros. Este método também funciona para os dois tipos de planos, estimados e real .

Objetos de gerenciamento dinâmico


Existem várias maneiras de consultar o cache do plano e os DMOs relacionados para determinar se uma instrução foi parametrizada. Naturalmente, essas consultas funcionam apenas em planos em cache, portanto, a instrução deve ter sido executada até a conclusão, armazenada em cache e não posteriormente despejada por qualquer motivo.

A abordagem mais direta é procurar um Adhoc planejar usando uma correspondência textual SQL exata para a declaração de interesse. O Adhoc plano será um shell contendo um ParameterizedPlanHandle se a instrução for parametrizada pelo servidor. O identificador do plano é usado para localizar o Preparado plano. Um Adhoc O plano não existirá se a otimização para cargas de trabalho ad hoc estiver habilitada e a instrução em questão tiver sido executada apenas uma vez.

Esse tipo de consulta geralmente acaba destruindo uma quantidade significativa de XML e verificando todo o cache do plano pelo menos uma vez. Também é fácil errar o código, principalmente porque os planos em cache cobrem um lote inteiro. Um lote pode conter várias instruções, cada uma das quais pode ou não ser parametrizada. Nem todos os DMOs funcionam com a mesma granularidade (lote ou instrução), o que facilita bastante o desbloqueio.

Uma maneira eficiente de listar declarações de interesse, juntamente com fragmentos de plano apenas para essas declarações individuais, é mostrada abaixo:
SELECT
    StatementText =
        SUBSTRING(T.[text], 
            1 + (QS.statement_start_offset / 2), 
            1 + ((QS.statement_end_offset - 
                QS.statement_start_offset) / 2)),
    IsParameterized = 
        IIF(T.[text] LIKE N'(%',
            'Yes',
            'No'),
    query_plan = 
        TRY_CONVERT(xml, P.query_plan)
FROM sys.dm_exec_query_stats AS QS
CROSS APPLY sys.dm_exec_sql_text (QS.[sql_handle]) AS T
CROSS APPLY sys.dm_exec_text_query_plan (
    QS.plan_handle, 
    QS.statement_start_offset, 
    QS.statement_end_offset) AS P
WHERE 
    -- Statements of interest
    T.[text] LIKE N'%DisplayName%Users%'
    -- Exclude queries like this one
    AND T.[text] NOT LIKE N'%sys.dm%'
ORDER BY
    QS.last_execution_time ASC,
    QS.statement_start_offset ASC;

Para ilustrar, vamos executar um único lote contendo os quatro exemplos anteriores:
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Example 1
SELECT U.DisplayName 
FROM dbo.Users AS U 
WHERE U.Reputation = 999;
 
-- Example 2
SELECT 
    U.DisplayName, 
    U.CreationDate 
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
 
-- Example 3
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
 
-- Example 4
SELECT 
    U.DisplayName 
FROM dbo.Users AS U 
WHERE 
    U.Reputation >= 5 
    AND U.DisplayName > N'ZZZ' 
ORDER BY 
    U.Reputation DESC;
GO

A saída da consulta DMO é:

Saída da consulta DMO

Isso confirma que apenas os exemplos 1 e 4 foram parametrizados com sucesso.

Contadores de desempenho


É possível usar os contadores de desempenho do SQL Statistics para obter uma visão detalhada da atividade de parametrização para ambos estimados e real planos. Os contadores usados ​​não têm escopo por sessão, portanto, você precisará usar uma instância de teste sem outra atividade simultânea para obter resultados precisos.

Vou complementar as informações do contador de parametrização com dados do sys.dm_exec_query_optimizer_info DMO para fornecer estatísticas sobre planos triviais também.

Alguns cuidados são necessários para evitar que as instruções que lêem as informações do contador modifiquem esses próprios contadores. Vou resolver isso criando alguns procedimentos armazenados temporários:
CREATE PROCEDURE #TrivialPlans
AS
SET NOCOUNT ON;
 
SELECT
    OI.[counter],
    OI.occurrence
FROM sys.dm_exec_query_optimizer_info AS OI
WHERE
    OI.[counter] = N'trivial plan';
GO
CREATE PROCEDURE #PerfCounters
AS
SET NOCOUNT ON;
 
SELECT
    PC.[object_name],
    PC.counter_name,
    PC.cntr_value
FROM 
    sys.dm_os_performance_counters AS PC
WHERE 
    PC.counter_name LIKE N'%Param%';

O script para testar uma declaração específica se parece com isso:
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
EXECUTE #PerfCounters;
EXECUTE #TrivialPlans;
GO
SET SHOWPLAN_XML ON;
GO
-- The statement(s) under test:
-- Example 3
SELECT 
    U.DisplayName, 
    LOWER(U.DisplayName)
FROM dbo.Users AS U 
WHERE 
    U.Reputation = 999;
GO
SET SHOWPLAN_XML OFF;
GO
EXECUTE #TrivialPlans;
EXECUTE #PerfCounters;

Comente o SHOWPLAN_XML lotes para executar a(s) instrução(ões) de destino e obter real planos. Deixe-os no lugar por estimativa planos de execução.

Executando a coisa toda como está escrito dá os seguintes resultados:

Resultados do teste do contador de desempenho

Eu destaquei acima onde os valores mudaram ao testar o exemplo 3.

O aumento no contador “plano trivial” de 1050 para 1051 mostra que um plano trivial foi encontrado para a instrução de teste.

Os contadores de parametrização simples aumentaram em 1 para tentativas e falhas, mostrando que o SQL Server tentou parametrizar a instrução, mas falhou.

Fim da Parte 3


Na próxima parte desta série, explicarei as coisas curiosas que vimos descrevendo como a parametrização simples e planos triviais interagir com o processo de compilação.

Se você alterou seu limite de custo para paralelismo para executar os exemplos, lembre-se de redefini-lo (o meu foi definido como 50):
EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 50;
RECONFIGURE;