web-dev-qa-db-fra.com

Meilleure façon de supprimer des millions de lignes par ID

Je dois supprimer environ 2 millions de lignes de ma base de données PG. J'ai une liste d'ID que je dois supprimer. Cependant, de toute façon j'essaie de faire cela prend des jours.

J'ai essayé de les mettre dans une table et de le faire par lots de 100. 4 jours plus tard, cela fonctionne toujours avec seulement 297268 lignes supprimées. (J'ai dû sélectionner 100 identifiants dans une table d'ID, supprimer où dans cette liste, supprimer de la table d'ID les 100 que j'ai sélectionnés).

J'ai essayé:

DELETE FROM tbl WHERE id IN (select * from ids)

Cela prend aussi une éternité. Difficile d'évaluer combien de temps, car je ne vois pas sa progression jusqu'à ce qu'il soit terminé, mais la requête était toujours en cours d'exécution après 2 jours.

Je cherche simplement le moyen le plus efficace de supprimer d'une table lorsque je connais les ID spécifiques à supprimer, et il y a des millions d'ID.

62
Anthony Greco

Tout dépend ...

  • Supprimer tous les index (sauf celui de l'ID dont vous avez besoin pour la suppression)
    Recréez-les ensuite (= beaucoup plus rapide que les mises à jour incrémentielles des index)

  • Vérifiez si vous avez des déclencheurs qui peuvent être supprimés/désactivés en toute sécurité temporairement

  • Les clés étrangères font-elles référence à votre table? Peuvent-ils être supprimés? Supprimé temporairement?

  • En fonction de vos paramètres de vide automatique, il peut-être aide à exécuter VACUUM ANALYZE avant l'opération.

  • En supposant pas d'accès en écriture simultané aux tables concernées ou vous devrez peut-être verrouiller les tables exclusivement ou cette route peut ne pas être pour vous du tout.

  • Certains des points énumérés dans le chapitre correspondant du manuel Remplissage d'une base de données peuvent également être utiles, selon votre configuration.

  • Si vous supprimez de grandes parties de la table et que le reste tient dans la RAM, le moyen le plus rapide et le plus simple serait le suivant:

SET temp_buffers = '1000MB'; -- or whatever you can spare temporarily

CREATE TEMP TABLE tmp AS
SELECT t.*
FROM   tbl t
LEFT   JOIN del_list d USING (id)
WHERE  d.id IS NULL;      -- copy surviving rows into temporary table

TRUNCATE tbl;             -- empty table - truncate is very fast for big tables

INSERT INTO tbl
SELECT * FROM tmp;        -- insert back surviving rows.

De cette façon, vous n'avez pas à recréer des vues, des clés étrangères ou d'autres objets dépendants. Lisez à propos de temp_buffers réglage dans le manuel . Cette méthode est rapide tant que la table tient en mémoire, ou du moins la majeure partie. Sachez que vous pouvez perdre des données si votre serveur tombe en panne au milieu de cette opération. Vous pouvez envelopper tout cela dans une transaction pour la rendre plus sûre.

Exécutez ANALYZE par la suite. Ou VACUUM ANALYZE si vous n'avez pas suivi l'itinéraire tronqué, ou VACUUM FULL ANALYZE si vous souhaitez l'amener à sa taille minimale. Pour les grandes tables, considérez les alternatives CLUSTER/pg_repack:

Pour les petites tables, un simple DELETE au lieu de TRUNCATE est souvent plus rapide:

DELETE FROM tbl t
USING  del_list d
WHERE  t.id = d.id;

Lisez les Notes section pour TRUNCATE dans le manuel . En particulier (comme Pedro l'a également souligné dans son commentaire ):

TRUNCATE ne peut pas être utilisé sur une table qui a des références de clé étrangère provenant d'autres tables, sauf si toutes ces tables sont également tronquées dans la même commande. [...]

Et:

TRUNCATE ne déclenchera aucun ON DELETE déclencheurs pouvant exister pour les tables.

80
Erwin Brandstetter

Nous savons que les performances de mise à jour/suppression de PostgreSQL ne sont pas aussi puissantes qu'Oracle. Lorsque nous devons supprimer des millions ou des dizaines de millions de lignes, c'est vraiment difficile et prend beaucoup de temps.

Cependant, nous pouvons toujours le faire dans les dbs de production. Voici mon idée:

Tout d'abord, nous devons créer une table de journal avec 2 colonnes - id & flag (id fait référence à l'ID que vous souhaitez supprimer; flag peut être Y ou null, Y signifiant que l'enregistrement a bien été supprimé).

Plus tard, nous créons une fonction. Nous effectuons la tâche de suppression toutes les 10 000 lignes. Vous pouvez voir plus de détails sur mon blog . Bien qu'il soit en chinois, vous pouvez toujours y obtenir les informations souhaitées à partir du code SQL.

Assurez-vous que la colonne id des deux tables sont des index, car elle s'exécutera plus rapidement.

4
francs

Vous pouvez essayer de copier toutes les données de la table sauf les ID que vous souhaitez supprimer sur une nouvelle table, puis renommer puis échanger les tables (à condition que vous ayez suffisamment de ressources pour le faire).

Ce n'est pas un avis d'expert.

2

Deux réponses possibles:

  1. Votre table peut être associée à de nombreuses contraintes ou déclencheurs lorsque vous essayez de supprimer un enregistrement. Cela entraînera beaucoup de cycles de processeur et de vérification à partir d'autres tables.

  2. Vous devrez peut-être mettre cette déclaration dans une transaction.

2
Zaldy Baguinon

Assurez-vous d'abord que vous disposez d'un index dans les champs ID, à la fois dans la table que vous souhaitez supprimer et dans la table que vous utilisez pour les ID de suppression.

100 à la fois semble trop petit. Essayez 1000 ou 10000.

Il n'est pas nécessaire de supprimer quoi que ce soit de la table des ID de suppression. Ajoutez une nouvelle colonne pour un numéro de lot et remplissez-la avec 1000 pour le lot 1, 1000 pour le lot 2, etc. et assurez-vous que la requête de suppression inclut le numéro de lot.

2
Mark Ransom

La façon la plus simple de procéder consiste à supprimer toutes vos contraintes, puis à supprimer.

1
Vincent Agnello

Si le tableau que vous supprimez est référencé par some_other_table (et vous ne voulez pas supprimer les clés étrangères même temporairement), assurez-vous d'avoir un index sur la colonne référence dans some_other_table!

J'ai eu un problème similaire et j'ai utilisé auto_explain avec auto_explain.log_nested_statements = true, qui a révélé que delete faisait en fait des seq_scans sur some_other_table:

    Query Text: SELECT 1 FROM ONLY "public"."some_other_table" x WHERE $1 OPERATOR(pg_catalog.=) "id" FOR KEY SHARE OF x    
    LockRows  (cost=[...])  
      ->  Seq Scan on some_other_table x  (cost=[...])  
            Filter: ($1 = id)

Apparemment, il essaie de verrouiller les lignes de référence dans l'autre table (qui ne devrait pas exister, sinon la suppression échouera). Après avoir créé des index sur les tables de référence, la suppression a été plus rapide de plusieurs ordres de grandeur.

0
FunctorSalad