Por que a segunda INSERT
declaração é ~ 5x mais lenta que a primeira?
Da quantidade de dados de log gerados, acho que o segundo não está qualificado para o log 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 registro mínimo é a principal diferença de desempenho, por que a segunda consulta não se qualifica para o registro 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 seguinte consulta, que insere linhas de 5MM em um heap. Essa consulta é executada 1 second
e gera 64MB
dados do 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 essa consulta muito semelhante, que opera exatamente nos mesmos dados, mas é extraída de uma tabela (ou SELECT
declaração complexa com muitas junções no meu caso de produção atual) em que a estimativa de cardinalidade é muito baixa. Essa consulta é executada 5.5 seconds
e gera 461MB
dados do 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
Script completo
Consulte este Pastebin para obter um conjunto completo de scripts para gerar os dados de teste e executar um desses cenários. Observe que você deve usar um banco de dados que esteja no SIMPLE
modelo de recuperação .
Contexto empresarial
Estamos semi-frequentemente movendo 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 de heap e usá-la INSERT...WITH (TABLOCK)
era uma boa maneira de fazer isso, mas agora nos tornamos menos confiantes, pois observamos a situação demonstrada acima em um cenário de produção real (embora com consultas mais complexas, não as versão simplificada aqui).
fonte
SELECT
declaração complexa com inúmeras associações que gera o conjunto de resultados para oINSERT
. Essas junções produzem estimativas de cardinalidade ruins para o operador de inserção da mesa final (que eu simulei no script de reprodução por meio de umaUPDATE STATISTICS
chamada incorreta ) e, portanto, não é tão simples como emitir umUPDATE STATISTICS
comando para corrigir o problema. Concordo plenamente que simplificar a consulta para facilitar o entendimento do Cardinality Estimator pode ser uma boa abordagem, mas não é um procedimento trivial implementar uma lógica de negócios complexa.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 obter otimizações de carregamento em massa. Confirmei que isso gera log mínimo e melhora o desempenho da consulta.
Conclusões finais
SELECT...INTO
para operações de inserção única se for necessário um registro mínimo. Como Paul aponta, isso garantirá o registro mínimo, independentemente da estimativa de linhaUm artigo relacionado
A publicação do blog de Paul White em maio de 2019 Minimal Logging with INSERT… SELECT into Heap Tables cobre algumas dessas informações com mais detalhes.
fonte