Actualización perdida

Actualización perdida (incluida la actualización perdida en inglés ) se refiere en la informática a un error que puede ocurrir en una pluralidad de accesos de escritura paralelos a una información compartida. Si dos transacciones cambian la misma información, los cambios en la primera pueden ser reemplazados inmediatamente por los cambios en la segunda.

No importa si la "información compartida" está en un archivo , en una tabla de base de datos o en la memoria principal .

Leer y escribir sin interacción con un usuario

ejemplo

Una cadena de oficinas de reserva anticipada almacena la cantidad de boletos vendidos para cada evento. Ya se habían vendido 100 tarjetas cuando se devolvieron cinco en una caja registradora. Al mismo tiempo, se compran tres boletos en una segunda caja registradora. El sistema de la primera caja registradora resta las 5 tarjetas devueltas de las 100 y escribe el nuevo valor (95) en la base de datos. El segundo sistema de caja registradora suma las tres tarjetas que se acaban de vender a 100 y también escribe este valor (103) en la base de datos. Se pierde el valor escrito primero y el resultado final es incorrecto (103 entradas vendidas, aunque en realidad solo hay 98).

hora Programa 1

Retirar 5 cartas

Número almacenado

entradas vendidas

Programa 2

Vende 3 cartas

0 100
1 Leer la cantidad de boletos vendidos

Resultado: 100

100
2 100 Leer la cantidad de boletos vendidos

Resultado: 100

3 Se retiran 5 cartas

Calcular nuevo valor: 100-5 = 95

Escriba un nuevo valor (95)

95
Cuarto 103 Se venden 3 cartas

Calcular nuevo valor: 100 + 3 = 103

Escriba un nuevo valor (103)

Métodos para solucionar el problema

Cuando se realiza el acceso de lectura, la información compartida se bloquea para que no pueda ser modificada por otro programa mientras tanto.

Los mecanismos de bloqueo necesarios para ello los proporcionan los distintos sistemas de gestión de datos:

  • el bloqueo de compartir permite el acceso de lectura a cualquier número de transacciones.
  • el bloqueo exclusivo solo permite el acceso de escritura a una sola transacción. Durante este tiempo, ninguna otra transacción puede leer los datos bloqueados.

Estos mecanismos de bloqueo son utilizados por la mayoría de los sistemas operativos y bases de datos, así como por los administradores de búfer para manejar el acceso concurrente.

El nivel de aislamiento RR

El nivel de aislamiento RR (lectura repetible) se menciona a menudo como una solución al problema de actualización perdida.

La mayoría de RDBMS ofrecen diferentes niveles de aislamiento. Lectura repetible significa que un bloqueo compartido permanece en su lugar hasta el final de una transacción y no vuelve a desaparecer inmediatamente después del acceso de lectura.

Si usa el nivel de aislamiento RR, debe asegurarse de que no se produzcan interbloqueos.

El primer programa escribe un bloqueo compartido que no se vuelve a eliminar. El segundo programa también escribe un bloqueo compartido. Ahora, el primer programa quiere convertir el bloqueo compartido en un bloqueo exclusivo, pero eso no es posible mientras el segundo programa aún mantenga su bloqueo compartido. Un poco más tarde, el segundo programa también quiere convertir su bloqueo compartido en un bloqueo exclusivo. Ahora todo el mundo está esperando al otro. Esta es la clásica situación de estancamiento.

hora Programa 1

Retirar 5 cartas

Bloqueos de

Prog. 1

Número almacenado

entradas vendidas

Bloqueos de

Prog. 2

Programa 2

Vende 3 cartas nuevas

0 100
1 Leer la cantidad de boletos vendidos

Resultado: 100

S-lock

desde P1

100
2 S-lock

desde P1

100 S-lock

desde P2

Leer la cantidad de boletos vendidos

Resultado: 100

3 Se retiran 5 cartas

Calcular nuevo valor: 100−5 = 95

Solicitar bloqueo exclusivo

esperando P2

S-lock

desde P1

100 S-lock

desde P2

Cuarto esperando P2 S-lock

desde P1

100 S-lock

desde P2

Se venden 3 cartas

Calcular nuevo valor: 100 + 3 = 103

Solicitar bloqueo exclusivo

esperando P1

5 esperando P2 S-lock

desde P1

100 S-lock

desde P2

esperando P1

Ahora depende de cómo reaccione el RDBMS en tal caso. Algunos RDBMS, p. Ej. B. DB2 : puede parametrizar para que una transacción solo espere un cierto tiempo por los recursos bloqueados. Tan pronto como haya pasado este tiempo y el recurso aún esté bloqueado, la transacción se revierte y el programa recibe un mensaje de error (SQLCODE −911). Esa sería una solución al problema, porque la reversión de la primera transacción también eliminó el bloqueo compartido y la segunda transacción ahora obtiene el bloqueo exclusivo que estaba esperando. Si el SQLCODE −911 se consulta específicamente en el programa, entonces el programa puede leer el registro nuevamente en tal caso y ahora recibe el valor que el otro programa acaba de escribir. Entonces no se pierde ninguna actualización.

hora Programa 1

Retirar 5 cartas

Bloqueos de

Prog. 1

Número almacenado

entradas vendidas

Bloqueos de

Prog. 2

Programa 2

Vende 3 cartas nuevas

Sexto SQLCODE −911

Retroceder

100 S-lock

desde P2

esperando P1
Séptimo 103 X-Lock

desde P2

Escriba un nuevo valor (103)
Octavo Leer la cantidad de boletos vendidos

Solicitar bloqueo para compartir

esperando P2

103 X-Lock

desde P2

9 Solicitar bloqueo para compartir

esperando P2

103 cometer
10 Compartir bloqueo recibido

Leer la cantidad de boletos vendidos

Resultado: 103

S-lock

desde P1

103
11 Se retiran 5 cartas

Calcular nuevo valor: 103-5 = 98

Solicitar X-Lock

Escriba un nuevo valor (98)

X-Lock

desde P1

98
12 Cometer 98

El segundo intento, como el primer intento, puede fallar si un tercer programa ha establecido un bloqueo compartido en el registro mientras tanto. Por lo tanto, la lectura y la escritura en el programa deben realizarse en bucle.

Ahora puede considerar si el bucle debe repetirse con la frecuencia que desee o si el procesamiento debe abandonarse después de n intentos y terminar con un mensaje de error.

  Schleife
     Select ...
     Neuen Wert berechnen
     Update ...
     if (sqlcode not in (0, −911)) return(FEHLER)
  Until (sqlcode = 0 or Anz_Schleifen_Durchlaeufe > n)
  if (sqlcode <> 0) return(FEHLER)

Si el RDBMS espera hasta que intervenga un administrador en caso de un interbloqueo, esta variante no es una buena solución.

Serializar el procesamiento

Si el primer programa bloquea la información exclusivamente durante el acceso de lectura, el segundo programa tiene que esperar con su acceso de lectura. Tan pronto como el primer programa también haya realizado el acceso de escritura y libere el recurso nuevamente, el segundo programa puede continuar su procesamiento. Esta variante es una serialización forzada del procesamiento.

Si la información se guarda en un archivo , el programa debe abrir el archivo inmediatamente para escribir.

Si la información se almacena en una tabla de base de datos, entonces la oración puede, por ejemplo, B. se puede leer con un CURSOR PARA ACTUALIZAR, o se puede bloquear toda la tabla con LOCK TABLE EN MODO EXCLUSIVO.

Atomizar el acceso de lectura y escritura

Si no se requiere ningún procesamiento adicional entre el acceso de lectura y el acceso de escritura, estos dos accesos también se pueden combinar (ver Operación Atómica ).

Para un RDBMS , el acceso al ejemplo podría ser:

  update Tab
  set Anzahl_verkaufte_Karten = Anzahl_verkaufte_Karten + :Aktueller_Verkauf

Esto elimina la necesidad de un acceso de lectura independiente. Ya no se puede producir una actualización perdida.

Leer y escribir con la interacción del usuario

En la práctica, el problema de la actualización perdida también ocurre a menudo en relación con las interacciones del usuario. Esto significa que la información leída se envía al usuario y puede ser modificada por él. La información modificada se vuelve a escribir. Si otro usuario desea cambiar la misma información, es posible que se pierdan los cambios realizados por el primer usuario. Lo siguiente es diferente con la interacción del usuario que sin ella:

  • La lectura y la escritura no se pueden fusionar.
  • En la mayoría de los casos, la lectura y la escritura se llevan a cabo como dos transacciones independientes.
  • Hay que tener en cuenta que el usuario puede tardar mucho en escribir (por ejemplo, pausa para el almuerzo), lo que significa que puede pasar mucho tiempo entre la lectura y la escritura.
  • La conexión puede interrumpirse durante la interacción del usuario (problema de red, el programa se está terminando, ...).

Esto se puede evitar verificando antes de escribir si los datos se han modificado mientras tanto y combinando este acceso de lectura y escritura en una sola transacción. No es necesario comprobar todos los valores de columna del registro de datos. Basta saber si se ha modificado el registro. Esto se puede lograr agregando una columna adicional con un número entero. Esto hace posible verificar si el valor del acceso de lectura aún es anterior al cambio de datos durante el acceso de escritura. Si es así, la transacción final de lectura y escritura debe aumentar el valor de esta columna en 1 en consecuencia. Los demás valores de la columna también se actualizan al mismo tiempo. Por lo tanto, solo debe recordar un valor entero read_column-test- value. En SQL se parece a esto:

  • El registro de datos se lee para luego editarlo. (el valor de la prueba read_column debe guardarse en una variable):
   SELECT tabelle spalte_1, ..., gelesener_spaltentestwert
   WHERE id_tabelle;
  • El registro de datos se cambia y luego se vuelve a escribir. (La verificación de cambios por accesos competidores y el proceso de escritura se combinan):
   UPDATE tabelle SET spalte_1=spaltenwert_1, ..., gelesener_spaltentestwert=gelesener_spaltentestwert+1
   WHERE spaltentestwert=gelesener_spaltentestwert AND id_tabelle;
  • Si no se encuentra ningún registro de datos aquí, el valor de prueba read_column ya no es correcto y mientras tanto se ha realizado una actualización a través de otro acceso. Ahora se debe realizar una evaluación de errores y se debe dar una respuesta adecuada.
  • Si tiene éxito, el procesamiento del registro de datos habrá finalizado.

Por supuesto, los valores de las columnas deben especificarse como variables de acuerdo con el lenguaje de programación utilizado.

Ver también