AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 335175
Accepted
James McGrath
James McGrath
Asked: 2024-01-25 05:10:20 +0800 CST2024-01-25 05:10:20 +0800 CST 2024-01-25 05:10:20 +0800 CST

Como posso otimizar um CTE recursivo dentro de um IVTF?

  • 772

Eu tenho um CTE recursivo dentro de uma função com valor de tabela embutida. O ITVF está retornando uma lista de IDs contendo uma longa sequência de ancestrais de uma pessoa, geralmente retorna cerca de 12 a 18 vezes antes de chegar ao fim. É bastante rápido, mas há um erro nas estimativas que se acumula quando usado por muitas pessoas, tornando-se extremamente lento.

O CTE fica assim

WITH ancestors AS (
  SELECT
    IndID,
    AncestorID
  FROM
    dbo.persons

  UNION ALL

  SELECT 
    IndID,
    AncestorID
  FROM
    ancestors a
    INNER JOIN dbo.persons p ON p.IndID = a.AncestorID
)
SELECT IndID, AncestorID FROM ancestors

Eu tenho uma dúzia de milhões de linhas, então é uma tabela bem grande. Quando solicito um IndID, o plano de execução diz que estimou 7 linhas, mas obteve 1.300 linhas reais. Para uma única solicitação, é aceitável (é executado em menos de um segundo), mas se eu me juntar a ela em outra solicitação para que ela seja chamada, digamos 100 vezes, a velocidade cai para um rastreamento, pois a estimativa está ficando cada vez pior.

Só para ficar claro, o erro de estimativa está presente mesmo fora do IVTF. Especifiquei apenas para deixar claro que não posso usar apenas uma tabela temporária. Ele precisa ficar em um IVTF para que eu possa integrá-lo em solicitações maiores e mais complexas e permanecer paralelizado. O que posso fazer para estimar melhor as linhas?

Atualização: cole o plano

Atualização 2: Menos simplificado

Estou meio preso entre dois problemas. Ou eu uso um MSTVF e todas as minhas consultas não podem ser paralelizadas, ou uso um ITVF e espero que os deuses do SQL sejam generosos e não subestimem terrivelmente a contagem de linhas, então tudo agora é trocado no disco rígido em vez de na RAM. Espero que seja apenas porque sou burro e que seja uma solução estúpida e fácil em algum lugar.

Atualização 3 Para responder, com o melhor de meu conhecimento, às perguntas feitas.

uno) Atualizado para a atualização cumulativa mais recente. Não mudou nada como esperado, mas é bom estar atualizado como você disse :)

dos) Estamos na edição Standard, mas tenho uma Column Store e não me lembro por que fiz isso. Está em IndID, FirstNameID, LastNameID. Vou tentar abandoná-lo, somos apenas 2 usuários no banco de dados hoje, podemos gerenciar os tempos de inatividade se ele travar alguma outra coisa.

Depois de remover o ColumnStore, ele economizou cerca de 30 segundos! Ainda lento, mas está melhor. Terei que verificar minhas anotações para descobrir por que fiz aquele ColumnStore.

dos: parte 2) A sensação de "caixa com pouca potência" que você tem é exatamente o que me pegou até agora. Achei que nossa máquina não tinha potência suficiente, mas depois de conversar com a TI aqui, eles disseram que não estávamos usando mais de 25% dos recursos disponíveis, então o gargalo estava definitivamente no nível SQL. Então, pedi uma atualização do SQL 2017 para 2022 no mês passado e então, agora que vi que a maioria das minhas consultas pesadas estavam sempre rodando serializadas, comecei a otimizar até chegar a esta. Eu tentei OPTION(USE HINT('DISALLOW_BATCH_MODE'), MAXDOP 8);e não vejo nenhuma mudança na velocidade.

tres) Essa solicitação deve retornar cerca de 14 milhões de linhas, então não se preocupe desse lado. Mas o fato de apenas 8 linhas terem sido estimadas na reserva de recursos não é uma razão pela qual ela é muito mais lenta do que deveria?

mais contexto) Eu estava usando um MSTVF antes de todo o meu trabalho este mês, quando mudei para um IVTF ele é mais rápido, mas a curva de tempo gasto versus linhas solicitadas é exponencial em vez de linear, se é que você me entende. Estou aberto para repensar como tudo isso é feito.

Trabalho para um grupo de pesquisa e parte do meu trabalho é extrair conjuntos de dados para pesquisadores. Sou praticamente o único usuário pesado do banco de dados, meus colegas estão mais envolvidos na parte do trabalho "inserir e limpar os dados". Portanto, posso fazer praticamente o que quiser com os índices, funções, etc., desde que a estrutura da tabela em si não seja muito alterada.

Atualização 4 - O quê? Não entendi, estava tentando fazer um belo gráfico para mostrar a curva exponencial "tempo gasto versus linhas solicitadas", então mudei minha consulta para obter bons números quadrados.

select 
    count(*)
FROM
    (SELECT TOP 10000 * FROM individus.Individus WHERE AnneeNaissance > 1901 AND AnneeDeces < 1911) i CROSS APPLY
    individus.GetAscendanceSimple(i.IndID) a

E isso funcionou em 10 segundos... Até tentei o TOP 10.000.000 e ainda rápido, então só tenho que colocar um número arbitrário grande para que todos os meus casos sejam cobertos e ele rode tão rápido quanto eu esperava (O TOP é importante). Antes de colocar isso como solução, devo estar errado, não? Essa é uma solução realmente estúpida se for tudo o que precisamos fazer para corrigir o planejamento.

Sem subconsulta vs Com subconsulta

sql-server
  • 2 2 respostas
  • 175 Views

2 respostas

  • Voted
  1. Best Answer
    Erik Darling
    2024-01-28T00:35:41+08:002024-01-28T00:35:41+08:00

    Quanto mais olho para isso, mais dúvidas tenho em relação ao plano de consulta. Provavelmente isso não responderá à sua pergunta na primeira tentativa, mas vamos começar.

    um

    Primeiro, o XML do plano indica que você está no SQL Server 2022 RTM (16.0.1000.6), o que coloca 11 atualizações cumulativas atrás das atuais. Não estou culpando seus problemas, mas não custa nada estar atualizado.

    dos

    Em segundo lugar, e mais intrigante: você está na edição Standard ou Enterprise do SQL Server ou Individusuma coluna armazena índice em algum lugar?

    A razão pela qual pergunto é porque o otimizador decidiu que um operador em seu plano deveria ser executado em modo lote:

    NOZES

    Na ausência de um índice de armazenamento de coluna, isso só acontece na Enterprise Edition com o recurso Intelligent Query Processing, Batch Mode On Row Store.

    O que torna isso ainda mais confuso é que seu plano só é executado com um Grau de Paralelismo (DOP) de 2. Limitar as operações do modo em lote a um DOP de 2 é algo que apenas a Standard Edition faz.

    Normalmente eu diria que você está em uma caixa com pouca potência, mas o plano de consulta também indica que você tem 12 threads disponíveis para uma consulta paralela. Talvez o DOP esteja limitado a dois lugares que o otimizador desconhece.

    EstimatedAvailableDegreeOfParallelism="12"
    

    Isso se torna lamentável no seu caso no Hash Match Aggregate mencionado acima, porque um thread fica parado por cerca de 22 segundos aguardando no HTBUILD.

    <Wait WaitType="HTBUILD" WaitTimeMs="22113" WaitCount="2" />
    

    Aqui estão as estatísticas de tempo real para o agregado de correspondência de hash. Observe a alta duração (tempo de relógio) versus tempo de CPU muito baixo.

    NOZES

    Uma coisa que você deve tentar é encerrar sua consulta com OPTION(USE HINT('DISALLOW_BATCH_MODE'), MAXDOP 8);.

    tres

    Deixando tudo isso de lado, sua consulta tem outras esperas bem estranhas.

     <Wait WaitType="PAGELATCH_SH" WaitTimeMs="24798" WaitCount="7034617" />
     <Wait WaitType="PAGELATCH_EX" WaitTimeMs="23758" WaitCount="6969758" />
     
    

    PAGELATCH_SH e PAGELATCH_EX combinados representam aproximadamente 49 segundos de tempo de espera enquanto sua consulta é executada. Aqui estão a maioria dos horários do operador em seu plano antes do agregado de correspondência de hash do modo em lote:

    NOZES

    Coloquei setas apontando para dois operadores que usam tempdb para armazenamento, porque às vezes (mas nem sempre, claro) aquelas esperas PAGELATCH_XX de que acabei de falar estão associadas à atividade lá. Não há como contornar esses spools em um plano de execução de expressão de tabela comum recursiva que eu conheça.

    Ler os tempos dos operadores é um pouco peculiar. No Modo Linha, os operadores pais mostram não apenas seu tempo, mas também o tempo gasto nos operadores filhos. No Modo Lote cada operador mostra apenas o tempo gasto.

    Um dos motivos pelos quais mencionei a possível contenção de tempdb aqui é que seu Lazy Index Spool fica bastante grande, com cerca de 14 milhões de linhas, e como carregá-lo com dados é a única coisa que geraria esperas de PAGELATCH_EX, vale a pena verificar.

    está bem então

    Dito isso, a estimativa de cardinalidade não é a raiz do seu problema aqui, e usar um MSTVF, que usaria uma variável de tabela, provavelmente não ajudaria na sua situação de tempdb ou na sua situação de estimativa de cardinalidade.

    Embora as variáveis ​​de tabela nas versões mais recentes do SQL Server possam obter estimativas de cardinalidade no nível da tabela, elas ainda não obtêm nenhum histograma no nível da coluna criado para elas, mesmo se você adicionar um índice. Eles também causarão uma zona serial no seu plano, sempre que os dados forem modificados neles (no seu caso, na inserção) e quando a variável da tabela retornada for lida pela consulta externa.

    Edite sua pergunta para adicionar detalhes sobre a leitura. Tentarei acompanhar conforme o tempo permitir para editar minha resposta de acordo.

    quatro

    Não tenho seu banco de dados para experimentar, mas posso fazer uma tentativa razoável com minha cópia do banco de dados StackOverflow.

    Para começar, adicionei alguns índices:

    CREATE UNIQUE INDEX 
        HierarychyHelper 
    ON dbo.Posts 
        (ParentId, Id, OwnerUserId) 
    INCLUDE
        (Score) 
    WITH 
        (SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
    
    CREATE UNIQUE INDEX 
        HierarychyHelper 
    ON dbo.Comments 
        (PostId, Id) 
    INCLUDE
        (Score, UserId) 
    WITH 
        (SORT_IN_TEMPDB = ON, DATA_COMPRESSION = PAGE);
    

    E uma função semelhante à sua:

    CREATE OR ALTER FUNCTION
        dbo.HierarchyHelper
    (
        @PostId integer
    )
    RETURNS table 
    WITH SCHEMABINDING
    AS
    RETURN
    (
        WITH 
            postparent AS 
        (
            SELECT 
                p.Id, 
                p.ParentId, 
                p.OwnerUserId,
                p.Score
            FROM dbo.Posts AS p WITH(FORCESEEK)
            WHERE p.Id = @PostId
          
            UNION ALL
          
            SELECT 
                p2.Id, 
                p2.ParentId, 
                p2.OwnerUserId,
                p2.Score
            FROM postparent pp
            JOIN dbo.Posts AS p2 WITH(FORCESEEK)
              ON pp.Id = p2.ParentId
          
            UNION ALL
          
            SELECT 
                c.Id, 
                c.PostId, 
                c.UserId,
                c.Score
            FROM postparent pp
            JOIN dbo.Comments AS c WITH(FORCESEEK)
              ON  pp.Id = c.PostId
        )
        SELECT
            pp.Id,
            pp.ParentId,
            pp.OwnerUserId,
            pp.Score
        FROM postparent AS pp
    );
    GO
    

    And then a starting query that results in a Lazy Spool that gets about 14 million rows and change in it. I have some hints here to better match your environment.

    SELECT
        c = COUNT_BIG(*)
    FROM dbo.Posts AS p
    CROSS APPLY dbo.HierarchyHelper(p.Id) AS hh
    WHERE p.CreationDate >= '20130501'
    OPTION
    (
        USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_160'), 
        MAXDOP 8
    );
    

    That query takes about 1 minute for me. Part of the problem is that recursive common table expressions don't generally qualify for parallel plans, and that parallel plans involving Nested Loops joins really just run DOP copies of the serial plan. Because of that, they're really sensitive to uneven row distributions on parallel threads.

    The reason why the query with a TOP in it runs so much faster is because the Top operator introduces a serial zone in the query plan, which requires a Distribute Streams Exchange operator to move the rows back into a parallel portion of the plan.

    NOZES

    I can duplicate the behavior by copying your queries:

    SELECT
        c = COUNT_BIG(*)
    FROM dbo.Posts AS p
    CROSS APPLY dbo.HierarchyHelper(p.Id) AS hh
    WHERE p.CreationDate >= '20130501'
    OPTION
    (
        USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_160'), 
        MAXDOP 8
    );
    
    
    SELECT
        c = COUNT_BIG(*)
    FROM 
    (
        SELECT TOP (2147483647) 
            p.* 
        FROM dbo.Posts AS p 
        WHERE p.CreationDate >= '20130501'
    ) AS p
    CROSS APPLY dbo.HierarchyHelper(p.Id) AS hh
    OPTION
    (
        USE HINT('QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_160'), 
        MAXDOP 8
    );
    

    Há apenas uma diferença de tempo de cerca de 30 segundos para mim, mas o motivo está nos seus planos de consulta e nos meus. Aqui estão algumas capturas de tela da distribuição de linhas paralelas em seus dois novos exemplos de planos de consulta:

    NOZES

    É claro que esse problema não era óbvio nos planos de consulta originais que você compartilhou, porque o modo em lote na Standard Edition estava limitando você a um DOP de 2. Em um plano de modo totalmente em linha, onde você pode usar um DOP muito mais alto , a questão fica clara.

    Você pode aprender muito mais sobre isso na sessão de Adam Machanic sobre metas de linha .

    • 3
  2. J. Mini
    2024-01-25T05:55:31+08:002024-01-25T05:55:31+08:00

    Como você governou tudo além dos ITVFs, nunca obterá uma boa estimativa de cardinalidade com isso. Simplesmente não há como saber em tempo de compilação quantas linhas você obterá. As restrições do ITVF excluem quaisquer truques úteis, como OPTION (RECOMPILE)tabelas temporárias. Dito isto, não vejo nenhuma razão para você ter descartado funções com valor de tabela com múltiplas instruções. Você já pensou em usar variáveis ​​de tabela e OPTION (RECOMPILE)?

    Desafio do quadro: pare de usar CTEs recursivos para esta tarefa. hierarquiaid é a solução moderna. Melhor ainda, evite completamente o SQL, se puder. Este é um trabalho para um banco de dados gráfico.

    • -1

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve