A maneira mais eficiente de excluir em massa linhas do postgres

23

Gostaria de saber qual seria a maneira mais eficiente de excluir um grande número de linhas do PostgreSQL, esse processo faria parte de uma tarefa recorrente todos os dias para importar dados em massa (um delta de inserções + exclusões) em uma tabela. Pode haver milhares, potencialmente milhões de linhas para excluir.

Eu tenho um arquivo de chaves primárias, uma por linha. As duas opções em que eu pensava estavam ao longo das linhas abaixo, mas não conheço o suficiente dos internos do PostgreSQL para tomar uma decisão informada que seria melhor.

  • Execute uma DELETEconsulta para cada linha do arquivo, com uma WHEREchave primária simples (ou agrupe as exclusões em lotes den usando uma IN()cláusula)
  • Importe as chaves primárias para uma tabela temporária usando o COPY comando e exclua da tabela principal usando uma junção

Qualquer sugestão será muito apreciado!

Tarnfeld
fonte
1
A mesma pergunta foi respondida com mais detalhes aqui: stackoverflow.com/a/8290958
Simon

Respostas:

25

Sua segunda opção é muito mais limpa e funcionará bem o suficiente para fazer isso valer a pena. Sua alternativa é criar consultas gigantescas, o que será bastante trabalhoso para planejar e executar. Em geral, será melhor deixar o PostgreSQL fazer o trabalho aqui. Em geral, encontrei atualizações em dezenas de milhares de linhas da maneira que você está descrevendo para executar adequadamente, mas há uma coisa importante a ser evitada.

A maneira de fazer isso é usar um select e um join na sua exclusão.

DELETE FROM foo WHERE id IN (select id from rows_to_delete);

Sob nenhuma circunstância você deve fazer o seguinte com uma tabela grande:

DELETE FROM foo WHERE id NOT IN (select id from rows_to_keep);

Isso geralmente causa um antijoin de loop aninhado, o que torna o desempenho bastante problemático. Se você precisar seguir esse caminho, faça o seguinte:

DELETE FROM foo 
WHERE id IN (select id from foo f 
          LEFT JOIN rows_to_keep d on f.id = d.id
              WHERE d.id IS NULL);

O PostgreSQL geralmente é muito bom para evitar planos ruins, mas ainda existem casos envolvendo junções externas que podem fazer uma grande diferença entre planos bons e ruins.

Isso está vagando um pouco mais longe, mas acho que vale a pena mencionar por causa de quão fácil é ir do IN para o NOT IN e assistir ao tanque de desempenho da consulta.

Chris Travers
fonte
Isso ajudou muito, obrigado! No entanto, descobri que o uso de "combinação de consultas" é mais eficiente nesse caso específico. Por exemplo, IN ( select id from foo except select id from rows_to_keep ) veja postgresql.org/docs/9.4/static/queries-union.html
Ufos
1

Me deparei com essa pergunta porque tinha um problema semelhante. Estou limpando um banco de dados com mais de 300 milhões de linhas; o banco de dados final terá apenas cerca de 30% dos dados originais. Se você estiver enfrentando um cenário semelhante, é realmente mais fácil inserir uma nova tabela e indexar novamente em vez de excluir.

Faça algo como

CREATE temp_foo as SELECT * FROM foo WHERE 1=2;
INSERT INTO temp_foo (SELECT * FROM foo where foo.id IN (SELECT bar.id FROM BAR);

Com a indexação adequada em foo e bar, você pode evitar verificações de Seq.

Então você teria que re-indexar e renomear a tabela.

Niro
fonte