Eu tenho uma instrução SQL UPDATE com uma cláusula "TOP (X)" e a linha na qual estou atualizando os valores tem cerca de 4 bilhões de linhas. Quando eu uso "TOP (10)", recebo um plano de execução que é executado quase instantaneamente, mas quando eu 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, em que 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çar o uso do plano de execução gerado pela consulta menor - quando faço isso, posso atualizar até 100.000 linhas em alguns segundos. Sei que o plano de consulta é bom, mas o SQL Server só escolherá esse plano sozinho quando apenas um pequeno número de linhas estiver envolvido - qualquer contagem de linhas decentemente grande na minha atualização resultará no plano abaixo do ideal.
Eu pensei que o paralelismo poderia ser o culpado, então eu iniciei MAXDOP 1
a consulta, mas sem efeito - esse passo se foi, mas a má escolha / desempenho não é. Também corri sp_updatestats
esta manhã para garantir que não era 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 eu 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 alguma coisa óbvia na maneira como estou configurando minha consulta ou no plano de execução, desde que se prestem à má escolha que o mecanismo de consulta está fazendo? Se necessário, também posso incluir as definições de tabela envolvidas e os índices definidos nelas.
Para aqueles que pediram uma versão apenas estatística dos objetos do banco de dados: eu nem percebi que você poderia fazer isso, mas faz todo o sentido! Tentei gerar os scripts para um banco de dados apenas de estatísticas, para que outros pudessem testar os planos de execução, mas eu posso gerar estatísticas / histogramas no meu índice filtrado (erro de sintaxe no script, ao que parece), então estou sem sorte lá. Tentei remover o filtro e os planos de consulta estavam próximos, mas não exatamente o mesmo, e não quero enviar ninguém para perseguir.
Atualização e alguns planos de execução mais completos: Primeiro, o Plan Explorer do SQL Sentry é uma ferramenta incrível. Eu nem sabia que existia até visualizar as outras perguntas do plano de consulta neste site, e havia muito a dizer sobre como as minhas consultas estavam sendo executadas. Embora eu não tenha certeza de como lidar com o problema, eles deixaram óbvio qual é o problema.
Aqui está o resumo para 10, 100 e 1000 linhas - você pode ver que a consulta de 1000 linhas está 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 contagem de linhas. Plano de execução estimado para 1000 linhas:
E aqui estão os resultados reais do plano de execução (a propósito, por "nunca termina", acontece que eu quis dizer "termina em uma hora"). Plano de execução real de 1000 linhas
A primeira coisa que notei foi que, em vez de puxar 60K linhas da tabela dimTime como se espera, é realmente puxando 1,6 bilhões, com um B . Olhando para a minha consulta, não tenho certeza de como está retirando muitas linhas da tabela dimTime. O operador BETWEEN que estou usando garante que você esteja obtendo o registro correto de #mac com base no registro de tempo na tabela Fatos. 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 horário, mas não tenho certeza de como alteraria os critérios de junção para solucionar esse problema e manter a precisão. . Alguma ideia?
Para referência, aqui 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 perto da mesma magnitude de um problema. Execução de 100 linhas com contagem de linhas :
from #mac sma join f on f.macid = sma.macid join dimTime t on f.TimeID = t.TimeID and t.TimeValue between sma.StartDate and sma.enddate
vsfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
TOP 50
ainda deve ser executado rapidamente. Você pode fazer o upload dos planos XML? Eu preciso olhar para a contagem de linhas. Você pode executar o TOP 50
com maxdop 1 e como um select, não como uma atualização e publicar o plano? (Tentando simplificar / dividir o espaço de pesquisa).
t.TimeValue between sma.StartDate and sma.enddate
pode acabar gerando muito mais linhas inúteis que depois são filtradas na junção contra o FactSubscriber e, portanto, não acabam no resultado final.
sp_updatestatistics
sobre a mesa?