86 lines
4.3 KiB
Markdown
86 lines
4.3 KiB
Markdown
[//]: # "Ctrl+K,V o Ctrl+Shift+V - Para ver vista previa en VSCode"
|
|
|
|
# Comandos principales
|
|
|
|
- `START TRANSACTION` se utiliza para abrir una transacción (desactiva temporalmente el modo `autocommit`).
|
|
- `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:
|
|
|
|
```sql
|
|
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í:
|
|
|
|
```sql
|
|
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
|
|
|
|
```sql
|
|
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](https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks.html) 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. |