Por que a segunda INSERT
instrução é aproximadamente 5x mais lenta que a primeira?
Pela quantidade de dados de log gerados, acho que o segundo não se qualifica para o registro mínimo. No entanto, a documentação no Guia de desempenho de carregamento de dados indica que ambas as inserções devem poder ser minimamente registradas. Portanto, se o log mínimo é a principal diferença de desempenho, por que a segunda consulta não se qualifica para o log mínimo? O que pode ser feito para melhorar a situação?
Consulta nº 1: Inserindo linhas de 5MM usando INSERT...WITH (TABLOCK)
Considere a consulta a seguir, que insere 5MM de linhas em um heap. Esta consulta é executada 1 second
e gerada 64MB
por dados de log de transações, conforme relatado por sys.dm_tran_database_transactions
.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
Consulta nº 2: Inserindo os mesmos dados, mas o SQL subestima o número de linhas
Agora, considere esta consulta muito semelhante, que opera exatamente com os mesmos dados, mas extrai de uma tabela (ou SELECT
instrução complexa com muitas junções em meu caso de produção real) onde a estimativa de cardinalidade é muito baixa. Esta consulta executa 5.5 seconds
e gera 461MB
dados de log de transações.
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
roteiro completo
Consulte este Pastebin para obter um conjunto completo de scripts para gerar os dados de teste e executar qualquer um desses cenários. Observe que você deve usar um banco de dados que esteja no SIMPLE
modelo de recuperação .
Contexto empresarial
Estamos movendo com frequência milhões de linhas de dados e é importante que essas operações sejam o mais eficientes possível, tanto em termos de tempo de execução quanto de carga de E/S do disco. Inicialmente, tínhamos a impressão de que criar uma tabela heap e usar INSERT...WITH (TABLOCK)
era uma boa maneira de fazer isso, mas agora ficamos menos confiantes, pois observamos a situação demonstrada acima em um cenário de produção real (embora com consultas mais complexas, não o versão simplificada aqui).
Por que a segunda consulta não se qualifica para registro mínimo?
O log mínimo está disponível para a segunda consulta, mas o mecanismo opta por não usá-lo no tempo de execução.
Há um limite mínimo abaixo do
INSERT...SELECT
qual ele escolhe não usar as otimizações de carregamento em massa. Há um custo envolvido na configuração de uma operação de conjunto de linhas em massa e a inserção em massa de apenas algumas linhas não resultaria na utilização eficiente do espaço.O que pode ser feito para melhorar a situação?
Use um dos muitos outros métodos (por exemplo
SELECT INTO
) que não possui esse limite. Como alternativa, você pode reescrever a consulta de origem de alguma forma para aumentar o número estimado de linhas/páginas acima do limite paraINSERT...SELECT
.Veja também a auto-resposta de Geoff para informações mais úteis.
Curiosidades possivelmente interessantes:
SET STATISTICS IO
relata leituras lógicas para a tabela de destino somente quando as otimizações de carregamento em massa não são usadas .Consegui recriar o problema com meu próprio equipamento de teste:
Isso levanta a questão: por que não "corrigir" o problema atualizando as estatísticas nas tabelas de origem antes de executar a operação minimamente registrada?
Expandindo a ideia de Paul, uma solução alternativa se você estiver realmente desesperado é adicionar uma tabela fictícia que garanta que o número estimado de linhas para a inserção seja alto o suficiente para qualidade para otimizações de carregamento em massa. Confirmei que isso obtém um registro mínimo e melhora o desempenho da consulta.
Considerações finais
SELECT...INTO
para operações de inserção únicas se for necessário registrar minimamente. Como Paul aponta, isso garantirá o registro mínimo, independentemente da estimativa de linhaUm artigo relacionado
A postagem no blog de maio de 2019 de Paul White Registro mínimo com INSERT…SELECT em tabelas heap aborda algumas dessas informações com mais detalhes.