No entiendo a qué se refería Craig Ringer cuando comentó:
Esta solución está sujeta a actualizaciones perdidas si la transacción de inserción retrocede; no hay verificación para asegurar que la ACTUALIZACIÓN haya afectado las filas.
en https://stackoverflow.com/a/8702291/14731 . Proporcione una secuencia de eventos de muestra (por ejemplo, el subproceso 1 hace X, el subproceso 2 hace Y) que demuestre cómo podrían ocurrir las actualizaciones perdidas.
postgresql
concurrency
cte
upsert
Gili
fuente
fuente
Respuestas:
Creo que probablemente quise agregar ese comentario en la respuesta anterior, sobre dos declaraciones separadas. Fue hace más de un año, así que ya no estoy totalmente seguro.
La consulta basada en wCTE realmente no resuelve el problema que se supone que debe resolver, pero al revisarla nuevamente más de un año después, no veo la posibilidad de perder actualizaciones en la versión de wCTE.
(Tenga en cuenta que todas estas soluciones solo funcionarán bien si intenta cambiar exactamente una fila con cada transacción. Tan pronto como intente hacer múltiples cambios en una transacción, las cosas se complican debido a la necesidad de volver a intentar bucles en las reversiones. Como mínimo necesitaría usar un punto de guardado entre cada cambio).
Versión de dos estados sujeta a actualizaciones perdidas.
La versión que usa dos declaraciones separadas está sujeta a actualizaciones perdidas a menos que la aplicación verifique el recuento de filas afectadas de la
UPDATE
declaración y laINSERT
declaración y vuelva a intentar si ambas son cero.Imagine lo que sucede si tiene dos transacciones de forma
READ COMMITTED
aislada.UPDATE
(sin efecto)INSERT
(inserta una fila)UPDATE
(sin efecto, la fila insertada por TX1 aún no está visible)COMMIT
s.INSERT
*, que obtiene una nueva instantánea que puede ver la fila confirmada por TX1. LaEXISTS
cláusula devuelve verdadero, porque TX2 ahora puede ver la fila insertada por TX1.Entonces TX2 no tiene efecto. A menos que la aplicación verifique el recuento de filas de la actualización y la inserción y vuelva a intentar si ambos informan filas cero, no sabrá que la transacción no tuvo ningún efecto y continuará felizmente.
La única forma en que puede verificar los recuentos de filas afectados es ejecutarlo como dos declaraciones separadas en lugar de una declaración múltiple, o usar un procedimiento.
Puede usar el
SERIALIZABLE
aislamiento, pero aún necesitará un ciclo de reintento para lidiar con fallas de serialización.La versión wCTE protege contra el problema de las actualizaciones perdidas porque depende
INSERT
de siUPDATE
afecta a las filas, en lugar de en una consulta por separado.El wCTE no elimina violaciones únicas
La versión de CTE que se puede escribir aún no es un upsert confiable.
Considere dos transacciones que ejecutan esto simultáneamente.
Ambos ejecutan la cláusula VALUES.
Ahora ambos ejecutan la
UPDATE
porción. Como no hay filas que coincidan con laUPDATE
cláusula s where, ambas devuelven un conjunto de resultados vacío de la actualización y no realizan cambios.Ahora ambos corren la
INSERT
porción. Dado que lasUPDATE
filas devueltas cero para ambas consultas, ambos intentanINSERT
la fila.Uno tiene exito. Uno arroja una violación única y aborta.
Esto no es motivo de preocupación por la pérdida de datos, siempre y cuando la aplicación verifique los resultados de error de sus consultas (es decir, cualquier aplicación escrita de manera adecuada) y vuelva a intentarlo, pero hace que la solución no sea mejor que las versiones existentes de dos declaraciones. No elimina la necesidad de un bucle de reintento.
La ventaja que ofrece el wCTE sobre la versión existente de dos sentencias es que utiliza el resultado del
UPDATE
para decidir si lo haceINSERT
, en lugar de utilizar una consulta separada en la tabla. Eso es en parte una optimización, pero en parte protege contra un problema con la versión de dos estados que causa actualizaciones perdidas; vea abajo.Puede ejecutar el wCTE de forma
SERIALIZABLE
aislada, pero luego obtendrá fallas de serialización en lugar de infracciones únicas. No cambiará la necesidad de un bucle de reintento.El wCTE no parece ser vulnerable a las actualizaciones perdidas
Mi comentario sugirió que esta solución podría dar lugar a la pérdida de actualizaciones, pero al revisar eso creo que puede haber estado equivocado.
Hace más de un año, y no puedo recordar las circunstancias exactas, pero creo que probablemente me perdí el hecho de que los índices únicos tienen una excepción parcial de las reglas de visibilidad de la transacción para permitir que una transacción de inserción espere a que otra se inserte o ruede antes de continuar.
O tal vez me perdí el hecho de que
INSERT
en el wCTE está condicionado a siUPDATE
afecta a las filas, no a si la fila candidata existe en la tabla.Los conflictos
INSERT
en un índice único esperan confirmación / reversiónDigamos que se ejecuta una copia de la consulta, insertando una fila. El cambio aún no se ha comprometido. La nueva tupla existe en el montón y el índice único, pero aún no es visible para otras transacciones, independientemente de los niveles de aislamiento.
Ahora se ejecuta otra copia de la consulta. La fila insertada aún no es visible ya que la primera copia no se ha confirmado, por lo que la actualización no coincide con nada. La consulta continuará para intentar una inserción, que verá que otra transacción en curso está insertando esa misma clave y bloqueará la espera de que esa transacción se confirme o retroceda .
Si se confirma la primera transacción, la segunda fallará con una violación única, según lo anterior. Si la primera transacción retrocede, la segunda continuará con su inserción.
El
INSERT
ser dependiente delUPDATE
recuento de filas protege contra actualizaciones perdidasA diferencia del caso de dos declaraciones, no creo que el wCTE sea vulnerable a las actualizaciones perdidas.
Si
UPDATE
no tiene efecto,INSERT
siempre se ejecutará, ya que está estrictamente condicionado a siUPDATE
hizo algo, no al estado de la tabla externa. Por lo tanto, aún puede fallar con una violación única, pero no puede dejar de tener ningún efecto en silencio y perder la actualización por completo.fuente