Baixo desempenho da tabela temporal em valores mais antigos


8

Estou encontrando um problema estranho ao acessar registros históricos em uma tabela temporal. As consultas que acessam as entradas mais antigas na tabela temporal por meio da sub-cláusula AS OF levam mais tempo do que as consultas nas entradas históricas recentes.

A tabela histórica foi gerada pelo SQL Server (inclui um índice clusterizado nas colunas de data e usa compactação de página), adicionei 50 milhões de linhas à tabela histórica e minhas consultas estavam recuperando cerca de 25.000 linhas.

Tentei determinar a causa raiz do problema, mas não consegui identificá-lo. Até agora eu testei:

  • Criando uma tabela de teste com 50 milhões de linhas com um índice em cluster para ver se a desaceleração ocorreu simplesmente devido ao volume. Consegui recuperar 25K linhas em tempo constante (~ 400ms).
  • Removendo a compactação de página da tabela histórica. Isso não teve efeito no tempo de recuperação, mas aumentou significativamente o tamanho da tabela.
  • Tentei acessar as linhas da tabela de histórico diretamente usando uma coluna de ID versus as colunas de data. É aqui que as coisas são um pouco mais interessantes. Eu poderia acessar linhas mais antigas da tabela em ~ 400ms, onde, como na sub cláusula AS OF, levaria ~ 1200ms. Tentei filtrar na minha tabela de teste na coluna date e notei uma desaceleração semelhante quando comparada à filtragem na coluna ID. Isso me leva a acreditar que as comparações de datas estão por trás de uma certa desaceleração.

Quero olhar mais para isso, mas também quero ter certeza de que não estou latindo na árvore errada. Primeiro, alguém mais experimentou esse mesmo comportamento ao acessar dados históricos mais antigos em uma tabela temporal (notamos apenas lentidão em 10 milhões de linhas)? Segundo, quais são algumas estratégias que posso usar para isolar ainda mais a causa raiz do problema de desempenho (comecei a examinar os planos de execução, mas ainda é um pouco enigmático para mim)?

Planos de execução

Estas são consultas de recuperação simples: a primeira acessa linhas mais antigas, a segunda acessa linhas mais recentes.

Linhas mais antigas ~ tempo de execução de 1200ms

Linhas recentes ~ tempo de execução de 350ms

Detalhes da tabela

Estas são as colunas na tabela temporal. A tabela de histórico possui as mesmas colunas, mas não possui uma chave primária (conforme os requisitos da tabela de histórico): Coluna da tabela temporal

Abaixo estão os índices na tabela de histórico: Índices na tabela de histórico

Respostas:


6

Em um comentário de Zane sobre sua pergunta, ele declarou:

... Parece que parte do seu problema é que você está lendo 50 milhões de linhas para retornar 20K no plano.

Este é, de fato, o problema. Não há índice disponível para enviar alguns ou todos os predicados para o mecanismo de armazenamento. A Microsoft recomenda esta estratégia de indexação de linha de base para tabelas temporais no artigo Docs Considerações e limitações da tabela temporal :

Uma estratégia de indexação ideal incluirá um índice de armazenamento de colunas em cluster e / ou um índice de armazenamento de linhas em árvore B na tabela atual e um índice de armazenamento de colunas em cluster na tabela de histórico para obter tamanho e desempenho ideais de armazenamento. Se você criar / usar sua própria tabela de histórico, é altamente recomendável que você crie esse tipo de índice que consiste em colunas de período começando com a coluna final do período para acelerar a consulta temporal e também para acelerar as consultas que fazem parte da consistência dos dados Verifica. A tabela de histórico padrão possui um índice de armazenamento de linhas em cluster criado para você com base nas colunas do período (final, início). No mínimo, é recomendado um índice rowstore não clusterizado

O fraseado disso é um pouco confuso (para mim, pelo menos). Mas o principal é que você pode criar esses índices para melhorar o desempenho de alguns, se não muito:

Índice NC na tabela atual, levando a SysEndTime:

CREATE NONCLUSTERED INDEX IX_SysEndTime_SysStartTime 
ON dbo.Benefits (SysEndTime, SysStartTime)
/*INCLUDE (ideally, include your other important fields here)*/;

Isso permitirá que você evite ler algumas das linhas da tabela atual procurando o horário de término apropriado.

CCI na tabela de histórico

CREATE CLUSTERED COLUMNSTORE INDEX ix_BenefitsHistory
ON dbo.BenefitsHistory
WITH (DROP_EXISTING = ON);

Isso permitirá que você obtenha o modo em lote na tabela de histórico, o que deve tornar as verificações muito mais rápidas.

Índice NC na tabela atual, levando a SysStartTime:

Consulte a resposta de Paul à pergunta Maneira mais eficiente de recuperar intervalos de datas para obter mais detalhes sobre por que a indexação para consultas de período é difícil. Com base na lógica existente, faz sentido adicionar outro índice NC na tabela atual que leva ao SysStartTime, para que o otimizador possa escolher qual usar com base nas estatísticas e nos parâmetros específicos da sua consulta:

CREATE NONCLUSTERED INDEX IX_SysStartTime_SysEndTime
ON dbo.Benefits (SysStartTime, SysEndTime)
/*INCLUDE (ideally, include your other important fields here)*/;

A criação dos três índices descritos acima fez uma diferença significativa no uso de recursos nos meus casos de teste. Configurei um caso de teste que executa duas consultas que retornam 1,5 milhão de linhas no total. As tabelas de histórico e atual têm 50 milhões de linhas).

Nota: Para reduzir a sobrecarga do SSMS, executei o teste com a opção "Descartar resultados após a execução" ativada.

Plano de execução - índices padrão

Leituras lógicas: 1.330.612
tempo da CPU: 00: 00: 14.718
Tempo decorrido: 00: 00: 06.198

Plano de execução - com índices descritos acima

Leituras lógicas: 27.656 (8.111 armazenamento de linha + 19.545 columnstore)
Tempo da CPU: 00: 00: 01.828
Tempo decorrido: 00: 00: 01.150

Como você pode ver, todas as três medidas caíram significativamente - incluindo o tempo total decorrido, de 6 segundos para 1 segundo.


A outra opção apresentada no artigo do Documentos é renunciar aos dois índices NC na tabela atual em favor de um índice columnstore clusterizado. No meu teste, o desempenho foi muito semelhante à solução de indexação descrita acima.


2

A FOR SYSTEM TIME AS OFcláusula tenta retornar o conjunto de dados como ele existia no tempo determinado. Isso significa que as atualizações precisam ser revertidas internamente, as exclusões precisam ser 'canceladas' e as inserções precisam ser ignoradas, com base no horário do sistema da solicitação.

Quanto mais tempo passado o AS OF, mais trabalho precisa ser validado para garantir que a tabela temporal exista na hora especificada do sistema e, portanto, mais tempo a consulta levará.

Se a tabela de dados for apenas uma tabela de registro em log e nenhuma alteração for feita nos dados, a data de registro e o índice retornarão os dados de maneira mais rápida e consistente. A utilização dos recursos temporais nesse caso é desnecessária. No entanto, se forem feitas alterações nas linhas (que não sejam inserções), o uso do recurso de tabela temporal é a única maneira de retornar os dados exatos que estão sendo solicitados (o estado da tabela que existia naquele horário específico) e você basta aceitar a sobrecarga adicional das consultas temporais.

Nota: As "reversões" não são reversões reais. As tabelas temporais usam duas tabelas - uma tabela Atual e uma tabela Histórico. Quando uma linha é alterada, uma cópia da versão anterior é inserida na tabela Histórico com o intervalo de tempo em que a linha era válida. Se você inserir uma linha em 20/10/2018 10: 20: 20.18, atualize um valor em 25/10/2018 10: 25: 20.18 e atualize-o novamente em 01/12/2018 12: 01: 20.18. a versão mais recente da linha na tabela Atual com uma data de início de 01/12/2018 12: 01: 20.18 e duas linhas na tabela de histórico com intervalos válidos de 20/10 a 25/10/2018 e 10 / 25 a 01/01/2018


Obrigado pela resposta! Definitivamente, isso faz sentido intuitivo, mas não encontrei nenhuma menção a esse tipo de comportamento nos documentos que li (apenas analisei o básico da tabela temporal nos documentos da MS). Você conhece alguma documentação que descreva o comportamento um pouco mais detalhadamente?
Ebrahim Behbahani
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.