Documentos de Académico
Documentos de Profesional
Documentos de Cultura
PARTE III.
TRANSACCIONES
3. Transacciones en MySQL
3.1 Introducción........................................................................................... 2
3.2 Niveles de aislamiento........................................................................... 3
3.3 Instrucciones de manejo de transacciones ............................................. 9
3.4 Transacciones y bloqueos .................................................................... 13
3.4.1 Deadlock. Cursores de actualización.............................. 15
3.5 Estrategias de bloqueo ......................................................................... 19
3.6 Aspectos a tener en cuenta a la hora de utilizar transacciones ............ 21
Página 1
Transacciones
TEMA 3.
Transacciones en MySQL
Segundo Fidel Puerto Garavito
3.1 Introducción
TRANSACCIÓN en SQL: Conjunto de instrucciones SQL, agrupadas lógicamente,
que o bien se ejecutan todas sobre la base de datos o bien no se ejecuta ninguna.
Una transferencia bancaria entre dos cuentas supone un ejemplo claro para ilustrar el
concepto de transacción. La transferencia se compone de dos operaciones (instrucciones):
1) Descontar de la libreta origen a transferir la cantidad fijada.
2) Aumentar el saldo de la libreta destino con el importe de la cantidad transferida
de la cuenta origen.
Está claro que la transacción bancaria anterior no se puede quedar “a medias”. O bien se
aplican las dos operaciones lógicas que componen la transacción o bien no se realiza
ninguna por mantener una consistencia contable. Si sólo llegara a realizarse una
operación de las dos anteriores, esta se desharía.
Las transacciones sobre la base de datos deben ajustarse al principio ACID (ATOMIC +
CONSISTENT + ISOLATED + DURABLE), lo que implica que una transacción debe
ser:
Página 2
Transacciones
Cada vez que abrimos el programa cliente MySQL Query Browser estamos abriendo
una sesión (podemos tener varias instancias de este programa abiertas Æ varias
sesiones). Para averiguar el identificador de conexión que nos asigna el servidor,
consultaremos la función que nos lo proporciona:
Página 3
Transacciones
También en alguna ocasión hemos creado alguna sesión para trabajar con la base de
datos desde la ventana de línea de comandos de Windows:
Cada sesión trabaja en su propia zona de memoria pudiendo llegar incluso a bloquear
los datos de la base de datos con los que trabaja.
Los niveles de aislamiento determinan la manera en que las transacciones de una sesión
pueden afectar a los datos recuperados o accedidos por otra sesión. Hay por tanto dos
conceptos interrelacionados: por una lado la concurrencia (varios sesiones realizando
transacciones al mismo tiempo) y por otro el grado de consistencia de los datos.
Determinan también el grado en que las transacciones se ajustan al principio ACID.
Cuanto mayor es el grado de aislamiento, menor es el número de transacciones que se
pueden realizar concurrentemente pero también es menor la posibilidad de que
interfieran las transacciones. Por otro lado, cuanto menor es el grado de aislamiento,
mayor es el número de transacciones que se pueden realizar concurrentemente pero el
riesgo de conflicto entre transacciones es elevado.
Página 4
Transacciones
Nota: Recordar que se mantiene siempre la consistencia en lectura Æ Los datos que ven
las transacciones son los últimos que se validaron.
Página 5
Transacciones
En este momento tendrás 3 sesiones abiertas, una del programa MySQL Query Browser
y dos clientes de línea de comando de MySQL (ilustración 5). Puedes comprobar el
identificador de cada sesión con la función vista anteriormente (SELECT
CONNECTION_ID();):
Como los números de identificación de conexión serán distintos a los que utilices,
llamaremos A a la conexión de cliente de línea de comando de identificador más bajo y
B a la otra conexión de cliente cuyo identificador es más alto.
Dejaremos la propiedad autocommit a 0 en las dos sesiones abiertas por lo que los
cambios en la base de datos no se almacenan después de cada instrucción sino cuando
se confirman explícitamente con la instrucción COMMIT.
A
> SET autocommit = 0;
> USE TEST
B
> SET autocommit = 0;
> USE TEST
A
> INSERT INTO alumnos VALUES (6, 'alumno6');
> SELECT * FROM alumnos WHERE id=6;
+----+---------+
| id | alumno |
+----+---------+
| 6 | alumno6 |
+----+---------+
B
> SELECT * FROM alumnos WHERE id=6;
Empty set (0.00 sec)
A
> COMMIT;
B
> SELECT * FROM alumnos WHERE id=6;
Empty set (0.00 sec)
> COMMIT;
Query OK, 0 rows affected (0.00 sec)
> SELECT * FROM alumnos WHERE id=6;
+----+---------+
| id | alumno |
+----+---------+
| 6 | alumno6 |
+----+---------+
1 row in set (0.00 sec)
Página 6
Transacciones
A
> UPDATE alumnos SET alumno = 'Alberto Carrera' WHERE id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
> SELECT * FROM ALUMNOS WHERE id=1;
+----+-----------------+
| id | alumno |
+----+-----------------+
| 1 | Alberto Carrera |
+----+-----------------+
1 row in set (0.00 sec)
B
> SELECT * FROM ALUMNOS WHERE id=1;
+----+----------+
| id | alumno |
+----+----------+
| 1 | alumno 1 |
+----+----------+
1 row in set (0.00 sec)
A
mysql> SELECT * FROM ALUMNOS WHERE id=2;
+----+----------+
| id | alumno |
+----+----------+
| 2 | alumno 2 |
+----+----------+
1 row in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.01 sec)
Página 7
Transacciones
B
mysql> SELECT * FROM ALUMNOS;
+----+----------------+
| id | alumno |
+----+----------------+
| 1 | alumno 1 |
| 2 | Raquel Carrera |
| 3 | alumno 3 |
| 4 | alumno 4 |
| 5 | alumno 5 |
| 6 | alumno6 |
+----+----------------+
6 rows in set (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.02 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
Página 8
Transacciones
Página 9
Transacciones
SET autocommit=0
Nota: Independientemente del valor de la propiedad anterior, todas las sentencias DDL
del lenguaje: ALTER, CREATE, DROP … tienen el COMMIT o confirmación de manera
implícita o automática.
Procedimiento 1 transac1
La llamada al procedimiento:
En cambio:
Página 10
Transacciones
Procedimiento 2 transac2
mysql> USE TEST
Database changed
mysql> call transac2(7, 'Blanca Bailin', @n_error, @texto_error);
Query OK, 0 rows affected (0.00 sec)
Página 11
Transacciones
Procedimiento 3 transac3
CALL transac3 (30, 'Centro 30', 'Los Olivos - Huesca', 140, 'Departamento 140', 40000)
Página 12
Transacciones
Página 13
Transacciones
Por otro lado la solución anterior no es buena para garantizar la clave única en una
tabla. Imagina que cada departamento se obtiene sumando 10 al número último
asignado. Si utilizamos la cláusula LOCK IN SHARE MODE puede ocurrir que dos
transacciones lean al mismo tiempo el último id asignado, le sumen 10 y a la hora de
modificarlo provoquen una situación de clave duplicada o como se ve más adelante
una situación de abrazo mortal (deadlock). La solución para este caso:
Página 14
Transacciones
Como consecuencia del abrazo mortal anterior, una de las dos transacciones realizará un
ROLLBACK provocando una situación de error.
Abrimos 2 sesiones A y B:
A
mysql> USE TEST;
Database changed
mysql> SET autocommit = 0;
B
mysql> USE TEST;
Database changed
mysql> SET autocommit = 0;
A
mysql> UPDATE ALUMNOS SET alumno='Alberto Carrera' WHERE id=1;
/* A ya puede ver el cambio producido pero B seguirá viendo alumno1
como nombre en lugar de Alberto Carrera */
Página 15
Transacciones
A
mysql>UPDATE ALUMNOS SET alumno='Carmen Bailin' WHERE id=2;
B
Si ejecutamos rápidamente:
mysql>UPDATE ALUMNOS SET alumno='Raquel Carrera' WHERE id=1;
A
mysql>COMMIT;
B
Sigue viendo la pantalla anterior última (ilustración 14).
mysql>COMMIT;
Para probar el cuarto ejemplo hay que lanzar antes otra vez el procedimiento
procedimiento1 para crear de nuevo la tabla alumnos.
Página 16
Transacciones
Procedimiento 4transac4
A
USE TEST;
SET autocommit = 0;
B
USE TEST;
SET autocommit = 0;
A
UPDATE alumnos SET alumno= 'Mario Carrera'
WHERE id=1;
B
CALL transac4 (1, 'Carmen Bailin', @p_n_error, @p_text_error);
/* ……Después de un tiempo de espera – 50 segundos en el que parece que se ha quedado
colgado el programa */
Query OK, 0 rows affected (51.55 sec)
SELECT @p_n_error, @p_text_error;
+------------+---------------------------+
| @p_n_error | @p_text_error |
+------------+---------------------------+
| 1205 | Tiempo de espera excedido |
+------------+---------------------------+
Página 17
Transacciones
Procedimiento 5 transac5
Antes se finalizar este apartado señalar que, como hemos comprobado al intentar
escribir en una fila bloqueada (sin ser deadlock), se ha producido un tiempo de espera
de 50 segundos tras el que se ha producido un error de tiempo de espera excedido
(1205) y la transacción se ha deshecho.
Página 18
Transacciones
Posibles soluciones:
Página 19
Transacciones
Procedimiento 6 transacciones
Es una estrategia muy segura pues asegura la consistencia entre la lectura (SELECT) y
la operación DML (UPDATE), pero limita mucho el rendimiento del sistema al obligar
a las transacciones a largas esperas para poder completarse.
En la estrategia optimista, como la transacción no bloquea la fila que lee, antes de que
realice la modificación sobre la misma debe asegurarse que el valor de la fila no ha
cambiado desde que la leyó; en caso de que no se hubieran producido cambios se realizará
y confirmará la transacción, en caso contrario no se llevará a cabo.
Para la elección de una estrategia u otra hay que tener en cuenta la concurrencia y
robustez: la estrategia pesimista supone menos errores y reintentos mientras que con la
optimista se reduce el tiempo de duración de los bloqueos aumentando por tanto la
concurrencia y el rendimiento del sistema.
Generalmente suele utilizarse la estrategia pesimista y sólo se recurre a la optimista si la
duración de los bloqueos o el número de filas que se bloquean es elevado en la
estrategia pesimista. De todas formas el uso de una u otra depende mucho de las
características de la aplicación.
Página 20
Transacciones
- La duración de los bloqueos debe ser lo mínimo posible. De igual manera debe
ser también mínimo el número de filas a bloquear por las transacciones.
1 Desde PHP
En el siguiente ejemplo se intenta llevar a cabo una transacción consistente en cambiar
el nombre del alumno de matrícula 1. Si todo va bien aparecerá el mensaje “Filas
Página 21
Transacciones
'..................
Dim MiConexion As New MySqlConnection(cadena_conexion)
Dim cadena_sql As String = "UPDATE alumnos" + _
" SET alumno='Mario Carrera' " + _
" WHERE id=1"
Dim instruccion As MySqlCommand = New MySqlCommand(cadena_sql, MiConexion)
MiConexion.Open()
Dim MiTransaccion As MySqlTransaction = MiConexion.BeginTransaction
Try
Dim filas_afectadas As Integer
filas_afectadas = instruccion.ExecuteNonQuery()
Console.WriteLine("Filas afectadas: " + filas_afectadas.ToString)
MiTransaccion.Commit()
Console.WriteLine("Transacción finalizada")
Catch Exception As MySqlException
Console.WriteLine("Error en la transacción: ")
Console.WriteLine(Exception.Message)
Try
MiTransaccion.Rollback()
Console.WriteLine("Transaction no realizada")
Catch RollbackException As MySqlException
Console.WriteLine("Rollback fallido:")
Console.WriteLine(RollbackException.Message)
End Try
End Try
'..................
Página 22