php - usar - ¿Cómo manejar las situaciones de bloqueo de MySQL en un nivel de aplicación?
usar mysql y php con google maps (1)
Una transacción puede fallar. El interbloqueo es un caso de falla, también podría tener más fallas en los niveles serializables. Problemas de aislamiento de transacciones es una pesadilla. Tratar de evitar fallos es la mala manera, creo.
Creo que cualquier código de transacción bien escrito debería estar efectivamente preparado para las transacciones fallidas.
Como ha visto registrar consultas y reproducirlas no es una solución, ya que cuando reinicia su transacción, la base de datos se ha movido. Si fuera una solución válida, el motor SQL sin duda lo haría por usted. Para mi las reglas son:
- rehaga todas sus lecturas dentro de las transacciones (cualquier información que haya leído fuera puede haber sido alterada)
- tirar todo desde el intento anterior, si ha escrito cosas fuera de la transacción (registros, LDAP, cualquier cosa fuera de SGBD), debe cancelarse debido a la reversión
- rehacer todo de hecho :-)
Esto significa un bucle de reintento .
Así que tienes tu bloque try / catch con la transacción dentro. Debe agregar un bucle while con tal vez 3 intentos, deje el bucle while si la parte de confirmación del código tiene éxito. Si después de 3 reintentos, la transacción sigue fallando, inicie una excepción para el usuario, de modo que no intente un ciclo de reintentos inifinito, de hecho, puede que tenga un gran problema. Tenga en cuenta que debe manejar el error de SQL y el bloqueo o la excepción serializable de diferentes maneras. 3 es un número arbitrario, puede intentar un mayor número de intentos.
Esto puede dar algo así:
$retry=0;
$notdone=TRUE;
while( $notdone && $retry<3 ) {
try {
$transaction->begin();
do_all_the_transaction_stuff();
$transaction->commit();
$notdone=FALSE;
} catch( Exception $e ) {
// here we could differentiate basic SQL errors and deadlock/serializable errors
$transaction->rollback();
undo_all_non_datatbase_stuff();
$retry++;
}
}
if( 3 == $retry ) {
throw new Exception("Try later, sorry, too much guys other there, or it''s not your day.");
}
Y eso significa que todas las cosas (leer, escribir, cosas de $do_all_the_transaction_stuff();
) deben estar encerradas en el $do_all_the_transaction_stuff();
. Al implicar que el código de administración de transacciones se encuentra en los controladores, el código principal de la aplicación de alto nivel no se divide en varios objetos de modelos de acceso de base de datos de bajo nivel .
Cuando se produce una situación de interbloqueo en MySQL / InnoDB, devuelve este error familiar:
''Deadlock encontrado al tratar de obtener el bloqueo; intente reiniciar la transacción ''
Entonces, lo que hice fue registrar todas las consultas que entran en una transacción para que simplemente puedan volver a emitirse si falla una declaración en la transacción. Sencillo.
Problema: cuando tiene consultas que dependen de los resultados de consultas anteriores, esto no funciona tan bien.
Por ejemplo:
START TRANSACTION;
INSERT INTO some_table ...;
-- Application here gets ID of thing inserted: $id = $database->LastInsertedID()
INSERT INTO some_other_table (id,data) VALUES ($id,''foo'');
COMMIT;
En esta situación, no puedo simplemente volver a emitir la transacción como se creó originalmente. La ID adquirida por la primera instrucción SQL ya no es válida después de que la transacción falle, pero la segunda instrucción la utiliza. Mientras tanto, muchos objetos se han rellenado con datos de la transacción que luego se vuelven obsoletos cuando la transacción se revierte. El código de la aplicación en sí no "retrocede" con la base de datos, por supuesto.
La pregunta es: ¿Cómo puedo manejar estas situaciones en el código de la aplicación? (PHP)
Estoy asumiendo dos cosas. Por favor, dime si crees que estoy en el camino correcto:
1) Debido a que la base de datos no puede reemitir una transacción literalmente en todas las situaciones, mi solución original no funciona y no debe utilizarse.
2) La única forma buena de hacerlo es envolver cualquier código de emisión de transacciones en su propio bloque try / catch e intentar volver a emitir el código en sí, no solo el SQL.
Gracias por tu contribución. Tu rock