Transacciones en MySQL
En MySQL, una de las ventajas de trabajar con tablas InnoDB es el soporte para transacciones que provee. En este artículo voy a explicar que son las transacciones y como funcionan en MySQL.
Que son las transacciones
Una transacción, es básicamente un grupo de sentencias SQL agrupadas de manera que deban ejecutarse todas juntas o no ejecutarse, es decir, nunca ejecutarse unas sentencias si y otras no.
Veámoslo con un ejemplo. Imaginemos un sistema bancario, el posee una base de datos, con una tabla accounts para almacenar los datos de todas las cuentas de los clientes (el Nº de cuenta, el saldo, etc.).
Cada vez que un cliente hace una transacción, el sistema transfiere determinada cantidad de dinero de una cuenta A a una cuenta B, de esta manera:
- Hace un UPDATE de la cuenta A restándole el dinero a transferir
- Hace un UPDATE de la cuenta B sumándole el dinero
A simple vista parece simplemente perfecto… pero… ¿qué pasaría si justo después del primer UPDATE ocurriera algún fallo y se detuviera la ejecución?
¡Entonces habríamos restado el dinero de la cuenta A sin habérselo sumado a la cuenta B!
Oh no! that’s a BIG BUG!
¿Cómo lo solucionamos?
Necesitamos usar transacciones.
Una transacción funciona de la siguiente manera, ejecuta las sentencias (siguiendo con el ejemplo anterior los UPDATE para las cuentas A y B) y si llegara a haber un error, por medio de journaling restablece la base de datos tal como estaba antes de iniciar la transacción, de esta manera se asegura que las sentencias se ejecuten todas o no se ejecute ninguna.
¿Qué es journaling?
Es un mecanismo por el cual por cada sentencia que se ejecute, se crea una sentencia para restablecer los cambios realizados por esa sentencia.
Por ejemplo, si ejecutáramos un INSERT entonces se crearía una sentencia DELETE que eliminaría el registro recién creado en caso de que la transacción falle, y de esa manera restablecer todo como estaba al inicio de la transacción.
Transacciones en MySQL
El tipo de tabla que hay que usar para poder trabajar con transacciones en MySQL, es InnoDB.
Vamos a crear una tabla accounts para hacer las pruebas:
[sourcecode language="sql"]CREATE TABLE accounts (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
balance INT NOT NULL
) ENGINE=InnoDB;[/sourcecode]
Insertamos algunos registros:
[sourcecode language="sql"]INSERT INTO accounts (name, balance) VALUES (‘A’, 100);
INSERT INTO accounts (name, balance) VALUES (‘B’ 0);[/sourcecode]
Nos quedaría algo así:
+----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | A | 100 | | 2 | B | 0 | +----+------+---------+
Ahora vamos a hacer una transferencia de todo el dinero de la cuenta A hacia la cuenta B. Por lo que vamos a hacer un UPDATE en la cuenta A eliminando el dinero existente, y otro UPDATE en la cuenta B para añadirle dicho dinero.
Sin transacciones
[sourcecode language="sql"]UPDATE accounts SET balance=0 WHERE name=’A';
UPDATE accounts SET balance=100 WHERE name=’B';[/sourcecode]
Si ocurriera un error antes de que se ejecutase la segunda sentencia, estaríamos en un problema, ya que como dijimos antes, el dinero no se añadiría en la cuenta B y quedarían las dos cuentas con un saldo de cero.
+----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | A | 0 | | 2 | B | 0 | +----+------+---------+
Con transacciones
[sourcecode language="sql"]BEGIN
UPDATE accounts SET balance=0 WHERE name=’A';
UPDATE accounts SET balance=100 WHERE name=’B';
COMMIT[/sourcecode]
El procedimiento para crear una transacción es bastante simple:
- Iniciamos una nueva transacción con la sentencia BEGIN
- Ejecutamos todas las sentencias que queramos que pertenezcan a esa transacción
- Ejecutamos la sentencia COMMIT para aceptar la transacción. Sin ejecutar esta sentencia, todas las acciones anteriores se restablecerían. Una vez ejecutada la sentencia COMMIT, los cambios quedan de manera definitiva.
- También tenemos la sentencia ROLLBACK, que sirve para deshacer todos los cambios hechos desde el inicio de la transacción.
Veamos que pasaría si ocurriera un error y se detuviera la ejecución del sistema, usando transacciones:
[sourcecode language="sql"]BEGIN
UPDATE accounts SET balance=0 WHERE name=’A';
EXIT[/sourcecode]
Los cambios se restablecerían, ya que no se realizó el COMMIT. Entonces la tabla quedaría igual que como estaba:
+----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | A | 100 | | 2 | B | 0 | +----+------+---------+
Ahora veamos, que pasaría si hacemos el COMMIT, es decir si todo ocurre sin ningún error ni nada por el estilo:
[sourcecode language="sql"]BEGIN
UPDATE accounts SET balance=0 WHERE name=’A';
UPDATE accounts SET balance=100 WHERE name=’B';
COMMIT[/sourcecode]
Entonces los cambios si harían efecto esta vez:
+----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | A | 0 | | 2 | B | 100 | +----+------+---------+
Veamos también un ejemplo de la sentencia ROLLBACK:
[sourcecode language="sql"]BEGIN
UPDATE accounts SET balance=200 WHERE name=’A';
UPDATE accounts SET balance=300 WHERE name=’B';
ROLLBACK
UPDATE accounts SET balance=50 WHERE name=’A';
COMMIT[/sourcecode]
Al ejecutar la sentencia ROLLBACK después de los dos primeros UPDATES, entonces estos quedarían descartados, y solo se realizaría el tercero, quedando así la tabla:
+----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | A | 50 | | 2 | B | 0 | +----+------+---------+11 comentarios
11 comentarios
Dejá una respuesta








[...] provee. En este art
Esta muy padre tu explicacion, mil respetos para ti, te entendi mejor a ti que a mi maestra
hola, tengo el siguiente problema al ejecutar las transacciones me sale el error: lock wait timeout exceeded try restarting transaction
Como hago para solucionar esto.
Esto me paso porque primero probe un insert con el begin pero no use el comit, luego modifique el codigo, puse el comit al final, pero desde ahi me sale el error que te dije.
Gracias
Hola, bueno tu articulo. Sabes, estoy trabajando con transacciones MySQL pero me gustaría saber si existe alguna función con la cual pueda saber si la transacción se ejecutó correctamente.
Lo he intentado utilizando la función row_count(), luego de hacer COMMIT, sin embargo me devuelve un valor de ‘-1′ :S.
Si supieras como se puede hacer esto, por favor respondeme a mi mail. Desde ya gracias.
Daniel no te funciona affected rows?
Huilén estuvo muy buenisima tu explicacion muchas gracias cuando tenga algo que aportar, lo are, estoy guardando la direccion de este bloc, un abrazo.
Bien.. creo que vi este mismo articulo, en ingles en algun lado.
a aquellos que esten tratando con transacciones seria recomendable leer algo tambien acerca de locks y la variable autocommit.
por otro lado estube viendo tu blog y creo que es genial como encarrilaste tu vida, espero que lleges lejos, y por favor una recomendacion: nunca te metas en ingenieria en sistemas, te mata el espiritu y distorciona el arte de la programacion, si arte nada menos
salu2
Oye que buen artículo. Tengo una pregunta:
¿No es como en MS SQL Server que tenías que poner algo así?
BEGIN
—–[Instrucciones]
IF [error] THEN
—–ROLLBACK
ELSE
—–COMMIT
¿O en MYSQL es solo?:
BEGIN
—–[Instrucciones]
COMMIT
Espero tu respuesta.
En MySQL también podés hacer una condición con PL/SQL y hacer un rollback, depende de lo que quieras hacer, pero generalmente esperás que se haga automáticamente si ocurre algún error en la consulta.
Ah, gracias por responder tan rápido, si ya encontré la forma pongo el código…SOLO FUNCIONA EN PROCEDIMIENTOS ALMACENADOS, ¿existe una razón? desconozco la respuesta pero ahí les va, por si lo llegan a necesitar:
BEGIN
—-DECLARE err INT;
—-START TRANSACTION;
——–[INSERT, UPDATE, DELETE, etc, separados por ';']
—-SET err = (SELECT @error);
—-IF(err0) THEN
——–ROLLBACK;
—-ELSE
——–COMMIT;
—-END IF;
END
Para recibir/dar ayuda les dejo mi correo al publico:
linux_magnoltti@msn.com
Ahora falta lo de las excepciones personalizadas en MySql…¿Alguien tiene idea o sabe?
De antemano gracias. Sherekan te debo una.
Corrijo porque la comparación salió incompleta (el operador ‘diferente’ no se muestra):
BEGIN
—-DECLARE err INT;
—-START TRANSACTION;
——–[INSERT, UPDATE, DELETE, etc, separados por ';']
—-SET err = (SELECT @error);
—-IF(err = 0) THEN
——–COMMIT;
—-ELSE
——–ROLLBACK;
—-END IF;
END