Cómo implementar correctamente el bloqueo optimista en MySQL

13

¿Cómo se implementa correctamente el bloqueo optimista en MySQL?

Nuestro equipo ha deducido que debemos hacer el # 4 a continuación o de lo contrario existe el riesgo de que otro hilo pueda actualizar la misma versión del registro, pero nos gustaría validar que esta es la mejor manera de hacerlo.

  1. Cree un campo de versión en la tabla para la que desea usar el bloqueo optimista, por ejemplo, columna nombre = "versión"
  2. En selecciones, asegúrese de incluir la columna de versión y tome nota de la versión
  3. En una actualización posterior del registro, la declaración de actualización debe emitir "where version = X" donde X es la versión que recibimos en el n. ° 2 y establecer el campo de versión durante esa declaración de actualización en X + 1
  4. Realice un SELECT FOR UPDATEregistro en el registro que vamos a actualizar para que serialicemos quién puede realizar cambios en el registro que estamos tratando de actualizar.

Para aclarar, estamos tratando de evitar que dos subprocesos que seleccionan el mismo registro en la misma ventana de tiempo donde toman la misma versión del registro se sobrescriban entre sí si intentan actualizar el registro al mismo tiempo. Creemos que a menos que hagamos el n. ° 4, existe la posibilidad de que si ambos hilos ingresan sus transacciones respectivas al mismo tiempo (pero aún no han emitido sus actualizaciones), cuando van a actualizar, el segundo hilo que usará la ACTUALIZACIÓN ... donde version = X funcionará con datos antiguos.

¿Estamos en lo cierto al pensar que debemos hacer este bloqueo pesimista al actualizar a pesar de que estamos usando campos de versión / bloqueo optimista?

Mejores prácticas
fuente
¿Cuál es el problema? Aumenta el número de versión con su ACTUALIZACIÓN, luego la segunda ACTUALIZACIÓN fallará porque el número de versión no es el mismo que cuando se leyó, que es lo que desea.
AndreKR
¿Estas seguro? No está claro que, a menos que establezca el nivel de aislamiento de la transacción en una configuración específica, realmente verá la actualización de los otros subprocesos. Si ambos ingresan la transacción al mismo tiempo, el segundo hilo puede ver muy bien los datos ANTIGUOS cuando va a hacer la actualización. MySQL no es tan robusto en el ámbito de ACID como dice Oracle, por lo tanto, busca la mejor forma de implementar un bloqueo optimista en MySQL que evite lecturas / actualizaciones sucias.
BestPractices
Pero entonces la transacción fallará de todos modos durante la confirmación, ¿verdad?
AndreKR
Las indicaciones indican que uno desearía hacer una selección para actualizar para lidiar con esta situación: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices
@BestPractices Se necesitan ya sea SELECT ... FOR UPDATE o bloqueo optimista por versiones de fila, pero no ambos. Ver detalles en la respuesta.
Craig Ringer

Respuestas:

17

Tu desarrollador está equivocado. Necesita versiones de fila o de uno , no ambas.SELECT ... FOR UPDATE

Pruébalo y verás. Tres sesiones abiertas de MySQL (A), (B)y (C)con la misma base de datos.

En (C)cuestión:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

En ambos (A)y (B)emite un UPDATEque prueba y establece la versión de la fila, cambiando el winnertexto en cada uno para que pueda ver qué sesión es cuál:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

Ahora adentro (C), UNLOCK TABLES;para liberar el bloqueo.

(A)y (B)correrá por el bloqueo de la fila. Uno de ellos ganará y obtendrá el bloqueo. El otro bloqueará la cerradura. El ganador que obtuvo el bloqueo procederá a cambiar la fila. Suponiendo que (A)es el ganador, ahora puede ver la fila modificada (aún sin confirmar, por lo que no es visible para otras transacciones) con un SELECT * FROM test WHERE id = 1.

Ahora COMMITen la sesión ganadora, digamos (A).

(B)obtendrá el bloqueo y procederá con la actualización. Sin embargo, la versión ya no coincide, por lo que no cambiará filas, según lo informado por el resultado del recuento de filas. Solo uno UPDATEtuvo algún efecto, y la aplicación cliente puede ver claramente cuál UPDATEtuvo éxito y cuál falló. No es necesario más bloqueo.

Vea los registros de sesión en pastebin aquí . Utilicé mysql --prompt="A> "etc. para facilitar la diferencia entre sesiones. Copié y pegué la salida intercalada en secuencia de tiempo, por lo que no es una salida totalmente sin procesar y es posible que haya cometido errores al copiarla y pegarla. Pruébelo usted mismo para ver.


Si hubieras no añadido un campo de versión de fila, a continuación, que tendría que SELECT ... FOR UPDATEser capaz de garantizar de manera fiable el pedido.

Si lo piensa, a SELECT ... FOR UPDATEes completamente redundante si está haciendo inmediatamente UPDATEsin volver a usar los datos del SELECT, o si está usando el control de versiones de fila. El UPDATEtomará un candado de todos modos. Si alguien más actualiza la fila entre su lectura y la escritura posterior, su versión ya no coincidirá, por lo que su actualización fallará. Así es como funciona el bloqueo optimista.

El propósito de SELECT ... FOR UPDATEes:

  • Para gestionar el orden de bloqueo para evitar puntos muertos; y
  • Para extender el lapso de un bloqueo de fila para cuando desee leer datos de una fila, cámbielo en la aplicación y escriba una nueva fila que se base en la original sin tener que usar SERIALIZABLEaislamiento o control de versiones de fila.

No necesita utilizar tanto el bloqueo optimista (versiones de fila) como SELECT ... FOR UPDATE. Usar uno u otro.

Craig Ringer
fuente
Gracias Craig Tenías razón: el desarrollador se equivocó. Gracias por ejecutar esta prueba.
BestPractices
¿Qué pasa con el servidor SQL? ¿Siempre se adquiere un bloqueo en la fila actualizada independientemente del nivel de aislamiento de la transacción?
plalx
@plalx Bueno, ¿qué dice la documentación? ¿Qué sucede si ejecuta una prueba interactiva como esta?
Craig Ringer
@CraigRinger, ¿qué sucederá si B obtiene el bloqueo antes de que A confirme pero después de una actualización?
MengT
1
@MengT No puede, por eso es un candado.
Craig Ringer el
0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

No se necesitan bloqueos (ni tabla, ni transacción), ni siquiera deseados:

  • ACTUALIZACIÓN es atómica
  • LAST_INSERT_ID () es específico de la sesión, por lo tanto, es seguro para subprocesos.
Rick James
fuente