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 / 307901
Accepted
Matthew Moisen
Matthew Moisen
Asked: 2022-02-24 19:56:19 +0800 CST2022-02-24 19:56:19 +0800 CST 2022-02-24 19:56:19 +0800 CST

Atualização recursiva postgresql resultando em varredura sequencial

  • 772

Eu tenho uma tabela com uma chave estrangeira auto-referencial e gostaria de atualizar um determinado pai e todos os descendentes do pai com a mesma atualização recursiva.

Ao usar um UPDATE com uma CTE recursiva que retorna apenas 10 linhas de 25.000 linhas, o otimizador usa uma semijunção de hash com varredura sequencial na tabela que está sendo atualizada, em vez do loop aninhado mais otimizado e varredura de índice. Tem um tempo de execução lento de cerca de 5-10ms.

Aumentar o tamanho dessa tabela para 250 mil linhas levará ao uso da verificação de índice. Ironicamente, o tempo de execução é realmente muito mais rápido (~0,5 a 1,0ms), por uma ordem de grandeza, comparado a uma tabela de 25K (~5-10ms segundos), precisamente porque está usando o índice em vez de varredura sequencial.

Meu palpite aqui é que o otimizador é incapaz de executar primeiro o CTE e depois planejar a atualização, em vez disso, ele precisa planejar com antecedência e está assumindo incorretamente que o CTE está retornando um número muito maior de linhas do que realmente é.

O Postgres não permite dicas do otimizador de índice. Além de definir enable_seqscan como desativado na produção, existe alguma solução alternativa para que o postgres use o índice?


Configurar:

drop table emp;

create table emp (id int primary key, manager_id int, department text);
create index emp_manager_id on emp (manager_id);

insert into emp 
select i id, 
    case when mod(i, 10) = 0 then null else i - 1 end manager_id, 
    null department
from generate_series(0, 25000) as i;

analyze emp;
vacuum emp;

Aqui está a atualização DML. Isso atualiza apenas 10 linhas. Não importa se eu uso IN, EXISTS ou atualização do CTE recursivo, todos eles resultam em uma varredura sequencial

explain 
with recursive foo as (
    select id, manager_id, department
    from emp
    where id = 1000

    union all

    select emp.id, emp.manager_id, emp.department
    from emp join foo on emp.manager_id = foo.id
)
update emp
set department = 'IT'
where id in (select id from foo);

Resulta em

                                             QUERY PLAN
-----------------------------------------------------------------------------------------------------
 Update on emp  (cost=766.85..939.24 rows=101 width=74)
   CTE foo
     ->  Recursive Union  (cost=0.29..763.57 rows=101 width=40)
           ->  Index Scan using emp_pkey on emp emp_1  (cost=0.29..8.30 rows=1 width=40)
                 Index Cond: (id = 1000)
           ->  Nested Loop  (cost=0.29..75.32 rows=10 width=40)
                 ->  WorkTable Scan on foo foo_1  (cost=0.00..0.20 rows=10 width=4)
                 ->  Index Scan using emp_manager_id on emp emp_2  (cost=0.29..7.50 rows=1 width=40)
                       Index Cond: (manager_id = foo_1.id)
   ->  Hash Semi Join  (cost=3.28..175.67 rows=101 width=74)
         Hash Cond: (emp.id = foo.id)
         ->  Seq Scan on emp  (cost=0.00..145.01 rows=10001 width=14)
         ->  Hash  (cost=2.02..2.02 rows=101 width=32)
               ->  CTE Scan on foo  (cost=0.00..2.02 rows=101 width=32)

Explicar analisar dá o mesmo resultado. Estou usando explicar aqui por brevidade.


Essa semijunção de hash com varredura sequencial não é ideal, pois apenas 10 linhas de 25.000 estão sendo atualizadas. Um loop aninhado com uma varredura de índice seria ideal aqui.

Definir enable_seqscan=off reduz o tempo para ~0,1ms (de ~5-10ms)

Se eu não usar uma CTE recursiva, a seguinte atualização using generate_seriesmostra que o índice emp_id é usado corretamente para realizar a atualização por meio de um loop aninhado. Isso é o que eu esperaria da atualização recursiva do CTE.

explain 
update emp 
set department = 'IT' 
where id in (
    select i from generate_series(1000,1009) i
);


                                        QUERY PLAN
------------------------------------------------------------------------------------------
 Update on emp  (cost=0.43..83.59 rows=11 width=74)
   ->  Nested Loop  (cost=0.43..83.59 rows=11 width=74)
         ->  HashAggregate  (cost=0.14..0.25 rows=11 width=32)
               Group Key: i.i
               ->  Function Scan on generate_series i  (cost=0.00..0.11 rows=11 width=32)
         ->  Index Scan using emp_pkey on emp  (cost=0.29..7.58 rows=1 width=14)
               Index Cond: (id = i.i)

Se eu aumentar o número de linhas na tabela para 250K de 10K, o plano de explicação resultará no uso ideal do índice. No entanto, com varredura de 25.000 linhas/seq, leva ~5-10ms para executar. Com 250 mil linhas, a verificação do índice leva ~ 0,5-0,1 ms.

Meu palpite aqui é que o postgres não é capaz de executar primeiro o CTE e depois calcular um plano para a atualização. Ele precisa calcular um plano antes de executar o CTE. Portanto, o postgres não pode saber que apenas 10 linhas estão sendo retornadas do CTE e, em vez disso, precisa adivinhar o número. Portanto, o postgres está supondo que o CTE retornará algo como 1000 linhas, o que faz com que ele prefira a varredura sequencial quando a tabela contém apenas 25K. A razão pela qual minha tabela de 250K usa a varredura de índice, suponho, é que o postgres continua a adivinhar que o CTE está retornando 1000 linhas, mas de 250K uma varredura de índice faz mais sentido.


O Postgres não permite dicas do otimizador de índice. Além de definir enable_seqscan como desativado na produção, existe alguma solução alternativa para que o postgres use o índice?


A solução de @a_horse_with_no_name usando emp.id = any(array(select id from foo))é ótima. Isso resulta na seguinte explicação simples, que é um pouco diferente:

                                     QUERY PLAN
------------------------------------------------------------------------------------
 Update on emp  (cost=44.19..48.93 rows=10 width=46)
   CTE foo
     ->  Recursive Union  (cost=0.00..42.17 rows=101 width=11)
           ->  Seq Scan on emp emp_1  (cost=0.00..3.08 rows=1 width=11)
                 Filter: (id = 0)
           ->  Hash Join  (cost=0.33..3.71 rows=10 width=11)
                 Hash Cond: (emp_2.manager_id = foo.id)
                 ->  Seq Scan on emp emp_2  (cost=0.00..2.66 rows=166 width=11)
                 ->  Hash  (cost=0.20..0.20 rows=10 width=4)
                       ->  WorkTable Scan on foo  (cost=0.00..0.20 rows=10 width=4)
   InitPlan 2 (returns $2)
     ->  CTE Scan on foo foo_1  (cost=0.00..2.02 rows=101 width=4)
   ->  Seq Scan on emp  (cost=0.00..4.73 rows=10 width=46)
         Filter: (id = ANY ($2))

Alguém pode explicar a diferença entre essas duas partes:

Original com enable_seqscan=off:

   ->  Nested Loop  (cost=2.56..294.11 rows=101 width=74) (actual time=0.091..0.118 rows=10 loops=1)
         ->  HashAggregate  (cost=2.27..3.28 rows=101 width=32) (actual time=0.076..0.080 rows=10 loops=1)
               Group Key: foo.id
               Batches: 1  Memory Usage: 24kB
               ->  CTE Scan on foo  (cost=0.00..2.02 rows=101 width=32) (actual time=0.024..0.068 rows=10 loops=1)
         ->  Index Scan using emp_pkey on emp  (cost=0.29..2.88 rows=1 width=14) (actual time=0.003..0.003 rows=1 loops=10)
               Index Cond: (id = foo.id)

Usando any(array(...)):

   InitPlan 2 (returns $2)
     ->  CTE Scan on foo foo_1  (cost=0.00..2.02 rows=101 width=4)
   ->  Seq Scan on emp  (cost=0.00..4.73 rows=10 width=46)
         Filter: (id = ANY ($2))

Primeiro, minha consulta original resulta em um HashAggregate do cte recursivo foo.iddepois de executar a varredura CTE. Somente depois disso ele percorre o empíndice. Não entendo porque está fazendo isso. Usando any(array(...)), ele pulará esta etapa e simplesmente fará um loop aninhado sobre a varredura cte e a varredura de índice.

Segundo, e provavelmente o mais importante, usar any(array(...))resultados neste arquivo InitPlan 2. Acredito que o que está acontecendo aqui é que de any(array(...))alguma forma força o planejador de consultas a executá-las como duas consultas diferentes. Primeiro ele executa o CTE, que retorna apenas 10 linhas. Então, o planejador sabe que com apenas 10 linhas ele pode usar uma varredura de índice em vez de seqscan. Minha solução original, por algum motivo, não pode forçar o planejador de consultas a executá-las como duas consultas diferentes, portanto, o planejador de consultas não sabe de antemão quantas linhas estão sendo retornadas.

Alguma ideia?

postgresql cte
  • 1 1 respostas
  • 186 Views

1 respostas

  • Voted
  1. Best Answer
    a_horse_with_no_name
    2022-02-24T23:09:14+08:002022-02-24T23:09:14+08:00

    Isso parece sempre usar uma verificação de índice (pelo menos no Postgres 14)

    with recursive foo as (
        select id, manager_id, department
        from emp
        where id = 1000
    
        union all
    
        select emp.id, emp.manager_id, emp.department
        from emp 
          join foo on emp.manager_id = foo.id
    )
    update emp
    set department = 'IT'
    where emp.id = any (array(select id from foo))
    

    Se você tem discos rápidos (SSD), você pode querer considerar abaixar random_page_cost, para fazer com que o Postgres favoreça as verificações de índice em geral

    • 1

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

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