SUPPRESSION très lente dans PostgreSQL, solution de contournement?


30

J'ai une base de données sur PostgreSQL 9.2 qui a un schéma principal avec environ 70 tables et un nombre variable de schémas par client à structure identique de 30 tables chacun. Les schémas clients ont des clés étrangères référençant le schéma principal et non l'inverse.

Je viens de commencer à remplir la base de données avec des données réelles tirées de la version précédente. La base de données avait atteint environ 1,5 Go (elle devrait atteindre plusieurs dizaines de Go en quelques semaines) lorsque j'ai dû effectuer une suppression en masse dans une table très centrale du schéma principal. Toutes les clés étrangères concernées sont marquées ON DELETE CASCADE.

Il n'était pas surprenant que cela prenne beaucoup de temps, mais après 12 heures, il est devenu clair que je ferais mieux de recommencer, de supprimer la base de données et de relancer la migration. Mais que se passe-t-il si je dois répéter cette opération plus tard lorsque la base de données est active et beaucoup plus grande? Existe-t-il des méthodes alternatives plus rapides?

Serait-il beaucoup plus rapide si j'écrivais un script qui parcourra les tables dépendantes, en commençant par la table la plus éloignée de la table centrale, en supprimant les lignes dépendantes table par table?

Un détail important est qu'il existe des déclencheurs sur certaines tables.


4
Après 5 ans, je change la réponse acceptée. Les suppressions lentes sont presque toujours causées par des index manquants sur les clés étrangères qui référencent directement ou indirectement la table à supprimer. Les déclencheurs qui se déclenchent sur les instructions DELETE peuvent également ralentir les choses, bien que la solution soit presque toujours de les faire fonctionner plus rapidement (par exemple en ajoutant des index manquants) et presque jamais de désactiver tous les déclencheurs.
jd.

Réponses:


30

J'avais un problème similaire. En fin de compte, ces ON DELETE CASCADEdéclencheurs ralentissaient un peu les choses, car ces suppressions en cascade étaient terriblement lentes.

J'ai résolu le problème en créant des index sur les champs de clé étrangère sur les tables de référence, et je suis passé de quelques heures pour la suppression à quelques secondes.


Wow, cela m'a aidé à supprimer 8 millions d'enregistrements en quelques minutes. Mais ce que je ne comprends pas, c'est que ma table ne contenait que des références à d'autres tables, aucune autre table ne contient de références à ma table. Alors, quel est exactement l'effet ici? (Je n'utilise pas ON DELETE CASCADE)
msrd0

2
Cela m'a aussi résolu. Pour tous ceux qui essaient cela, vous pouvez effectuer une EXPLAIN (ANALYZE, BUFFERS)requête sur une suppression d'une seule ligne et cela devrait vous montrer quelles contraintes de clé étrangère ont pris le plus de temps (du moins c'est le cas pour moi).
Justin Workman

Idem, a dû supprimer sur 600k lignes en cascade et au début, il fallait entre 2 à 10 par opération avec 100% d'utilisation du processeur. Désormais, il n'a fallu que quelques minutes pour les supprimer tous avec 80% d'utilisation du processeur.
fillobotto

Il est important de noter que si vous avez une référence étrangère vers n'importe où, la colonne source doit avoir un index réel sinon les performances en souffriront. Je ne sais pas si l' PRIMARYindex est suffisant, mais l' UNIQUEindice n'est certainement pas assez bon à cet effet.
Mikko Rantalainen

26

Vous avez quelques options. La meilleure option consiste à exécuter une suppression par lots afin que les déclencheurs ne soient pas activés. Désactivez les déclencheurs avant de les supprimer, puis réactivez-les. Cela vous fait gagner beaucoup de temps. Par exemple:

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

Une clé majeure ici est que vous souhaitez minimiser la profondeur des sous-requêtes. Dans ce cas, vous souhaiterez peut-être configurer des tables temporaires pour stocker les informations pertinentes afin d'éviter les sous-requêtes approfondies lors de votre suppression.


Dans mon cas, j'ai démarré la commande DELETE FROM avant d'aller me coucher et cela n'a toujours pas été fait lorsque je suis revenu sur mon ordinateur le lendemain. 100% d'utilisation du processeur sur un cœur tout le temps. Après avoir désactivé les déclencheurs et réessayé, il a fallu 3 secondes pour supprimer 200 000 enregistrements. Merci!
Nick Woodhams du

13

La méthode la plus simple pour résoudre le problème consiste à interroger le calendrier détaillé de PostgreSQL: EXPLAIN. Pour cela, vous devez trouver au moins une seule requête qui se termine mais prend plus de temps que prévu. Disons que cette ligne ressemblerait à

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

Au lieu d'exécuter vraiment cette commande, vous pouvez le faire

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

Le rollback à la fin permet d'exécuter cela sans vraiment modifier la base de données mais vous obtenez toujours le timing détaillé de ce qui a pris combien. Après avoir exécuté cela, vous pouvez trouver dans la sortie qu'un déclencheur provoque d'énormes retards:

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

La valeur timeest en ms (millisecondes), il a donc fallu environ 12,3 secondes pour vérifier cette contrainte. Vous devez ajouter un nouveau INDEXsur les colonnes requises afin que ce déclencheur puisse être calculé efficacement. Pour les références de clé étrangère, la colonne qui fait référence à une autre table doit être indexée (c'est-à-dire la colonne source, pas la colonne cible). PostgreSQL ne crée pas automatiquement de tels index pour vous et DELETEest la seule requête courante où vous avez vraiment vraiment besoin de cet index. En conséquence, vous pouvez avoir accumulé des années de données jusqu'à ce que vous atteigniez le cas où DELETEest trop lent en raison de l'absence d'index.

Une fois que vous avez corrigé les performances de cette contrainte (ou une autre chose qui a pris trop de temps), répétez la commande dans begin/ rollbackblock pour pouvoir comparer le nouveau temps d'exécution au précédent. Continuez jusqu'à ce que vous soyez satisfait du temps de réponse de suppression d'une seule ligne (j'ai eu une requête pour passer de 25,6 secondes à 15 ms simplement en ajoutant différents index). Ensuite, vous pouvez procéder à votre suppression complète sans aucun piratage.

(Notez que EXPLAINnécessite une requête qui peut se terminer avec succès. J'ai eu un problème où PostgreSQL a mis trop de temps à comprendre qu'une suppression allait violer une contrainte de clé étrangère et dans ce cas EXPLAINne peut pas être utilisée car elle n'émettra pas de synchronisation pour l'échec Je ne connais aucun moyen facile de déboguer les problèmes de performances dans un tel cas.)


8

La désactivation des déclencheurs peut constituer une menace pour l'intégrité de la base de données et ne peut pas être recommandée; cependant, si vous êtes sûr que votre opération est à l'épreuve des défaillances de contraintes, vous pouvez désactiver les déclencheurs, comme suit:SET session_replication_role = replica;

Exécutez DELETEici.

Pour restaurer les déclencheurs, exécutez: SET session_replication_role = DEFAULT;

Source ici.


0

Si vous avez des déclencheurs ON DELETE CASCADE, ils sont là, espérons-le, pour une raison et ne doivent donc pas être désactivés. Une autre astuce (ajoutez toujours vos indices) qui fonctionne pour moi est de créer une fonction de suppression qui supprime manuellement les données en commençant par les tables à la fin de la cascade, et fonctionne vers la table principale. (C'est la même chose que si vous aviez un déclencheur 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)
);

Dans ce cas, supprimez les données dans tablec puis tableb puis tablea

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

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

$$ LANGUAGE SQL;
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.