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 la INSERT
declaración y vuelva a intentar si ambas son cero.
Imagine lo que sucede si tiene dos transacciones de forma READ COMMITTED
aislada.
- TX1 ejecuta el
UPDATE
(sin efecto)
- TX1 ejecuta el
INSERT
(inserta una fila)
- TX2 ejecuta el
UPDATE
(sin efecto, la fila insertada por TX1 aún no está visible)
- TX1
COMMIT
s.
- TX2 ejecuta el
INSERT
*, que obtiene una nueva instantánea que puede ver la fila confirmada por TX1. La EXISTS
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 si UPDATE
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 la UPDATE
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 las UPDATE
filas devueltas cero para ambas consultas, ambos intentan INSERT
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 hace INSERT
, 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 si UPDATE
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ón
Digamos 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 del UPDATE
recuento de filas protege contra actualizaciones perdidas
A 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 si UPDATE
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.