Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Cache de Objeto Temporário do SQL Server


A criação de uma tabela é uma operação que consome muito tempo e muitos recursos. O servidor deve localizar e alocar espaço de armazenamento para os novos dados e estruturas de índice e fazer as entradas correspondentes em várias tabelas de metadados do sistema. Todo esse trabalho deve ser feito de forma que sempre funcione corretamente em alta simultaneidade e que atenda a todas as garantias ACID esperadas de um banco de dados relacional.

No SQL Server, isso significa usar os tipos certos de bloqueios e travas, na sequência correta, ao mesmo tempo em que garante que as entradas detalhadas do log de transações sejam confirmadas com segurança no armazenamento persistente antes de qualquer alteração física no banco de dados. Essas entradas de log garantem que o sistema possa trazer o banco de dados de volta a um estado consistente no caso de uma reversão de transação ou falha do sistema.

Descartar uma tabela é uma operação igualmente cara. Felizmente, a maioria dos bancos de dados não cria ou descarta tabelas com grande frequência. A exceção óbvia a isso é o banco de dados do sistema tempdb . Esse banco de dados único contém o armazenamento físico, estruturas de alocação, metadados do sistema e entradas de log de transações para todas as tabelas temporárias e variáveis ​​de tabela em toda a instância do SQL Server.

É da natureza das tabelas temporárias e variáveis ​​de tabela serem criadas e eliminadas com muito mais frequência do que outros tipos de objetos de banco de dados. Quando essa frequência naturalmente alta de criação e destruição é combinada com o efeito concentrador de todas as tabelas temporárias e variáveis ​​de tabela associadas a um único banco de dados, não é de surpreender que a contenção possa surgir nas estruturas de alocação e metadados do tempdb base de dados.

Cache de objeto temporário


Para reduzir o impacto no tempdb estruturas, o SQL Server pode armazenar em cache objetos temporários para reutilização. Em vez de descartar um objeto temporário, o SQL Server retém os metadados do sistema e trunca os dados da tabela. Se a tabela tiver 8 MB ou menos, o truncamento será executado de forma síncrona; caso contrário, a queda diferida é usada. Em ambos os casos, o truncamento reduz o requisito de armazenamento para uma única página de dados (vazia) e as informações de alocação para uma única página do IAM.

O armazenamento em cache evita quase todos os custos de alocação e metadados da criação do objeto temporário na próxima vez. Como efeito colateral de fazer menos alterações no tempdb banco de dados do que um ciclo completo de descarte e recriação, o cache de objetos temporários também reduz a quantidade de log de transações necessária.

Atingindo o cache


Variáveis ​​de tabela e tabelas temporárias locais podem ser armazenadas em cache. Para se qualificar para armazenamento em cache, uma tabela temporária local ou variável de tabela deve ser criado em um módulo:
  • Procedimento armazenado (incluindo um procedimento armazenado temporário)
  • Acionador
  • Função com valor de tabela de várias instruções
  • Função escalar definida pelo usuário

O valor de retorno de uma função com valor de tabela de várias instruções é uma variável de tabela, que pode ser armazenada em cache. Parâmetros com valor de tabela (que também são variáveis ​​de tabela) podem ser armazenados em cache quando o parâmetro é enviado de um aplicativo cliente, por exemplo, em código .NET usando SqlDbType.Structured . Quando a instrução é parametrizada, as estruturas de parâmetro com valor de tabela só podem ser armazenadas em cache no SQL Server 2012 ou posterior.

O seguinte não pode ser armazenado em cache:
  • Tabelas temporárias globais
  • Objetos criados usando SQL ad-hoc
  • Objetos criados usando SQL dinâmico (por exemplo, usando EXECUTE ou sys.sp_executesql )

Para ser armazenado em cache, um objeto temporário também não deve :
  • Ter restrições nomeadas (restrições sem nomes explícitos são perfeitamente adequadas)
  • Execute "DDL" após a criação do objeto
  • Estar em um módulo definido usando o WITH RECOMPILE opção
  • Ser chamado usando o WITH RECOMPILE opção do EXECUTE declaração

Para abordar explicitamente alguns equívocos comuns:
  • TRUNCATE TABLE não evitar cache
  • DROP TABLE não evitar cache
  • UPDATE STATISTICS não evitar cache
  • A criação automática de estatísticas não evitar cache
  • Manual CREATE STATISTICS vai evitar o armazenamento em cache

Todos os objetos temporários em um módulo são avaliados quanto à adequação ao armazenamento em cache separadamente. Um módulo que contém um ou mais objetos temporários que não podem ser armazenados em cache ainda pode se qualificar para armazenamento em cache de outros objetos temporários dentro do mesmo módulo.

Um padrão comum que desabilita o cache para tabelas temporárias é a criação de índices após a instrução inicial de criação da tabela. Na maioria dos casos, isso pode ser contornado usando chave primária e restrições exclusivas. No SQL Server 2014 e posterior, temos a opção de adicionar índices não clusterizados não exclusivos diretamente na instrução de criação de tabela usando o INDEX cláusula.

Monitoramento e Manutenção


Podemos ver quantos objetos temporários estão atualmente armazenados em cache usando os contadores de cache DMV:
SELECT
    DOMCC.[type],
    DOMCC.pages_kb,
    DOMCC.pages_in_use_kb,
    DOMCC.entries_count,
    DOMCC.entries_in_use_count
FROM sys.dm_os_memory_cache_counters AS DOMCC 
WHERE 
    DOMCC.[name] = N'Temporary Tables & Table Variables';

Um exemplo de resultado é:



Uma entrada de cache é considerada em uso enquanto qualquer parte do módulo que o contém estiver em execução. Execuções simultâneas do mesmo módulo resultarão na criação de vários objetos temporários em cache. Vários planos de execução para o mesmo módulo (talvez devido a diferentes sessões SET options) também levará a várias entradas de cache para o mesmo módulo.

As entradas de cache podem envelhecer ao longo do tempo em resposta às necessidades concorrentes de memória. Objetos temporários em cache também podem ser removidos (de forma assíncrona, por um encadeamento do sistema em segundo plano) quando o plano de execução do módulo pai é removido do cache do plano.

Embora não seja suportado (ou de alguma forma recomendado) para sistemas de produção, o armazenamento temporário de cache de objetos pode ser completamente limpo manualmente para fins de teste com:
DBCC FREESYSTEMCACHE('Temporary Tables & Table Variables')
    WITH MARK_IN_USE_FOR_REMOVAL;
WAITFOR DELAY '00:00:05';

O atraso de cinco segundos permite que a tarefa de limpeza em segundo plano seja executada. Observe que este comando é realmente perigoso . Você só deve empregá-lo (por sua conta e risco) em uma instância de teste à qual tenha acesso exclusivo. Depois de concluir o teste, reinicie a instância do SQL Server.

Detalhes da implementação do cache


As variáveis ​​de tabela são implementadas por uma tabela de usuário 'real' no tempdb banco de dados (embora não seja uma tabela que possamos consultar diretamente). O nome da tabela associada é "#" seguido pela representação hexadecimal de oito dígitos do id do objeto. A consulta a seguir mostra a relação:
-- A table variable
DECLARE @Z AS table (z integer NULL);
 
-- Corresponding sys.tables entry
SELECT
    T.[name],
    ObjIDFromName = CONVERT(integer, CONVERT(binary(4), RIGHT(T.[name], 8), 2)),
    T.[object_id],
    T.[type_desc],
    T.create_date,
    T.modify_date
FROM tempdb.sys.tables AS T 
WHERE
    T.[name] LIKE N'#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]';

Um exemplo de resultado é mostrado abaixo. Observe como o id do objeto calculado a partir do nome do objeto corresponde ao id do objeto real:



A execução desse script como SQL ad-hoc produzirá um tempdb diferente ID do objeto (e nome do objeto) em cada execução (sem armazenamento em cache). Colocar o mesmo script dentro de um módulo (por exemplo, um procedimento armazenado) permitirá que a variável da tabela seja armazenada em cache (desde que o SQL dinâmico não seja usado), de modo que o ID e o nome do objeto sejam os mesmos em cada execução.

Quando a variável de tabela não é armazenada em cache, a tabela subjacente é criada e descartada a cada vez. Quando o cache de objeto temporário está habilitado, a tabela é truncada no final do módulo em vez de ser descartada. Não há nenhuma alteração aos metadados do sistema quando uma variável de tabela é armazenada em cache. O impacto nas estruturas de alocação e no log de transações é limitado à exclusão das linhas da tabela e à remoção de qualquer excesso de dados e páginas de alocação quando o módulo termina.

Tabelas temporárias


Quando uma tabela temporária é usada em vez de uma variável de tabela, o mecanismo básico é essencialmente o mesmo, com apenas algumas etapas extras de renomeação:Quando uma tabela temporária não é armazenada em cache , é visível em tempdb com o nome familiar fornecido pelo usuário, seguido por vários sublinhados e a representação hexadecimal do id do objeto como sufixo final. A tabela temporária local permanece até que seja explicitamente eliminada ou até que o escopo no qual foi criada termine. Para SQL ad-hoc, isso significa quando a sessão é desconectada do servidor.

Para uma tabela temporária em cache , na primeira vez que o módulo é executado, a tabela temporária é criada exatamente como para o caso não armazenado em cache. No final do módulo, em vez de ser descartada automaticamente (à medida que o escopo em que foi criada termina), a tabela temporária é truncada e então renomeada à representação hexadecimal do ID do objeto (exatamente como visto para a variável de tabela). Na próxima vez que o módulo for executado, a tabela armazenada em cache será renomeada do formato hexadecimal para o nome fornecido pelo usuário (mais sublinhados mais id de objeto hexadecimal).

As operações extras de renomeação no início e no final do módulo envolvem um pequeno número de alterações de metadados do sistema . As tabelas temporárias armazenadas em cache podem, portanto, ainda sofrer pelo menos alguma contenção de metadados sob taxas muito altas de reutilização. No entanto, o impacto de metadados de uma tabela temporária armazenada em cache é muito menor do que para o caso não armazenado em cache (criar e descartar a tabela a cada vez).

Mais detalhes e exemplos de como funciona o cache de objetos temporários podem ser encontrados em meu artigo anterior.

Estatísticas em tabelas temporárias em cache


Conforme mencionado anteriormente, as estatísticas podem ser automaticamente criados em tabelas temporárias sem perder as vantagens do cache de objetos temporários (como lembrete, criar estatísticas manualmente vai desabilitar o cache).

Uma advertência importante é que as estatísticas associados a uma tabela temporária em cache não são redefinidos quando o objeto é armazenado em cache no final do módulo ou quando o objeto armazenado em cache é recuperado do cache no início do módulo. Como consequência, as estatísticas em uma tabela temporária em cache podem sobrar de uma execução anterior não relacionada. Em outras palavras, as estatísticas podem ter absolutamente nenhuma relação ao conteúdo atual da tabela temporária.

Isso é obviamente indesejável, dado que a principal razão para preferir uma tabela temporária local a uma variável de tabela é a disponibilidade de estatísticas de distribuição precisas. Na mitigação, as estatísticas serão atualizadas automaticamente quando (se) o número acumulado de alterações no objeto em cache subjacente atingir o limite de recompilação interno. Isso é difícil de avaliar com antecedência, porque os detalhes são complexos e um tanto contra-intuitivos.

A solução alternativa mais abrangente, mantendo os benefícios do cache de objetos temporário, é:
  • Manualmente UPDATE STATISTICS na tabela temporária dentro do módulo; e
  • Adicione uma OPTION (RECOMPILE) dica para instruções que fazem referência à tabela temporária

Naturalmente, há um custo envolvido em fazer isso, mas na maioria das vezes isso é aceitável. De fato, ao escolher usar uma tabela temporária local em primeiro lugar, o autor do módulo está dizendo implicitamente que a seleção do plano provavelmente será sensível ao conteúdo da tabela temporária, de modo que a recompilação pode fazer sentido. A atualização manual das estatísticas garante que as estatísticas usadas durante a recompilação reflitam o conteúdo atual da tabela (como certamente esperaríamos).

Para obter mais detalhes sobre exatamente como isso funciona, consulte meu artigo anterior sobre o tópico.

Resumo e recomendações


O cache de objetos temporários dentro de um módulo pode reduzir bastante a pressão sobre alocação compartilhada e estruturas de metadados no tempdb base de dados. A maior redução ocorrerá ao usar variáveis ​​de tabela porque armazenar em cache e reutilizar esses objetos temporários não envolve a modificação de metadados (sem operações de renomeação). A contenção nas estruturas de alocação ainda pode ser vista se a única página de dados em cache for insuficiente para conter todos os dados da variável de tabela em tempo de execução.

O impacto na qualidade do plano devido à falta de informações de cardinalidade para variáveis ​​de tabela pode ser mitigado usando OPTION(RECOMPILE) ou sinalizador de rastreamento 2453 (disponível a partir do SQL Server 2012). Observe que essas atenuações apenas fornecem ao otimizador informações sobre o número total de linhas na tabela.

Para generalizar, variáveis ​​de tabela são melhor usados ​​quando os dados são pequenos (idealmente se encaixam em uma única página de dados para obter o máximo de benefícios de contenção) e quando a seleção do plano não depende dos valores presentes na variável da tabela.

Se as informações sobre a distribuição de dados (densidade e histogramas) é importante para a seleção do plano, use uma tabela temporária local em vez de. Certifique-se de atender às condições para o cache de tabela temporário, o que geralmente significa não criar índices ou estatísticas após a instrução inicial de criação da tabela. Isso se tornou mais conveniente a partir do SQL Server 2014 devido à introdução do INDEX cláusula do CREATE TABLE demonstração.

Um UPDATE STATISTICS explícito após os dados serem carregados na tabela temporária, e OPTION (RECOMPILE) dicas sobre instruções que fazem referência à tabela podem ser necessárias para produzir todos os benefícios esperados de tabelas temporárias armazenadas em cache dentro de um módulo.

É importante usar apenas objetos temporários quando eles produzem um benefício claro, na maioria das vezes em termos de qualidade do plano. O uso excessivo, ineficiente ou desnecessário de objetos temporários pode levar a tempdb contenção, mesmo quando o cache de objeto temporário é alcançado.

O cache de objeto temporário ideal pode não ser suficiente para reduzir o tempdb contenção a níveis aceitáveis ​​em todos os casos, mesmo quando objetos temporários são usados ​​apenas quando totalmente justificados. O uso de variáveis ​​de tabela na memória ou tabelas na memória não duráveis ​​pode fornecer soluções direcionadas nesses casos, embora sempre haja compensações a serem feitas e nenhuma solução única atualmente represente a melhor opção em todos os casos.