9.5 y más reciente:
PostgreSQL 9.5 y soporte más reciente INSERT ... ON CONFLICT UPDATE
(y ON CONFLICT DO NOTHING
), es decir, upsert.
Comparación conON DUPLICATE KEY UPDATE
.
Explicación rápida .
Para el uso, consulte el manual , específicamente la cláusula conflict_action en el diagrama de sintaxis y el texto explicativo .
A diferencia de las soluciones para 9.4 y versiones anteriores que se proporcionan a continuación, esta función funciona con múltiples filas en conflicto y no requiere bloqueo exclusivo o un ciclo de reintento.
El commit que agrega la característica está aquí y la discusión sobre su desarrollo está aquí .
Si está en 9.5 y no necesita ser compatible con versiones anteriores, puede dejar de leer ahora .
9.4 y mayores:
PostgreSQL no tiene ninguna función incorporada UPSERT
(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, debe elegir entre dos opciones:
- Operaciones individuales de inserción / actualización en un ciclo de reintento; o
- Bloqueo de la mesa y combinación de lotes
Bucle de reintento de fila individual
El uso de upserts de fila individuales en un bucle de reintento es la opción razonable si desea que muchas conexiones intenten simultáneamente realizar inserciones.
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. Sin READ COMMITTED
embargo, solo funcionará en modo y solo es seguro si es lo único que haces en la transacción. La función no funcionará correctamente si los disparadores o las teclas únicas secundarias causan violaciones únicas.
Esta estrategia es muy ineficiente. Siempre que sea práctico, debe poner en cola el trabajo y hacer un upsert masivo como se describe a continuación.
Muchos intentos de solución a este problema no consideran las reversiones, por lo que resultan en actualizaciones incompletas. Dos transacciones corren entre sí; uno de ellos con éxito INSERT
s; el otro obtiene un error de clave duplicada y lo hace en su UPDATE
lugar. Los UPDATE
bloques que esperan INSERT
que retrocedan o se comprometan. Cuando se revierte, la UPDATE
nueva comprobación de la condición coincide con cero filas, por lo que a pesar de las UPDATE
confirmaciones, en realidad no ha realizado la recuperación que esperaba. Debe verificar el recuento de filas de resultados y volver a intentarlo cuando sea necesario.
Algunas soluciones intentadas tampoco logran considerar 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 se ejecutan 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 ambos UPDATE
al mismo tiempo, coinciden con cero filas y continúan. Luego ambos hacen la EXISTS
prueba, que ocurre antes del INSERT
. Ambos obtienen cero filas, por lo que ambos hacen el INSERT
. Uno falla con un error de clave duplicada.
Es por eso que necesita un bucle de reintento. Puede pensar que puede evitar errores clave duplicados o actualizaciones perdidas con SQL inteligente, pero no puede. Debe verificar los recuentos de filas o manejar errores clave duplicados (según el enfoque elegido) y volver a intentarlo.
Por favor, no presente su propia solución para esto. Al igual que con la cola de mensajes, probablemente esté mal.
Upsert a granel con cerradura
A veces, desea realizar una inserción ascendente masiva, donde tiene un nuevo conjunto de datos que desea fusionar en un conjunto de datos existente más antiguo. Esto es mucho más eficiente que las filas superiores individuales y debe preferirse siempre que sea práctico.
En este caso, normalmente sigue el siguiente proceso:
CREATE
una TEMPORARY
mesa
COPY
o inserte en masa los nuevos datos en la tabla temporal
LOCK
la mesa de destino IN EXCLUSIVE MODE
. Esto permite otras transacciones SELECT
, pero no realiza ningún cambio en la tabla.
Realice uno UPDATE ... FROM
de los registros existentes utilizando los valores de la tabla temporal;
Haga una INSERT
de las filas que aún no existen en la tabla de destino;
COMMIT
, soltando la cerradura.
Por ejemplo, para el ejemplo dado en la pregunta, usando valores múltiples INSERT
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;
Lectura relacionada
¿Qué hay de MERGE
?
El estándar SQL en MERGE
realidad tiene una semántica de concurrencia mal definida y no es adecuado para la inserción 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 la inserción segura de concurrencia. Hay muchos consejos para las personas que usan otros DBMS para usar MERGE
en los upserts, pero en realidad está mal.
Otros DB: