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

Comment UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) dans PostgreSQL?

9.5 et versions ultérieures :

PostgreSQL 9.5 et les versions plus récentes prennent en charge INSERT ... ON CONFLICT (key) DO UPDATE (et ON CONFLICT (key) DO NOTHING ), c'est-à-dire upsert.

Comparaison avec ON DUPLICATE KEY UPDATE .

Explication rapide.

Pour l'utilisation, voir le manuel - en particulier le conflict_action clause dans le diagramme de syntaxe et le texte explicatif.

Contrairement aux solutions pour 9.4 et les versions antérieures présentées ci-dessous, cette fonctionnalité fonctionne avec plusieurs lignes en conflit et ne nécessite pas de verrouillage exclusif ni de boucle de nouvelle tentative.

Le commit ajoutant la fonctionnalité est ici et la discussion autour de son développement est ici.

Si vous êtes sur 9.5 et n'avez pas besoin d'être rétrocompatible, vous pouvez arrêter de lire maintenant .

9.4 et versions antérieures :

PostgreSQL n'a pas de UPSERT intégré (ou MERGE ), et le faire efficacement face à une utilisation simultanée est très difficile.

Cet article traite du problème en détail utile.

En général vous devez choisir entre deux options :

  • Opérations d'insertion/mise à jour individuelles dans une boucle de nouvelle tentative ; ou
  • Verrouiller la table et effectuer une fusion par lots

Boucle de nouvelle tentative de ligne individuelle

L'utilisation d'upserts de ligne individuels dans une boucle de nouvelle tentative est l'option raisonnable si vous voulez que plusieurs connexions essaient simultanément d'effectuer des insertions.

La documentation PostgreSQL contient une procédure utile qui vous permettra de faire cela dans une boucle à l'intérieur de la base de données. Il protège contre les mises à jour perdues et les courses d'insertion, contrairement à la plupart des solutions naïves. Cela ne fonctionnera que dans READ COMMITTED mode et n'est sûr que si c'est la seule chose que vous faites dans la transaction. La fonction ne fonctionnera pas correctement si des déclencheurs ou des clés uniques secondaires provoquent des violations uniques.

Cette stratégie est très inefficace. Dans la mesure du possible, vous devez mettre le travail en file d'attente et effectuer à la place une mise à jour groupée comme décrit ci-dessous.

De nombreuses tentatives de solutions à ce problème ne tiennent pas compte des restaurations, elles entraînent donc des mises à jour incomplètes. Deux transactions s'affrontent ; l'un d'eux a réussi INSERT s ; l'autre obtient une erreur de clé en double et effectue une UPDATE Au lieu. La UPDATE bloque en attendant le INSERT pour revenir en arrière ou s'engager. Lorsqu'il est annulé, le UPDATE la re-vérification de la condition correspond à zéro ligne, donc même si le UPDATE commits, il n'a pas fait l'upsert que vous attendiez. Vous devez vérifier le nombre de lignes de résultats et réessayer si nécessaire.

Certaines tentatives de solutions ne prennent pas non plus en compte les races SELECT. Si vous essayez l'évidence et la simplicité :

-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.

BEGIN;

UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;

-- Remember, this is WRONG. Do NOT COPY IT.

INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);

COMMIT;

puis lorsque deux s'exécutent à la fois, il existe plusieurs modes de défaillance. L'un est le problème déjà discuté avec une nouvelle vérification de la mise à jour. Un autre est où les deux UPDATE en même temps, en faisant correspondre zéro ligne et en continuant. Ensuite, ils font tous les deux le EXISTS test, qui se produit avant le INSERT . Les deux obtiennent zéro ligne, donc les deux font le INSERT . L'un échoue avec une erreur de clé en double.

C'est pourquoi vous avez besoin d'une boucle de nouvelle tentative. Vous pourriez penser que vous pouvez empêcher les erreurs de clé en double ou les mises à jour perdues avec SQL intelligent, mais vous ne pouvez pas. Vous devez vérifier le nombre de lignes ou gérer les erreurs de clé en double (selon l'approche choisie) et réessayer.

S'il vous plaît, ne lancez pas votre propre solution pour cela. Comme avec la file d'attente des messages, c'est probablement faux.

Upsert en vrac avec serrure

Parfois, vous souhaitez effectuer une mise à jour en bloc, où vous avez un nouvel ensemble de données que vous souhaitez fusionner dans un ancien ensemble de données existant. C'est largement plus efficace que les upserts de lignes individuelles et devrait être préféré chaque fois que possible.

Dans ce cas, vous suivez généralement le processus suivant :

  • CREATE un TEMPORARY tableau

  • COPY ou insérer en masse les nouvelles données dans la table temporaire

  • LOCK la table cible IN EXCLUSIVE MODE . Cela permet aux autres transactions de SELECT , mais n'apportez aucune modification au tableau.

  • Faites une UPDATE ... FROM des enregistrements existants en utilisant les valeurs de la table temporaire ;

  • Faites un INSERT de lignes qui n'existent pas déjà dans la table cible ;

  • COMMIT , libérant le verrou.

Par exemple, pour l'exemple donné dans la question, en utilisant plusieurs valeurs INSERT pour remplir la table temporaire :

BEGIN;

CREATE TEMPORARY TABLE newvals(id integer, somedata text);

INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');

LOCK TABLE testtable IN EXCLUSIVE MODE;

UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;

INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;

COMMIT;

Lecture connexe

  • Page wiki UPSERT
  • UPSERTismes dans Postgres
  • Insérer, lors d'une mise à jour en double dans PostgreSQL ?
  • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
  • Mise à jour avec une transaction
  • SELECT ou INSERT est-il dans une fonction sujette à des conditions de concurrence ?
  • SQL MERGE sur le wiki PostgreSQL
  • La manière la plus idiomatique d'implémenter UPSERT dans Postgresql de nos jours

Qu'en est-il de MERGE ?

MERGE au standard SQL a en fait une sémantique de concurrence mal définie et ne convient pas à l'insertion sans verrouiller une table au préalable.

C'est une instruction OLAP vraiment utile pour la fusion de données, mais ce n'est pas vraiment une solution utile pour l'upsert sécurisé en concurrence. Il y a beaucoup de conseils aux personnes utilisant d'autres SGBD pour utiliser MERGE pour les upserts, mais c'est en fait faux.

Autres bases de données :

  • INSERT ... ON DUPLICATE KEY UPDATE dans MySQL
  • MERGE depuis MS SQL Server (mais voir ci-dessus à propos de MERGE problèmes)
  • MERGE d'Oracle (mais voir ci-dessus à propos de MERGE problèmes)