9.5 et plus récent:
PostgreSQL 9.5 et support plus récent INSERT ... ON CONFLICT UPDATE
(et ON CONFLICT DO NOTHING
), ie upsert.
Comparaison avecON DUPLICATE KEY UPDATE
.
Explication rapide .
Pour l'utilisation, voir le manuel - en particulier la clause conflict_action dans le diagramme de syntaxe et le texte explicatif .
Contrairement aux solutions pour 9.4 et 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 relance.
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 la lecture maintenant .
9.4 et plus:
PostgreSQL n'a pas de fonction intégrée UPSERT
(ou MERGE
), et le faire efficacement face à une utilisation simultanée est très difficile.
Cet article décrit le 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
- Verrouillage de la table et fusion par lots
Boucle de relance de ligne individuelle
L'utilisation de sauts de ligne individuels dans une boucle de nouvelle tentative est l'option raisonnable si vous souhaitez que de nombreuses connexions essaient simultanément d'effectuer des insertions.
La documentation PostgreSQL contient une procédure utile qui vous permettra de le faire en 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 qu'en 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 les déclencheurs ou les 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 une mise à jour groupée comme décrit ci-dessous.
De nombreuses tentatives de solutions à ce problème ne prennent pas en compte les annulations, elles entraînent donc des mises à jour incomplètes. Deux transactions se font la course; l'un d'eux a réussi INSERT
s; l'autre obtient une erreur de clé en double et fait à la UPDATE
place. Les UPDATE
blocs en attente INSERT
de restauration ou de validation. Lorsqu'elle est annulée, la UPDATE
nouvelle vérification de la condition correspond à zéro ligne, donc même si UPDATE
les validations n'ont pas réellement fait l'upert que vous attendiez. Vous devez vérifier le nombre de lignes de résultats et réessayer si nécessaire.
Certaines solutions tentées ne tiennent pas compte non plus des 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 fonctionnent en même temps, 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, correspondant à zéro ligne et continuant. Ensuite, ils font tous les deux le EXISTS
test, qui a lieu avant le INSERT
. Les deux obtiennent zéro ligne, donc les deux font le INSERT
. Un échoue avec une erreur de clé en double.
C'est pourquoi vous avez besoin d'une boucle de réessai. Vous pourriez penser que vous pouvez éviter 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.
Veuillez ne pas lancer votre propre solution pour cela. Comme pour la mise en file d'attente des messages, c'est probablement faux.
Upsert en vrac avec serrure
Parfois, vous souhaitez effectuer une mise à niveau groupée, dans laquelle vous disposez d'un nouvel ensemble de données que vous souhaitez fusionner avec un ancien ensemble de données existant. Ceci est beaucoup plus efficace que les sauts de rangs individuels et devrait être préféré chaque fois que cela est possible.
Dans ce cas, vous suivez généralement le processus suivant:
CREATE
une TEMPORARY
table
COPY
ou insérez en masse les nouvelles données dans la table temporaire
LOCK
la table cible IN EXCLUSIVE MODE
. Cela permet à d'autres transactions SELECT
, mais sans apporter de modifications à la table.
Faites un UPDATE ... FROM
des enregistrements existants en utilisant les valeurs de la table temporaire;
Faites une INSERT
des 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
Et alors MERGE
?
Le standard SQL MERGE
a en fait une sémantique de concurrence d'accès mal définie et ne convient pas pour la mise à jour sans verrouiller d'abord une table.
C'est une instruction OLAP vraiment utile pour la fusion de données, mais ce n'est pas en fait une solution utile pour l'upsert concurrentiel sécurisé. Il y a beaucoup de conseils aux personnes utilisant d'autres SGBD MERGE
pour les upserts, mais c'est en fait faux.
Autres DB: