Le moyen le plus efficace de supprimer en vrac des lignes de postgres

23

Je me demande quel serait le moyen le plus efficace de supprimer un grand nombre de lignes de PostgreSQL, ce processus ferait partie d'une tâche récurrente chaque jour pour importer en masse des données (un delta d'insertions + suppressions) dans une table. Il pourrait y avoir des milliers, voire des millions de lignes à supprimer.

J'ai un fichier de clés primaires, un par ligne. Les deux options auxquelles je pensais étaient dans le sens de ce qui suit, mais je ne connais pas / ne comprends pas suffisamment les internes de PostgreSQL pour prendre une décision éclairée qui serait la meilleure.

  • Exécutez une DELETErequête pour chaque ligne du fichier, avec une simple WHEREclé primaire (ou regroupez les suppressions par lots en nutilisant une IN()clause)
  • Importez les clés primaires dans une table temporaire à l'aide de la COPYcommande, puis supprimez-les de la table principale à l'aide d'une jointure

Toutes les suggestions seront très appréciées!

tarnfeld
la source
1
La même question a reçu une réponse plus détaillée ici: stackoverflow.com/a/8290958
Simon

Réponses:

25

Votre deuxième option est beaucoup plus propre et fonctionnera assez bien pour que cela en vaille la peine. Votre alternative est de construire des requêtes gigantesques qui seront assez pénibles à planifier et à exécuter. En général, vous feriez mieux de laisser PostgreSQL faire le travail ici. En général, j'ai trouvé des mises à jour sur des dizaines de milliers de lignes de la manière que vous décrivez pour fonctionner correctement, mais il y a une chose importante à éviter.

La façon de le faire est d'utiliser une sélection et une jointure dans votre suppression.

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

Vous ne devez en aucun cas procéder comme suit avec une grande table:

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

Cela provoquera généralement une boucle anti-jointure imbriquée qui rendra les performances plutôt problématiques. Si vous finissez par emprunter cette voie, faites-le à la place:

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);

PostgreSQL est généralement assez bon pour éviter les mauvais plans, mais il existe encore des cas impliquant des jointures externes qui peuvent faire une grande différence entre les bons et les mauvais plans.

Cela se promène un peu plus loin, mais je pense que cela vaut la peine d'être mentionné en raison de la facilité avec laquelle il est possible de passer de IN à NOT IN et de regarder le réservoir de performances de requête.

Chris Travers
la source
Cela a beaucoup aidé, merci! Cependant, j'ai trouvé que l'utilisation de "combinaison de requêtes" est plus efficace dans ce cas particulier. Par exemple, IN ( select id from foo except select id from rows_to_keep ) voir postgresql.org/docs/9.4/static/queries-union.html
Ufos
1

Je suis tombé sur cette question parce que j'avais un problème similaire. Je nettoie une base de données qui compte plus de 300 millions de lignes, la base de données finale ne contiendra qu'environ 30% des données d'origine. Si vous êtes confronté à un scénario similaire, il est en fait plus facile d'insérer dans une nouvelle table et de réindexer au lieu de supprimer.

Faites quelque chose comme

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);

Avec une indexation correcte sur foo et bar, vous pouvez éviter les analyses Seq.

Ensuite, vous devrez réindexer et renommer la table.

Niro
la source