Nous avons une table de 2,2 Go dans Postgres avec 7 801 611 lignes. Nous y ajoutons une colonne uuid / guid et je me demande quel est le meilleur moyen de remplir cette colonne (car nous voulons lui ajouter une NOT NULL
contrainte).
Si je comprends bien Postgres, une mise à jour est techniquement une suppression et une insertion. Il s'agit donc en fait de reconstruire l'intégralité du tableau de 2,2 Go. De plus, nous avons un esclave qui court donc nous ne voulons pas que cela prenne du retard.
Existe-t-il un meilleur moyen que d’écrire un script qui le remplit progressivement au fil du temps?
postgresql
storage
ddl
Collin Peters
la source
la source
ALTER TABLE .. ADD COLUMN ...
ou faut-il y répondre également?Réponses:
Cela dépend beaucoup des détails de vos besoins.
Si vous disposez de suffisamment d'espace libre (au moins 110% de
pg_size_pretty((pg_total_relation_size(tbl))
) sur le disque et que vous pouvez vous permettre un verrou de partage pendant un certain temps et un verrou exclusif pendant très peu de temps , créez une nouvelle table incluant lauuid
colonne à l'aide deCREATE TABLE AS
. Pourquoi?Le code ci-dessous utilise une fonction du
uuid-oss
module supplémentaire .Verrouillez la table contre les modifications simultanées du
SHARE
mode (autorisant toujours les lectures simultanées). Les tentatives d'écriture sur la table attendent et finissent par échouer. Voir ci-dessous.Copiez l'intégralité de la table tout en remplissant la nouvelle colonne à la volée - en ordonnant éventuellement les lignes tout en respectant les instructions.
Si vous envisagez de réorganiser les lignes, veillez à définir la valeur la
work_mem
plus élevée possible (uniquement pour votre session, pas globalement).Ajoutez ensuite des contraintes, des clés étrangères, des index, des déclencheurs, etc. à la nouvelle table. Lors de la mise à jour de grandes parties d'une table, il est beaucoup plus rapide de créer des index à partir de rien que d'ajouter des lignes de manière itérative.
Lorsque la nouvelle table est prête, supprimez l'ancienne et renommez la nouvelle pour la remplacer par une nouvelle version. Seule cette dernière étape acquiert un verrou exclusif sur l’ancienne table pour le reste de la transaction - ce qui devrait être très court maintenant.
Vous devez également supprimer tout objet en fonction du type de table (vues, fonctions utilisant le type de table dans la signature, ...), puis les recréer.
Faites tout cela en une seule transaction pour éviter les états incomplets.
Cela devrait être le plus rapide. Toute autre méthode de mise à jour en place doit également réécrire la table entière, mais de manière plus coûteuse. Vous ne pouvez emprunter cette voie que si vous ne disposez pas de suffisamment d'espace libre sur le disque ou si vous ne pouvez vous permettre de verrouiller la table entière ou de générer des erreurs pour les tentatives d'écriture simultanées.
Qu'advient-il des écritures simultanées?
Une autre transaction (dans d'autres sessions) essayant de
INSERT
/UPDATE
/DELETE
dans la même table après que votre transaction soitSHARE
verrouillée attendra jusqu'à ce que le verrou soit libéré ou qu'un délai expire, selon la première éventualité. Ils échoueront dans les deux cas, car la table à laquelle ils essayaient d'écrire a été supprimée.La nouvelle table a un nouvel OID de table, mais les transactions simultanées ont déjà résolu le nom de la table en OID de la table précédente . Lorsque le verrou est enfin relâché, ils essaient de verrouiller la table eux-mêmes avant d'écrire et de constater qu'elle a disparu. Postgres répondra:
Où se
123456
trouve l'OID de l'ancienne table. Vous devez intercepter cette exception et réessayer les requêtes dans le code de votre application pour l'éviter.Si vous ne pouvez pas vous le permettre, vous devez conserver votre table d'origine.
Deux alternatives en gardant la table existante
Mettre à jour en place (éventuellement en exécutant la mise à jour sur de petits segments à la fois) avant d'ajouter la
NOT NULL
contrainte. Ajouter une nouvelle colonne avec des valeurs NULL et sansNOT NULL
contrainte est bon marché.Depuis Postgres 9.2, vous pouvez également créer une
CHECK
contrainte avecNOT VALID
:Cela vous permet de mettre à jour les lignes peu à peu - dans plusieurs transactions distinctes . Cela évite de conserver les verrous de ligne trop longtemps et permet également de réutiliser des lignes mortes. (Vous devrez exécuter
VACUUM
manuellement s'il n'y a pas assez de temps entre autovacuum et les autres.) Enfin, ajoutez laNOT NULL
contrainte et supprimez-laNOT VALID CHECK
:Réponse associée discuter
NOT VALID
plus en détail:Préparez le nouvel état dans une table temporaire ,
TRUNCATE
l'original et rechargez à partir de la table temporaire. Tout en une transaction . Vous devez toujoursSHARE
verrouiller votre ordinateur avant de préparer la nouvelle table pour éviter de perdre des écritures simultanées.Détails dans ces réponses connexes sur SO:
la source
LOCK
et sans leDROP
. Je ne pouvais que pousser des suppositions sauvages et inutiles. En ce qui concerne le point 2, veuillez examiner l'addendum à ma réponse.Je n'ai pas de "meilleure" réponse, mais j'ai une "moins mauvaise" réponse qui pourrait vous permettre de faire les choses assez rapidement.
Ma table avait des lignes de 2MM et les performances de la mise à jour étaient médiocres lorsque j'ai tenté d'ajouter une colonne d'horodatage secondaire à la première.
Après 40 minutes d’attente, j’ai essayé ceci sur un petit lot pour avoir une idée du temps que cela pouvait prendre - la prévision était d’environ 8 heures.
La réponse acceptée est définitivement meilleure - mais cette table est fortement utilisée dans ma base de données. Il y a quelques dizaines de tables avec FKEY dessus; Je voulais éviter de changer de touches étrangères sur autant de tables. Et puis il y a des points de vue.
Un peu de recherche de documents, d'études de cas et de StackOverflow, et j'ai eu le "A-Ha!" moment. Le drain ne se trouvait pas sur la mise à jour principale, mais sur toutes les opérations INDEX. Ma table comportait 12 index, quelques-uns pour les contraintes uniques, quelques-uns pour accélérer le planificateur de requêtes et quelques-uns pour la recherche en texte intégral.
Chaque ligne mise à jour ne fonctionnait pas uniquement sur un élément DELETE / INSERT, mais aussi sur la surcharge liée à la modification de chaque index et à la vérification des contraintes.
Ma solution consistait à supprimer tous les index et contraintes, à mettre à jour la table, puis à rajouter tous les index / contraintes.
Il a fallu environ 3 minutes pour écrire une transaction SQL ayant les conséquences suivantes:
Le script a pris 7 minutes pour s'exécuter.
La réponse acceptée est définitivement meilleure et plus appropriée ... et élimine pratiquement le besoin de temps d'arrêt. Dans mon cas, toutefois, il aurait fallu beaucoup plus de "développeurs" pour utiliser cette solution et nous avions une fenêtre d'indisponibilité planifiée de 30 minutes pour y parvenir. Notre solution en tenait compte dans 10.
la source