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

Parametrização Simples e Planos Triviais - Parte 1


Esta é a primeira parte de uma série sobre parametrização simples e planos triviais . Esses dois recursos de compilação estão intimamente conectados e têm objetivos semelhantes. Ambos visam desempenho e eficiência para cargas de trabalho que enviam declarações simples com frequência.

Apesar dos nomes “simples” e “trivial”, ambos têm comportamentos sutis e detalhes de implementação que podem dificultar o entendimento de como funcionam. Esta série não se debruça muito sobre o básico, mas concentra-se em aspectos menos conhecidos que podem atrapalhar até mesmo os profissionais de banco de dados mais experientes.

Nesta primeira parte, após uma rápida introdução, analiso os efeitos da parametrização simples no cache do plano.

Parametrização simples


É quase sempre melhor parametrizar explicitamente instruções, em vez de depender do servidor para fazê-lo. Ser explícito dá a você controle completo sobre todos os aspectos do processo de parametrização, incluindo onde os parâmetros são usados, os tipos de dados precisos usados ​​e quando os planos são reutilizados.

A maioria dos clientes e drivers fornece maneiras específicas de usar a parametrização explícita. Existem também opções como sp_executesql , procedimentos armazenados e funções.

Não vou entrar nos problemas relacionados à detecção de parâmetros ou injeção de SQL porque, embora importantes, eles não são o foco desta série. Ainda assim, você deve escrever código com ambos próximos à sua mente.

Para aplicativos legados ou outros códigos de terceiros que não podem ser alterados facilmente, a parametrização explícita nem sempre é possível. Você pode superar alguns obstáculos usando guias de plano de modelo. De qualquer forma, seria uma carga de trabalho incomum que não contém pelo menos algumas instruções parametrizadas do lado do servidor.

Planos Shell


Quando o SQL Server 2005 introduziu a Parametrização Forçada , a parametrização automática existente recurso foi renomeado para Parametrização Simples . Apesar da mudança na terminologia, parametrização simples funciona da mesma forma que a parametrização automática sempre fez:o SQL Server tenta substituir valores literais constantes em instruções ad hoc por marcadores de parâmetro. O objetivo é reduzir compilações aumentando a reutilização de planos em cache.

Vejamos um exemplo, usando o banco de dados Stack Overflow 2010 no SQL Server 2019 CU 14. A compatibilidade do banco de dados é definida como 150 e o limite de custo para paralelismo é definido como 50 para evitar o paralelismo no momento:
EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'cost threshold for parallelism',
    @configvalue = 50;
RECONFIGURE;

Código de exemplo:
-- Clear the cache of plans for this database
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

Essas declarações apresentam predicados que diferem apenas em seus valores literais constantes. SQL Server aplica com sucesso parametrização simples , resultando em um plano parametrizado. O plano parametrizado único é usado quatro vezes, como podemos ver consultando o cache do plano:
SELECT
    CP.usecounts,
    CP.cacheobjtype,
    CP.objtype,
    CP.size_in_bytes,
    ST.[text],
    QP.query_plan
FROM sys.dm_exec_cached_plans AS CP
OUTER APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
OUTER APPLY sys.dm_exec_query_plan (CP.plan_handle) AS QP
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%DisplayName%Users%'
ORDER BY 
    CP.usecounts ASC;

Os resultados mostram um Adhoc planejar a entrada de cache para cada instrução original e um único Preparado plano:

Quatro planos ad hoc e um plano preparado

A Preparado A instrução é semelhante a um procedimento armazenado, com parâmetros inferidos de valores literais encontrados no Adhoc demonstração. Menciono isso como um modelo mental útil ao pensar no processo de parametrização do lado do servidor.

Observe que o SQL Server armazena em cache ambos o texto original e o formulário parametrizado. Quando a parametrização simples é bem sucedida, o plano associado ao texto original é Adhoc e não contém um plano de execução completo. Em vez disso, o plano em cache é um shell com muito pouco além de um ponteiro para o Preparado plano parametrizado.

A representação XML dos planos de shell conter texto como:
<ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.539" Build="15.0.4188.2">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple 
  StatementText="SELECT U.DisplayName&#xD;&#xA;FROM dbo.Users AS U &#xD;&#xA;WHERE U.Reputation = 3151"
  StatementId="1" 
  StatementCompId="1" 
  StatementType="SELECT" 
  RetrievedFromCache="true" 
  ParameterizedPlanHandle="0x0600050090C8321CE04B4B079E01000001000000000000000000000000000000000000000000000000000000" 
  ParameterizedText="(@1 smallint)SELECT [U].[DisplayName] FROM [dbo].[Users] [U] WHERE [U].[Reputation]=@1" />
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>

Esse é todo o plano. O ParameterizedPlanHandle pontos do Adhoc shell para o plano parametrizado completo. O valor do identificador é o mesmo para todos os quatro planos de shell.

Stubs de plano


Os planos Shell são menores do que um plano compilado completo — 16 KB em vez de 40 KB no exemplo. Isso ainda pode adicionar uma quantidade significativa de memória se você tiver muitas instruções usando parametrização simples ou muitos valores de parâmetro diferentes. A maioria das instâncias do SQL Server não está tão cheia de memória a ponto de desperdiçá-la assim. Os planos de shell são considerados muito descartáveis ​​pelo SQL Server, mas encontrá-los e removê-los consome recursos e pode se tornar um ponto de discórdia.

Podemos reduzir o consumo total de memória para planos de shell ativando a opção otimizar para cargas de trabalho ad hoc.
EXECUTE sys.sp_configure
    @configname = 'show advanced options',
    @configvalue = 1;
RECONFIGURE;
GO
EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 1;
RECONFIGURE;

Isso armazena em cache um pequeno stub na primeira vez que uma instrução ad hoc é encontrada em vez de um shell. O stub serve como um marcador para que o servidor possa lembrar que viu o texto exato da instrução antes. Ao encontrar o mesmo texto uma segunda vez, a compilação e o armazenamento em cache prosseguem como se otimize para cargas de trabalho ad hoc não foram habilitados.

Executando novamente o exemplo com otimizar para cargas de trabalho ad hoc enabled mostra o efeito no cache do plano.

Stubs de plano compilados

Nenhum plano é armazenado em cache para as instruções ad-hoc, apenas um stub. Não há ParameterizedPlanHandle ponteiro para o Preparado plano, embora um plano parametrizado completo seja em cache.

Executar os lotes de teste pela segunda vez (sem limpar o cache do plano) fornece o mesmo resultado de quando otimizar para cargas de trabalho ad hoc não foi ativado—quatro Adhoc planos de shell apontando para o Preparado plano.

Antes de continuar, redefina a otimização para cargas de trabalho ad hoc ajuste para zero:
EXECUTE sys.sp_configure
    @configname = 'optimize for ad hoc workloads',
    @configvalue = 0;
RECONFIGURE;

Planejar limites de tamanho de cache


Quer sejam usados ​​shells de planos ou stubs de planos, ainda há desvantagens em todos esses Adhoc entradas de cache. Já mencionei o uso total de memória, mas cada cache de plano também tem um número máximo de entradas. Mesmo onde o uso total de memória não é uma preocupação, a grande quantidade pode ser.

Os limites podem ser aumentados com o sinalizador de rastreamento documentado 174 (número de entradas) e o sinalizador de rastreamento 8032 (tamanho total). Dependendo da carga de trabalho e de outras demandas de memória, essa pode não ser a melhor solução. Afinal, significa apenas armazenar em cache mais Adhoc de baixo valor planos, tirando a memória de outras necessidades.

Planos preparados apenas para armazenamento em cache


Se a carga de trabalho raramente emitir lotes ad-hoc com exatamente o mesmo texto de instrução, armazenar em cache shells de plano ou stubs de plano é um desperdício de recursos. Ele consome memória e pode causar contenção quando os Planos SQL armazenamento de cache (CACHESTORE_SQLCP ) precisa ser reduzido para caber dentro dos limites configurados.

O ideal seria parametrizar os lotes ad-hoc recebidos, mas somente armazenar em cache a versão parametrizada. Há um custo para fazer isso, porque as instruções ad-hoc futuras precisam ser parametrizadas antes de poderem corresponder ao plano em cache parametrizado. Por outro lado, isso teria acontecido de qualquer maneira, pois já declaramos exato correspondências textuais são raras para a carga de trabalho de destino.

Para cargas de trabalho que se beneficiam da parametrização simples, mas não do cache de Adhoc entradas, há algumas opções.

Sinalizador de rastreamento não documentado


A primeira opção é habilitar o sinalizador de rastreamento não documentado 253. Isso impede o cache de Adhoc planeja completamente. Ele não restringe simplesmente o número de tais planos, ou impede que eles “permaneçam” no cache, como às vezes foi sugerido.

O sinalizador de rastreamento 253 pode ser ativado no nível da sessão - restringindo seus efeitos apenas a essa conexão - ou mais amplamente como um sinalizador global ou de inicialização. Ele também funciona como uma dica de consulta, mas usá-los impede a parametrização simples, o que seria contraproducente aqui. Há uma lista parcial das coisas que impedem a parametrização simples no Microsoft Technical Paper, Plan Caching and Recompilation in SQL Server 2012.

Com o sinalizador de rastreamento 253 ativo antes do lote ser compilado , apenas o Preparado as instruções são armazenadas em cache:
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Do not cache ad-hoc plans
DBCC TRACEON (253);
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO
-- Cache ad-hoc plans again
DBCC TRACEOFF (253);
GO

A consulta de cache do plano confirma apenas o Preparado A instrução é armazenada em cache e reutilizada.

Somente a instrução preparada é armazenada em cache

O lote que não pode ser armazenado em cache


A segunda opção é incluir uma declaração que marque todo o lote como não armazenável . As declarações adequadas geralmente são relacionadas à segurança ou, de alguma forma, sensíveis.

Isso pode parecer impraticável, mas há algumas mitigações. Primeiro, a instrução sensível não precisa ser executada - ela só precisa estar presente . Quando essa condição é atendida, o usuário que executa o lote nem precisa de permissão para executar a instrução sensível. Observe com atenção que o efeito está confinado ao lote que contém a instrução confidencial.

Duas instruções adequadamente sensíveis e exemplos de uso são mostrados abaixo (com as instruções de teste agora em um único lote):
ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
-- Prevent caching of all statements in this batch.
-- Neither KEY nor CERTIFICATE need to exist.
-- No special permissions are needed.
-- GOTO is used to ensure the statements are not executed.
GOTO Start
    OPEN SYMMETRIC KEY Banana 
        DECRYPTION BY CERTIFICATE Banana;
Start:
 
/* Another way to achieve the same effect without GOTO
IF 1 = 0
BEGIN
    CREATE APPLICATION ROLE Banana 
    WITH PASSWORD = '';
END;
*/
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2521;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 2827;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3144;
 
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 3151;
GO

O Preparado planos criados por parametrização simples ainda são armazenados em cache e reutilizados, apesar de o lote pai ser marcado como não armazenável em cache.

Somente a instrução preparada é armazenada em cache

Nenhuma das soluções é ideal, mas até que a Microsoft forneça uma solução documentada e com suporte para esse problema, elas são as melhores opções que conheço.

Fim da Parte 1


Há muito mais terreno para cobrir neste tópico. A parte dois cobrirá os tipos de dados atribuídos quando parametrização simples está empregado.