DELETE muito lento no PostgreSQL, solução alternativa?

30

Eu tenho um banco de dados no PostgreSQL 9.2 que possui um esquema principal com cerca de 70 tabelas e um número variável de esquemas por cliente estruturados de forma idêntica, com 30 tabelas cada. Os esquemas do cliente têm chaves estrangeiras que referenciam o esquema principal e não o contrário.

Comecei a preencher o banco de dados com alguns dados reais extraídos da versão anterior. O banco de dados atingiu cerca de 1,5 GB (espera-se aumentar para vários 10s GB em semanas) quando eu tive que fazer uma exclusão em massa em uma tabela muito central no esquema principal. Todas as chaves estrangeiras envolvidas estão marcadas em EXCLUIR CASCADE.

Não foi surpresa que isso levasse muito tempo, mas após 12 horas ficou claro que era melhor começar de novo, abandonando o banco de dados e iniciando a migração novamente. Mas e se eu precisar repetir essa operação mais tarde, quando o banco de dados estiver ativo e muito maior? Existem métodos alternativos e mais rápidos?

Seria muito mais rápido se eu escrevesse um script que navegasse pelas tabelas dependentes, começando na tabela mais distante da tabela central, excluindo as linhas dependentes tabela por tabela?

Um detalhe importante é que existem gatilhos em algumas das tabelas.

jd.
fonte
4
Após 5 anos, estou mudando a resposta aceita. DELETEs lentos quase sempre são causados ​​por índices ausentes em chaves estrangeiras que fazem referência direta ou indiretamente à tabela da qual está sendo excluída. Os gatilhos que disparam nas instruções DELETE também podem atrasar as coisas, embora a solução seja quase sempre fazê-los funcionar mais rapidamente (por exemplo, adicionando índices ausentes) e quase nunca desativar todos os gatilhos.
jd.

Respostas:

30

Eu tive um problema parecido. Acontece que esses ON DELETE CASCADEgatilhos estavam atrasando bastante as coisas, porque essas exclusões em cascata eram terrivelmente lentas.

Resolvi o problema criando índices nos campos de chave estrangeira nas tabelas de referência e passei de algumas horas para a exclusão a alguns segundos.

ailnlv
fonte
Uau, isso me ajudou a excluir 8 milhões de registros em alguns minutos. Mas o que não entendo é que minha tabela continha apenas referências a outras tabelas, nenhuma outra tabela contém referências a minha tabela. Então, qual é exatamente o efeito aqui? (Não estou usando ON DELETE CASCADE)
msrd0
2
Isso resolveu para mim também. Para quem está tentando fazer isso, é possível fazer uma EXPLAIN (ANALYZE, BUFFERS)consulta em uma única exclusão de linha e deve mostrar quais restrições de chave estrangeira demoraram mais (pelo menos para mim).
23630 Justin Workman
Mesmo, teve que excluir em cascata 600k linhas e no início estava demorando entre 2-10 por operação com 100% de uso da CPU. Agora, foram necessários apenas alguns minutos para excluir todos eles com 80% de uso da CPU.
Fillbotto # 18/18
É importante observar que, se você tiver uma referência estrangeira para qualquer lugar, a coluna de origem deverá ter um índice real ou o desempenho sofrerá. Não tenho certeza se o PRIMARYíndice é suficiente, mas o UNIQUEíndice definitivamente não é bom o suficiente para esse fim.
Mikko Rantalainen
26

Você tem poucas opções. A melhor opção é executar uma exclusão em lote para que os gatilhos não sejam acionados. Desative os gatilhos antes de excluir e, em seguida, reative-os. Isso economiza uma quantidade muito grande de tempo. Por exemplo:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

Uma chave importante aqui é que você deseja minimizar a profundidade das subconsultas. Nesse caso, convém configurar tabelas temporárias para armazenar informações relevantes, para evitar subconsultas profundas ao excluir.

Chris Travers
fonte
No meu caso, iniciei o comando DELETE FROM antes de ir para a cama e ainda não estava pronto quando retornei ao meu computador no dia seguinte. 100% de uso da CPU em um núcleo o tempo todo. Depois de desativar os gatilhos e tentar novamente, foram necessários 3 segundos para excluir 200k registros. Obrigado!
Nick Woodhams
13

O método mais fácil de resolver o problema é a consulta tempo detalhada do PostgreSQL: EXPLAIN. Para isso, você precisa encontrar no mínimo uma única consulta concluída, mas que leva mais tempo do que o esperado. Digamos que essa linha se pareceria

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

Em vez de realmente executar esse comando, você pode executar

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

A reversão no final permite executar isso sem realmente modificar o banco de dados, mas você ainda obtém o tempo detalhado do que levou quanto. Depois de executar isso, você pode descobrir na saída que algum gatilho causa grandes atrasos:

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

Como timeestá em ms (milissegundo), a verificação dessa contraint levou cerca de 12,3 segundos. Você precisa adicionar um novo INDEXsobre as colunas necessárias para que esse gatilho possa ser calculado com eficiência. Para referências de chave estrangeira, a coluna que faz referência a outra tabela deve ser indexada (ou seja, a coluna de origem, não a coluna de destino). O PostgreSQL não cria automaticamente esses índices para você e DELETEé a única consulta comum em que você realmente precisa desse índice. Como resultado, você pode acumular anos de dados até chegar ao caso em que DELETEé muito lento devido à falta de um índice.

Depois de corrigir o desempenho dessa restrição (ou outra coisa que demorou muito tempo), repita o comando in begin/ rollbackblock para comparar o novo tempo de execução com o anterior. Continue até que você esteja satisfeito com o tempo de resposta de exclusão de linha única (eu tenho uma consulta para ir de 25,6 segundos a 15 ms simplesmente adicionando índices diferentes). Em seguida, você pode prosseguir para concluir sua exclusão completa sem hacks.

(Observe que EXPLAINprecisa de uma consulta que possa ser concluída com êxito. Certa vez, tive um problema em que o PostgreSQL demorou muito para descobrir que uma exclusão violaria uma restrição de chave estrangeira e, nesse caso, EXPLAINnão pode ser usada porque não emitirá tempo para falha. Não conheço nenhuma maneira fácil de depurar problemas de desempenho nesse caso.)

Mikko Rantalainen
fonte
8

Desativar gatilhos pode ser uma ameaça à integridade do banco de dados e não pode ser recomendado; no entanto, se você tiver certeza de que sua operação é à prova de restrições, poderá desativar os acionadores, com o seguinte:SET session_replication_role = replica;

Execute o DELETEaqui.

Para restaurar gatilhos, execute: SET session_replication_role = DEFAULT;

Fonte aqui.

Pinimo
fonte
0

Se você tiver gatilhos ON DELETE CASCADE, esperamos que eles estejam lá por um motivo e, portanto, não devem ser desativados. Outro truque (ainda adicione seus índices) que funciona para mim é criar uma função de exclusão que exclui manualmente os dados começando com as tabelas no final da cascata e trabalha na direção da tabela principal. (É o mesmo que você faria se tivesse um gatilho ON DELETE RESTRICT)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

Nesse caso, exclua os dados no tablec, em seguida, tableb e tablea

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
cego
fonte