MySQL: Transacciones vs Tablas de Bloqueo


Estoy un poco confundido con las transacciones frente a las tablas de bloqueo para garantizar la integridad de la base de datos y asegurarse de que una SELECCIÓN y ACTUALIZACIÓN permanecen sincronizadas y ninguna otra conexión interfiere con ella. Necesito:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Necesito asegurarme de que ninguna otra consulta interfiera y realice lo mismo SELECT (leyendo el 'valor antiguo' antes de que la conexión termine de actualizar la fila.

Sé que puedo usar LOCK TABLES table por defecto para asegurarme de que solo 1 conexión está haciendo esto a la vez, y desbloquearlo cuando He terminado, pero eso parece exagerado. ¿Envolver eso en una transacción haría lo mismo (asegurar que ninguna otra conexión intente el mismo proceso mientras otra todavía está procesando)? ¿O sería mejor un SELECT ... FOR UPDATE o SELECT ... LOCK IN SHARE MODE?

Author: robsch, 2010-11-19

6 answers

Bloquear tablas evita que otros usuarios de BD afecten las filas/tablas que ha bloqueado. Pero las cerraduras, por sí mismas, NO asegurarán que su lógica salga en un estado consistente.

Piense en un sistema bancario. Cuando pagas una factura en línea, hay al menos dos cuentas afectadas por la transacción: Tu cuenta, de la que se toma el dinero. Y la cuenta del receptor, a la que se transfiere el dinero. Y la cuenta del banco, en la que felizmente depositarán todos los tarifas de servicio cobradas en la transacción. Dado (como todo el mundo sabe en estos días) que los bancos son extraordinariamente estúpidos, digamos que su sistema funciona así:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Ahora, sin bloqueos ni transacciones, este sistema es vulnerable a varias condiciones de carrera, la mayor de las cuales es que se realicen múltiples pagos en su cuenta o en la cuenta del receptor en paralelo. Mientras que su código tiene su saldo recuperado y está haciendo el huge_overdraft_fees() y demás, es enteramente posible que algún otro pago se ejecutará el mismo tipo de código en paralelo. Que va a ser recuperar el saldo (es decir, $100), hacer sus transacciones (sacar los $20 que están pagando, y los $30 que están atornillando con), y ahora los dos rutas de código tenemos dos diferentes saldos: $80 y $70. Dependiendo de cuál termine último, terminarás con cualquiera de esos dos saldos en tu cuenta, en lugar de los 5 50 que deberías haber tenido ($100 - $20 - $30). En este caso, "error bancario en tu favor".

Ahora, digamos que usas cerraduras. El pago de su factura ($20) llega primero, por lo que gana y bloquea el registro de su cuenta. Ahora usted tiene uso exclusivo, y puede deducir los 2 20 del saldo, y escribir el nuevo saldo en paz... y su cuenta termina con $80 como se espera. Pero... uhoh... Intenta actualizar la cuenta del receptor, y está bloqueada, y bloqueada más tiempo del que el código permite, cronometrando su transacción... Estamos tratando con bancos estúpidos, así que en su lugar de tener un manejo adecuado de errores, el código simplemente tira de un exit(), y sus 2 20 se desvanecen en una nube de electrones. Ahora estás fuera de $20, y todavía debes 2 20 al receptor, y su teléfono se embargó.

So... ingrese transacciones. Inicia una transacción, debita a su cuenta 2 20, intenta acreditar al receptor con 2 20... y algo explota de nuevo. Pero esta vez, en lugar de exit(), el código solo puede hacer rollback, y poof, sus 2 20 se agregan mágicamente a su cuenta.

Al final, se reduce a esto:

Los bloqueos evitan que cualquier otra persona interfiera con cualquier registro de base de datos con el que esté tratando. Las transacciones evitan que cualquier error " posterior "interfiera con las cosas" anteriores " que haya hecho. Ninguno de los dos por sí solo puede garantizar que las cosas funcionen bien al final. Pero juntos, lo hacen.

En la lección de mañana: La Alegría de los Estancados.

 138
Author: Marc B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-19 18:00:06

Desea un SELECT ... FOR UPDATE o SELECT ... LOCK IN SHARE MODE dentro de una transacción, como ha dicho, ya que normalmente SELECTs, no importa si están en una transacción o no, no bloqueará una tabla. El que elija dependerá de si desea que otras transacciones puedan leer esa fila mientras su transacción está en curso.

Http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOT no hará el truco para usted, ya que otras transacciones todavía pueden venir y modificar esa fila. Esto se menciona justo en la parte superior del enlace a continuación.

Si otras sesiones simultáneamente actualizar la misma tabla [...] usted puede ver la tabla en un estado que nunca existía en la base de datos.

Http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

 13
Author: Alison R.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-19 19:52:57

Tuve un problema similar al intentar un IF NOT EXISTS ... y luego realizar un INSERT que causó una condición de carrera cuando varios subprocesos estaban actualizando la misma tabla.

Encontré la solución al problema aquí: Cómo escribir INSERT IF NOT EXISTS queries en SQL estándar

Me doy cuenta de que esto no responde directamente a su pregunta, pero el mismo principio de realizar una verificación e insertar como una sola declaración es muy útil; debe ser capaz de modificarlo para realizar su actualizar.

 6
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-19 16:06:58

Los conceptos de transacción y los bloqueos son diferentes. Sin embargo, la transacción utiliza bloqueos para ayudar a seguir los principios ÁCIDOS. Si desea que la tabla evite que otros lean / escriban al mismo tiempo mientras usted está leyendo / escribiendo, necesita un bloqueo para hacer esto. Si desea asegurarse de la integridad y consistencia de los datos, es mejor que use las transacciones. Creo que conceptos mezclados de niveles de aislamiento en transacciones con bloqueos. Por favor, busque niveles de aislamiento de transacciones, SERIALIZAR debe ser el nivel que quieras.

 3
Author: tczhaodachuan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-23 19:35:43

Está confundido con lock & transaction. Son dos cosas diferentes en RMDB. El bloqueo evita las operaciones simultáneas, mientras que la transacción se centra en el aislamiento de datos. Echa un vistazo este gran artículo para la aclaración y alguna solución elegante.

 2
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-31 10:27:40

Usaría un

START TRANSACTION WITH CONSISTENT SNAPSHOT;

Para empezar, y un

COMMIT;

Para terminar.

Todo lo que hagas en el medio está aislado de los demás usuarios de tu base de datos si tu motor de almacenamiento admite transacciones (que es InnoDB).

 1
Author: Martin Schapendonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-19 16:06:45