PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment effectuer des mises à jour importantes non bloquantes dans PostgreSQL ?

Colonne / Ligne

... Je n'ai pas besoin que l'intégrité transactionnelle soit maintenue tout au long de l'opération, car je sais que la colonne que je modifie ne sera ni écrite ni lue pendant la mise à jour.

Toute UPDATE dans le modèle MVCC de PostgreSQL écrit une nouvelle version de la ligne entière . Si des transactions simultanées changent tout colonne de la même ligne, des problèmes de simultanéité chronophages surviennent. Détails dans le manuel. Connaître la même colonne ne sera pas touché par des transactions simultanées évite certaines complications possibles, mais pas les autres.

Index

Pour éviter d'être détourné vers une discussion hors sujet, supposons que toutes les valeurs de statut pour les 35 millions de colonnes sont actuellement définies sur la même valeur (non nulle), rendant ainsi un index inutile.

Lors de la mise à jour de toute la table (ou la majeure partie de celui-ci) Postgres n'utilise jamais d'index . Une analyse séquentielle est plus rapide lorsque toutes ou la plupart des lignes doivent être lues. Au contraire :la maintenance de l'index signifie un coût supplémentaire pour la UPDATE .

Performances

Par exemple, disons que j'ai une table appelée "commandes" avec 35 millions de lignes, et je veux faire ceci :

UPDATE orders SET status = null;

Je comprends que vous visez une solution plus générale (voir ci-dessous). Mais pour répondre à la vraie question demandé :Cela peut être traité en quelques millisecondes , quelle que soit la taille du tableau :

ALTER TABLE orders DROP column status
                 , ADD  column status text;

Le manuel (jusqu'à Postgres 10):

Lorsqu'une colonne est ajoutée avec ADD COLUMN , toutes les lignes existantes dans la table sont initialisées avec la valeur par défaut de la colonne (NULL si non DEFAULT clause est spécifiée). S'il n'y a pas de DEFAULT clause, il s'agit simplement d'un changement de métadonnées [...]

Le manuel (depuis Postgres 11):

Lorsqu'une colonne est ajoutée avec ADD COLUMN et un DEFAULT non volatile est spécifié, la valeur par défaut est évaluée au moment de l'instruction et le résultat est stocké dans les métadonnées de la table. Cette valeur sera utilisée pour la colonne pour toutes les lignes existantes. Si non DEFAULT est spécifié, NULL est utilisé. Dans aucun des cas, une réécriture de la table n'est requise.

Ajouter une colonne avec un DEFAULT volatil ou changer le type d'une colonne existante nécessitera la réécriture de la table entière et de ses index. [...]

Et :

La DROP COLUMN form ne supprime pas physiquement la colonne, mais la rend simplement invisible pour les opérations SQL. Les opérations d'insertion et de mise à jour ultérieures dans la table stockeront une valeur nulle pour la colonne. Ainsi, la suppression d'une colonne est rapide mais elle ne réduira pas immédiatement la taille sur disque de votre table, car l'espace occupé par la colonne supprimée n'est pas récupéré. L'espace sera récupéré au fur et à mesure que les lignes existantes seront mises à jour.

Assurez-vous de ne pas avoir d'objets dépendant de la colonne (contraintes de clé étrangère, index, vues, ...). Vous auriez besoin de supprimer / recréer ceux-ci. Sauf cela, de petites opérations sur la table de catalogue système pg_attribute fait le travail. Nécessite un verrou exclusif sur la table, ce qui peut poser problème en cas de charge simultanée importante. (Comme le souligne Buurman dans son commentaire.) À part cela, l'opération est une question de millisecondes.

Si vous souhaitez conserver une colonne par défaut, ajoutez-la dans une commande distincte . Le faire dans la même commande l'applique immédiatement à toutes les lignes. Voir :

  • Ajouter une nouvelle colonne sans verrou de table ?

Pour appliquer réellement la valeur par défaut, envisagez de le faire par lots :

  • PostgreSQL optimise-t-il l'ajout de colonnes avec des DEFAULT non NULL ?

Solution générale

dblink a été mentionné dans une autre réponse. Il permet l'accès aux bases de données Postgres "distantes" dans des connexions séparées implicites. La base de données "distante" peut être la base courante, réalisant ainsi des "transactions autonomes"  :ce que la fonction écrit dans la base de données "distante" est validé et ne peut pas être annulé.

Cela permet d'exécuter une seule fonction qui met à jour une grande table en parties plus petites et chaque partie est validée séparément. Évite de créer une surcharge de transaction pour un très grand nombre de lignes et, plus important encore, libère les verrous après chaque partie. Cela permet aux opérations simultanées de se dérouler sans trop de retard et rend les blocages moins probables.

Si vous n'avez pas d'accès simultané, cela n'est guère utile - sauf pour éviter le ROLLBACK après une exception. Considérez également SAVEPOINT pour ce cas.

Avis de non-responsabilité

Tout d'abord, beaucoup de petites transactions sont en fait plus chères. Cela n'a de sens que pour les grandes tables . Le sweet spot dépend de nombreux facteurs.

Si vous n'êtes pas sûr de ce que vous faites :une seule transaction est la méthode sûre . Pour que cela fonctionne correctement, les opérations simultanées sur la table doivent jouer le jeu. Par exemple :écritures simultanées peut déplacer une ligne vers une partition supposée déjà traitée. Ou les lectures simultanées peuvent voir des états intermédiaires incohérents. Vous êtes prévenu.

Instructions étape par étape

Le module supplémentaire dblink doit d'abord être installé :

  • Comment utiliser (installer) dblink dans PostgreSQL ?

La configuration de la connexion avec dblink dépend beaucoup de la configuration de votre cluster de base de données et des politiques de sécurité en place. Cela peut être délicat. Réponse ultérieure connexe avec plus de comment se connecter avec dblink :

  • Inserts persistants dans une UDF même si la fonction abandonne

Créer un FOREIGN SERVER et un USER MAPPING comme indiqué ici pour simplifier et rationaliser la connexion (sauf si vous en avez déjà une).
En supposant une serial PRIMARY KEY avec ou sans quelques lacunes.

CREATE OR REPLACE FUNCTION f_update_in_steps()
  RETURNS void AS
$func$
DECLARE
   _step int;   -- size of step
   _cur  int;   -- current ID (starting with minimum)
   _max  int;   -- maximum ID
BEGIN
   SELECT INTO _cur, _max  min(order_id), max(order_id) FROM orders;
                                        -- 100 slices (steps) hard coded
   _step := ((_max - _cur) / 100) + 1;  -- rounded, possibly a bit too small
                                        -- +1 to avoid endless loop for 0
   PERFORM dblink_connect('myserver');  -- your foreign server as instructed above

   FOR i IN 0..200 LOOP                 -- 200 >> 100 to make sure we exceed _max
      PERFORM dblink_exec(
       $$UPDATE public.orders
         SET    status = 'foo'
         WHERE  order_id >= $$ || _cur || $$
         AND    order_id <  $$ || _cur + _step || $$
         AND    status IS DISTINCT FROM 'foo'$$);  -- avoid empty update

      _cur := _cur + _step;

      EXIT WHEN _cur > _max;            -- stop when done (never loop till 200)
   END LOOP;

   PERFORM dblink_disconnect();
END
$func$  LANGUAGE plpgsql;

Appel :

SELECT f_update_in_steps();

Vous pouvez paramétrer n'importe quelle partie selon vos besoins :le nom de la table, le nom de la colonne, la valeur, ... assurez-vous simplement de nettoyer les identifiants pour éviter l'injection SQL :

  • Nom de table en tant que paramètre de fonction PostgreSQL

Évitez les UPDATE vides :

  • Comment puis-je (ou puis-je) SELECT DISTINCT sur plusieurs colonnes ?