Consulta T-SQL usando plano completamente diferente, dependendo do número de linhas que estou atualizando

20

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 1a consulta, mas sem efeito - esse passo se foi, mas a má escolha / desempenho não é. Também corri sp_updatestatsesta 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

Aqui está o plano rápido : Plano de Execução Rápida

E aqui está o mais lento : Plano de execução lenta

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: Resumo da declaração

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: Plano de execução estimado de 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 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 : insira a descrição da imagem aqui

SqlRyan
fonte
É OBRIGADO Ser estatísticas. Você já jogou uma sp_updatestatisticssobre a mesa?
JNK
@JNK: Inicialmente achei que sim, mas já executei o sp_updatestats sem alterações. Acabei de executá-lo novamente e não se preocupou em atualizar as estatísticas de qualquer um dos índices envolvidos na consulta. Obrigado embora!
SqlRyan
O segundo é um plano de atualização amplo (por índice), em vez de um plano estreito (por linha), o que explica parte da complexidade extra visível. Mas realmente a única diferença é se juntar a fim 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.enddatevsfrom #mac join t on t.TimeValue between sma.StartDate and sma.enddate join f on f.TimeID = t.TimeID and f.macid = sma.macid
Martin Smith
Algo não está certo aqui. Mesmo o plano de consulta caro deve gerar linhas de forma incremental. A TOP 50ainda 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 50com maxdop 1 e como um select, não como uma atualização e publicar o plano? (Tentando simplificar / dividir o espaço de pesquisa).
usr
A junção @usr on t.TimeValue between sma.StartDate and sma.enddatepode 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.
Martin Smith

Respostas:

3

O índice no dimTime está mudando. O plano mais rápido é usar um índice _dta. Primeiro, verifique se não está marcado como um índice hipotético em sys.indexes.

Pensando que você poderia ignorar algumas parametrizações usando a tabela #mac para filtrar, em vez de apenas fornecer as datas de início / término como esta WHERE t.TimeValue entre @StartDate e @enddate. Livre-se dessa tabela temporária.

william_a_dba
fonte
O índice prefixado dta parece que ele foi criado seguindo uma recomendação do DTA sem personalizar o nome. Os índices hipotéticos não podem aparecer nos planos de execução reais (e não serão estimados sem alguns comandos não documentados). Não tenho certeza de como sua segunda sugestão funcionaria. t.TimeValue between sma.StartDate and sma.enddateestá correlacionado para que seja alterado para cada linha da #temptabela. Com o que o OP o substituiu?
Martin Smith
Justo, eu não prestei atenção suficiente à mesa temporária.
william_a_dba
11
No entanto, índices hipotéticos podem de fato estragar um plano de execução. Se for hipotético, deve ser descartado e recriado. blogs.technet.com/b/anurag_sharma/archive/2008/04/15/…
william_a_dba
Os índices hipotéticos são deixados quando o DTA não termina / congela antes da conclusão. Você precisa limpá-los manualmente se houver algum problema com o DTA.
william_a_dba
11
@william_a_dba - Ah, entendo o que você quer dizer agora (depois de ler o seu link). A consulta nunca termina e poderia ter sido recompilada continuamente. Interessante!
Martin Smith
1

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 a usar OPTION (FORCE ORDER). Impor a ordem de junção do primeiro plano.

usr
fonte