9.5 et plus récents :
Support de PostgreSQL 9.5 et plus récent INSERT ... ON CONFLICT (key) DO UPDATE
(et ON CONFLICT (key) DO NOTHING
), c'est-à-dire l'upsert.
Comparaison avec ON DUPLICATE KEY UPDATE
.
Explication rapide .
Pour l'utilisation, voir le manuel - notamment le conflit_action dans le diagramme syntaxique, et le texte explicatif .
Contrairement aux solutions pour les versions 9.4 et antérieures qui sont données ci-dessous, cette fonctionnalité fonctionne avec plusieurs lignes en conflit et ne nécessite pas de verrouillage exclusif ou de boucle de réessai.
Le commit ajoutant la fonctionnalité est ici y la discussion autour de son développement est ici .
Si vous êtes sous la version 9.5 et que vous n'avez pas besoin d'être rétrocompatible, vous pouvez arrêter de lire maintenant. .
9.4 et plus :
PostgreSQL ne dispose pas d'un système intégré de gestion de l'information. UPSERT
(ou MERGE
), et il est très difficile de le faire de manière efficace face à une utilisation simultanée.
Cet article aborde le problème de manière très détaillée .
En général, vous devez choisir entre deux options :
- des opérations individuelles d'insertion/mise à jour dans une boucle de réessai ; ou
- Verrouiller la table et faire une fusion par lots
Boucle de réessai de ligne individuelle
L'utilisation d'insertions de rangées individuelles dans une boucle de réessai est l'option raisonnable si vous voulez que de nombreuses connexions essaient simultanément d'effectuer des insertions.
La documentation de PostgreSQL contient une procédure utile qui vous permet 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. Elle ne fonctionne que dans READ COMMITTED
et n'est sûr que si c'est la seule chose que vous faites dans la transaction, cependant. 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. Chaque fois que cela est possible, vous devez mettre le travail en file d'attente et effectuer un upsert en masse comme décrit ci-dessous.
De nombreuses tentatives de solutions à ce problème ne prennent pas en compte les retours en arrière, ce qui entraîne des mises à jour incomplètes. Deux transactions s'affrontent, l'une d'entre elles réussit à INSERT
l'autre obtient une erreur de clé dupliquée et fait un UPDATE
à la place. Le site UPDATE
blocs en attente de la INSERT
pour faire un retour en arrière ou un commit. Lorsqu'il effectue un rollback, le UPDATE
ne correspond à aucune rangée, donc même si la condition UPDATE
commite qu'il n'a pas réellement fait l'upsert que vous attendiez. Vous devez vérifier le nombre de lignes du résultat et réessayer si nécessaire.
Certaines tentatives de solutions ne tiennent pas compte non plus des races SELECT. Si vous essayez la solution évidente et simple :
-- 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 fonctionnent en même temps, il y a plusieurs modes de défaillance. L'un d'entre eux est le problème déjà évoqué de la mise à jour et de la revérification. Un autre est celui où les deux UPDATE
en même temps, en faisant correspondre les rangées zéro et en continuant. Ensuite, ils font tous les deux le EXISTS
test, qui se produit avant le site INSERT
. Les deux obtiennent des rangs zéro, donc les deux font le INSERT
. L'un d'eux échoue avec une erreur de clé dupliquée.
C'est pourquoi vous avez besoin d'une boucle de réessai. Vous pourriez penser que vous pouvez éviter les erreurs de clés dupliquées ou les mises à jour perdues avec un SQL intelligent, mais ce n'est pas le cas. Vous devez vérifier le nombre de lignes ou gérer les erreurs de clés dupliquées (selon l'approche choisie) et réessayer.
Veuillez ne pas élaborer votre propre solution pour ce problème. Comme pour la mise en file d'attente des messages, c'est probablement une erreur.
Renvoi en vrac avec serrure
Il arrive parfois que l'on veuille effectuer un upsert en masse, c'est-à-dire que l'on souhaite fusionner un nouvel ensemble de données avec un ensemble de données existant plus ancien. Cette opération est largement est plus efficace que l'insertion de rangs individuels et doit être privilégiée chaque fois que cela est possible.
Dans ce cas, vous suivez généralement le processus suivant :
-
CREATE
a TEMPORARY
tableau
-
COPY
ou insérer en vrac les nouvelles données dans la table temporaire
-
LOCK
la table cible IN EXCLUSIVE MODE
. Cela permet à d'autres transactions de SELECT
mais sans apporter de modifications au tableau.
-
Faites un 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
en libérant la serrure.
Ainsi, pour l'exemple donné dans la question, l'utilisation d'une valeur multiple 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
Qu'en est-il MERGE
?
Norme SQL MERGE
a en fait une sémantique de concurrence mal définie et ne convient pas pour l'upserting sans verrouiller une table au préalable.
Il s'agit d'une instruction OLAP très utile pour la fusion de données, mais ce n'est pas une solution utile pour l'insertion ascendante à sécurité concurrentielle. 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 DBs :
1 votes
Duplicata possible de Insertion, sur mise à jour dupliquée dans PostgreSQL ?
11 votes
@MichaelHampton le but ici était de créer une version définitive qui ne soit pas embrouillée par de multiples réponses périmées - et verrouillée, pour que personne ne puisse rien y faire. Je ne suis pas d'accord avec le closevote.
1 votes
Pourquoi, alors cela deviendrait vite obsolète - et verrouillé, de sorte que personne ne pourrait rien y faire.
2 votes
@MichaelHampton Si vous êtes concerné, peut-être pourriez-vous signaler celui auquel vous êtes lié et demander qu'il soit déverrouillé afin qu'il puisse être nettoyé, puis nous pourrons le fusionner. Je suis juste malade d'avoir le seul close-as-dup évident pour upsert étant un tel désordre confus et erroné.
2 votes
Cette Q&R n'est pas verrouillée !
0 votes
J'ai trouvé cela plus utile - stackoverflow.com/a/36799500/80428