9.5 y posteriores:
PostgreSQL 9.5 y versiones posteriores admiten INSERT ... ON CONFLICT (key) DO UPDATE
(y ON CONFLICT (key) DO NOTHING
), es decir, upsert.
Comparación con ON DUPLICATE KEY UPDATE
.
Explicación rápida.
Para conocer el uso, consulte el manual, específicamente la conflict_action cláusula en el diagrama de sintaxis y el texto explicativo.
A diferencia de las soluciones para 9.4 y anteriores que se proporcionan a continuación, esta función funciona con varias filas en conflicto y no requiere un bloqueo exclusivo ni un bucle de reintento.
El compromiso de agregar la función está aquí y la discusión sobre su desarrollo está aquí.
Si tiene la versión 9.5 y no necesita compatibilidad con versiones anteriores, puede dejar de leer ahora .
9.4 y anteriores:
PostgreSQL no tiene ningún UPSERT
incorporado (o MERGE
) y hacerlo de manera eficiente frente al uso concurrente es muy difícil.
Este artículo analiza el problema con detalles útiles.
En general deberás elegir entre dos opciones:
- Operaciones individuales de inserción/actualización en un bucle de reintento; o
- Bloquear la tabla y fusionar por lotes
Bucle de reintento de fila individual
El uso de upserts de filas individuales en un bucle de reintento es la opción razonable si desea que muchas conexiones intenten realizar inserciones al mismo tiempo.
La documentación de PostgreSQL contiene un procedimiento útil que le permitirá hacer esto en un bucle dentro de la base de datos. Protege contra actualizaciones perdidas e inserta carreras, a diferencia de la mayoría de las soluciones ingenuas. Solo funcionará en READ COMMITTED
Sin embargo, solo es seguro si es lo único que hace en la transacción. La función no funcionará correctamente si los activadores o las claves únicas secundarias provocan infracciones únicas.
Esta estrategia es muy ineficiente. Siempre que sea práctico, debe hacer una cola de trabajo y hacer una inserción masiva como se describe a continuación.
Muchos intentos de solución a este problema no tienen en cuenta las reversiones, por lo que resultan en actualizaciones incompletas. Dos transacciones compiten entre sí; uno de ellos con éxito INSERT
s; el otro recibe un error de clave duplicada y realiza una UPDATE
en cambio. La UPDATE
bloques esperando el INSERT
para revertir o confirmar. Cuando se retrotrae, UPDATE
la condición de volver a verificar coincide con cero filas, por lo que aunque UPDATE
confirma que en realidad no ha hecho el upsert que esperabas. Tienes que comprobar el recuento de filas de resultados y volver a intentarlo cuando sea necesario.
Algunas soluciones intentadas tampoco tienen en cuenta las carreras SELECT. Si intentas lo obvio y 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;
luego, cuando dos funcionan a la vez, hay varios modos de falla. Uno es el problema ya discutido con una nueva verificación de actualización. Otro es donde tanto UPDATE
al mismo tiempo, haciendo coincidir cero filas y continuando. Luego ambos hacen EXISTS
prueba, que ocurre antes el INSERT
. Ambos obtienen cero filas, por lo que ambos hacen INSERT
. Uno falla con un error de clave duplicada.
Es por eso que necesita un ciclo de reintento. Puede pensar que puede evitar errores de claves duplicadas o actualizaciones perdidas con SQL inteligente, pero no puede. Debe verificar el recuento de filas o manejar los errores clave duplicados (según el enfoque elegido) y volver a intentarlo.
Por favor, no lance su propia solución para esto. Al igual que con la cola de mensajes, probablemente esté mal.
Inserción masiva con bloqueo
A veces, desea realizar una inserción masiva, donde tiene un nuevo conjunto de datos que desea fusionar en un conjunto de datos existente más antiguo. Esto es muy más eficiente que los saltos de fila individuales y debe preferirse siempre que sea práctico.
En este caso, normalmente sigue el siguiente proceso:
-
CREATE
unTEMPORARY
mesa -
COPY
o inserte de forma masiva los nuevos datos en la tabla temporal -
LOCK
la tabla de destinoIN EXCLUSIVE MODE
. Esto permite que otras transaccionesSELECT
, pero no realice ningún cambio en la tabla. -
Haz una
UPDATE ... FROM
de registros existentes usando los valores en la tabla temporal; -
Haz un
INSERT
de filas que aún no existen en la tabla de destino; -
COMMIT
, liberando el candado.
Por ejemplo, para el ejemplo dado en la pregunta, usando INSERT
de valores múltiples para llenar la tabla temporal:
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;
Lecturas relacionadas
- Página wiki de UPSERT
- UPSERTismos en Postgres
- Insertar, en actualización duplicada en PostgreSQL?
- http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
- Upsert con una transacción
- ¿Es SELECCIONAR o INSERTAR en una función propensa a condiciones de carrera?
- SQL
MERGE
en la wiki de PostgreSQL - La forma más idiomática de implementar UPSERT en Postgresql hoy en día
¿Qué pasa con MERGE
? ?
SQL estándar MERGE
en realidad tiene una semántica de concurrencia mal definida y no es adecuado para modificar sin bloquear primero una tabla.
Es una declaración OLAP realmente útil para la fusión de datos, pero en realidad no es una solución útil para upsert seguro de concurrencia. Hay muchos consejos para las personas que usan otros DBMS para usar MERGE
para upserts, pero en realidad está mal.
Otros DB:
INSERT ... ON DUPLICATE KEY UPDATE
en MySQLMERGE
desde MS SQL Server (pero vea arriba sobreMERGE
problemas)MERGE
de Oracle (pero vea arriba sobreMERGE
problemas)