4.3 KiB
Comandos principales
START TRANSACTION
se utiliza para abrir una transacción (desactiva temporalmente el modoautocommit
).COMMIT
se utiliza para que los cambios que están pendientes de la transacción actual, se vuelvan permanentes en la BD y cierra la transacción.ROLLBACK
para deshechar cualquier cambio pendiente que se haya hecho en la transacción actual y cierra la transacción.
MYSQL por defecto funciona con el modo autocommit
activado, eso quiere decir que cualquier consulta es una transacción implicitamente, osea, si hacen un UPDATE ...
en el fondo InnoDB ejecuta algo parecido a esto:
START TRANSACTION;
UPDATE ...
COMMIT;
Si ese UPDATE ...
falla estando el modo autocommit
activado, entonces automáticamente se ejecuta un ROLLBACK
Usos
Ejecutar todo o nada
Supongamos que queremos ejecutar una serie de consultas que modifican la BD, pero queremos que se ejecuten todas, y que si cualquiera falla que se deshagan los cambios.
Podemos hacer algo así:
BEGIN
START TRANSACTION;
.. Consulta 1 ..
.. Consulta 2 .. # Esta consulta siempre falla
.. Consulta 3 ..
COMMIT;
END
Al ejecutar ese procedimiento almacenado, se abre una transacción, se ejecuta la Consulta 1, y al intentar la Consulta 2 falla.
Hay un problema aquí, la transacción no se deshace, sino que queda abierta, osea, tiene cambios hechos de la Consulta 1, pero no se han hecho permanentes en la BD (COMMIT
), ni tampoco se han devuelto los cambios (ROLLBACK
). Y puede ocurrir cualquiera de los dos escenarios. Por ejemplo si se cierra la conexión, la BD ejecuta un ROLLBACK
y si se ejecuta cualquiera de estas consultas https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html se genera un COMMIT
implicitamente.
Si en este estado de la BD se hace un SELECT
para verificar si en la BD se hicieron los cambios de la Consulta 1, se va a obtener distintas respuestas dependiendo de si el SELECT
se ejecuta desde la misma conexión/sesión o no.
Cuando se abre una transacción y se hace un cambio, ese cambio solamente es visible para esa misma sesión que abrió la transacción.
Solución
Explicitamente hacer ROLLBACK
cuando ocurra un error.
Para eso definimos un HANDLER para los errores, es como el equivalente a un try ... catch
pero afecta a todo el procedimeinto almacenado
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
.. Consulta 1 ..
.. Consulta 2 .. # Esta consulta siempre falla
.. Consulta 3 ..
COMMIT;
END
Ahora cuando la Consulta 2 falle, se dispara el handler, y se ejecutan los comandos ROLLBACK
y RESIGNAL
. RESIGNAL sirve para que se vuelva a lanzar el error, ya que de lo contrario el procedimiento almacenado termina "exitosamente" a pesar de que hubo un error.
Bloquear tablas
Luego de abrir una transaccion con START TRANSACTION
las consultas como el INSERT
, UPDATE
o DELETE
bloquean las tablas, registros o índices que afecten, hasta que dicha transacción se cierre ya sea con un COMMIT
o un ROLLBACK
.
Esto sirve para que cualquier otra consulta que quiera leer o modificar los elementos bloqueados, deban esperar hasta que la transacción que los bloquea se cierre y los libere.
Los SELECT
normales no generan ningun bloqueo, para eso existen el SELECT ... FOR SHARE
y el SELECT ... FOR UPDATE
.
En términos simples el FOR SHARE bloquea los elementos seleccionados para que otra consulta no pueda escribir sobre ellos hasta que se cierre la transacción, y el FOR UPDATE adicionalmente impide que tampoco puedan leerlos (igual que los INSERT
, UPDATE
o DELETE
). Mas información https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html
Recomendaciones
Hay que analizar detalladamente todo lo que se mete dentro de una transacción, y reducirlo a lo mas mínimo posible, para evitar Deadlocks o retrasos debido a bloqueos innecesarios.
Usar índices en todos los WHERE
de las consultas que bloquean, para que dichos bloqueos sean lo mas óptimos posibles, y se bloquee solo lo que hace falta bloquear.