Eu tenho uma instrução SQL UPDATE com uma cláusula "TOP (X)" e a linha em que estou atualizando valores tem cerca de 4 bilhões de linhas. Quando uso "TOP (10)", obtenho um plano de execução que é executado quase instantaneamente, mas quando uso "TOP (50)" ou maior, a consulta nunca (pelo menos, não enquanto estou esperando) termina e ele usa um plano de execução completamente diferente. A consulta menor usa um plano muito simples com um par de buscas de índice e uma junção de loop aninhada, onde exatamente a mesma consulta (com um número diferente de linhas na cláusula TOP da instrução UPDATE) usa um plano que envolve duas buscas de índice diferentes , um carretel de mesa, paralelismo e várias outras complexidades.
Usei "OPTION (USE PLAN...)" para forçá-lo a usar o plano de execução gerado pela consulta menor - quando faço isso, posso atualizar até 100.000 linhas em alguns segundos. Eu sei que o plano de consulta é bom, mas o SQL Server só escolherá esse plano por conta própria quando apenas um pequeno número de linhas estiver envolvido - qualquer contagem de linha decentemente grande em minha atualização resultará no plano abaixo do ideal.
Achei que o paralelismo poderia ser o culpado, então comecei MAXDOP 1
a consulta, mas sem efeito - essa etapa foi concluída, mas a má escolha/desempenho não. Também corri sp_updatestats
esta manhã para garantir que não era essa a causa.
Anexei os dois planos de execução - o mais curto também é o mais rápido. Além disso, aqui está a consulta em questão (vale a pena notar que o SELECT que incluí parece ser rápido nos casos de contagens de linhas pequenas e grandes):
update top (10000) FactSubscriberUsage3
set AccountID = sma.CustomerID
--select top 50 f.AccountID, sma.CustomerID
from FactSubscriberUsage3 f
join dimTime t
on f.TimeID = t.TimeID
join #mac sma
on f.macid = sma.macid
and t.TimeValue between sma.StartDate and sma.enddate
where f.AccountID = 0 --There's a filtered index on the table for this
Existe algo óbvio na maneira como estou configurando minha consulta ou no plano de execução fornecido que se prestaria à má escolha que o mecanismo de consulta está fazendo? Se necessário, também posso incluir as definições das tabelas envolvidas e os índices nelas definidos.
Para aqueles que pediram uma versão apenas de estatísticas dos objetos do banco de dados: eu nem sabia que você poderia fazer isso, mas faz todo o sentido! Tentei gerar os scripts para um banco de dados somente de estatísticas para que outros pudessem testar os planos de execução por si mesmos, mas posso gerar estatísticas/histogramas no meu índice filtrado (erro de sintaxe no script, ao que parece), então estou sem sorte lá. Eu tentei remover o filtro e os planos de consulta estavam próximos, mas não exatamente os mesmos, e não quero enviar ninguém em uma perseguição.
Atualização e alguns planos de execução mais completos: Em primeiro lugar, o Plan Explorer do SQL Sentry é uma ferramenta incrível. Eu nem sabia que existia até ver as outras perguntas do plano de consulta neste site, e tinha muito a dizer sobre como minhas consultas estavam sendo executadas. Embora eu não tenha certeza de como resolver o problema, eles deixaram claro qual é o problema.
Aqui está o resumo para 10, 100 e 1.000 linhas - você pode ver que a consulta de 1.000 linhas está muito, muito fora de linha com as outras:
Você pode ver que a terceira consulta tem um número ridículo de leituras, então obviamente está fazendo algo completamente diferente. Aqui está o plano de execução estimado, com contagens de linhas. Plano de execução estimado de 1.000 linhas:
E aqui estão os resultados reais do plano de execução (aliás, por "nunca termina", eu quis dizer "acaba em uma hora"). Plano de execução real de 1.000 linhas
A primeira coisa que notei foi que, em vez de extrair 60 mil linhas da tabela dimTime como esperado, na verdade está extraindo 1,6 bilhão, com um B . Olhando para minha consulta, não tenho certeza de como ela está retirando tantas linhas da tabela dimTime. O operador BETWEEN que estou usando apenas garante que estou extraindo o registro correto do #mac com base no registro de tempo na tabela Fact. No entanto, quando adiciono uma linha à cláusula WHERE onde filtro t.TimeValue (ou t.TimeID) para um único valor, posso atualizar com êxito 100.000 linhas em questão de segundos. Como resultado disso, e como ficou claro nos planos de execução que incluí, é óbvio que o problema é meu cronograma, mas não tenho certeza de como alteraria os critérios de junção para contornar esse problema e manter a precisão . Alguma ideia?
Para referência, aqui está o plano (com contagens de linhas) para a atualização de 100 linhas. Você pode ver que ele atinge o mesmo índice e ainda com uma tonelada de linhas, mas nem de longe a mesma magnitude de um problema. Execução de 100 linhas com contagens de linha :
O índice em dimTime está mudando. O plano mais rápido é usar um índice _dta. Primeiro, certifique-se de que não está marcado como um índice hipotético em sys.indexes.
Pensando que você poderia estar ignorando alguma parametrização usando a tabela #mac para filtrar em vez de apenas fornecer as datas de início/término como este WHERE t.TimeValue entre @StartDate e @enddate. Livre-se dessa mesa temporária.
Sem mais informações sobre a contagem de linhas no plano, minha recomendação preliminar é organizar a ordem de junção correta na consulta e forçá-la usando
OPTION (FORCE ORDER)
. Aplique a ordem de junção do primeiro plano.