¿Por qué CTE está abierto a actualizaciones perdidas?


8

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.


1
Pregúntame algo sobre un comentario que dejé hace más de un año sobre un tema complejo ... ¡divertido! Ahora tengo que recordar cuál era el problema exacto. Reexaminando ahora.
Craig Ringer

Respuestas:


14

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 UPDATEdeclaración y la INSERTdeclaración y vuelva a intentar si ambas son cero.

Imagine lo que sucede si tiene dos transacciones de forma READ COMMITTEDaislada.

  • 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 COMMITs.
  • TX2 ejecuta el INSERT*, que obtiene una nueva instantánea que puede ver la fila confirmada por TX1. La EXISTSclá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 SERIALIZABLEaislamiento, 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 INSERTde si UPDATEafecta 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 UPDATEporción. Como no hay filas que coincidan con la UPDATEcláusula s where, ambas devuelven un conjunto de resultados vacío de la actualización y no realizan cambios.

  • Ahora ambos corren la INSERTporción. Dado que las UPDATEfilas devueltas cero para ambas consultas, ambos intentan INSERTla 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 UPDATEpara 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 SERIALIZABLEaislada, 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 INSERTen el wCTE está condicionado a si UPDATEafecta a las filas, no a si la fila candidata existe en la tabla.

Los conflictos INSERTen 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 INSERTser dependiente del UPDATErecuento 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 UPDATEno tiene efecto, INSERTsiempre se ejecutará, ya que está estrictamente condicionado a si UPDATEhizo 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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.