Transacciones en MySQLIntroducción al uso de las sentencias BEGIN, COMMIT y ROLLBACK para el manejo de transacciones en MySQL con el uso de tablas del tipo InnoDB. Introducción El servidor de bases de datos MySQL soporta distintos tipos de tablas, tales como ISAM, MyISAM, InnoDB, y DBD (Berkeley Database). De éstos, InnoDB es el tipo de tabla más importante (después del tipo predeterminado, MyISAM), y merece una atención especial. Las tablas del tipo InnoDB están estructuradas de forma distinta que MyISAM, ya que se almacenan en un sólo archivo en lugar de tres, y sus principales características son que permite trabajar con transacciones, y definir reglas de integridad referencial. El soporte de transacciones que provee MySQL no es algo nuevo en MySQL 4, ya que desde la versión 3.23 se podía hacer uso de tablas InnoDB, la única diferencia es que con la llegada de la versión 4.0 de MySQL, el soporte para este tipo de tablas es habilitado por default. Las transacciones aportan una fiabilidad superior a las bases de datos. Si disponemos de una serie de consultas SQL que deben ejecutarse en conjunto, con el uso de transacciones podemos tener la certeza de que nunca nos quedaremos a medio camino de su ejecución. De hecho, podríamos decir que las transacciones aportan una característica de "deshacer" a las aplicaciones de bases de datos. Para este fin, las tablas que soportan transacciones, como es el caso de InnoDB, son mucho más seguras y fáciles de recuperar si se produce algún fallo en el servidor, ya que las consultas se ejecutan o no en su totalidad. Por otra parte, las transacciones pueden hacer que las consultas tarden más tiempo en ejecutarse. Seguramente alguna vez hemos escuchado hablar acerca de las transacciones, pero probablemente no entendemos bien lo que son, y como deben de ser usadas. La idea de este artículo es presentar algunos ejemplos para mostrar que no es nada complicado, y difícil de aprender. En este artículo se asume que se cuenta ya con un servidor MySQL con soporte para el tipo de tablas InnoDB. En nuestro caso haremos uso de un servidor MySQL 4.013 ejecutándose en un sistema MSWindows. Para asegurarnos que tenemos soporte para el tipo de tablas InnoDB podemos ejecutar la siguiente sentencia: mysql> SHOW VARIABLES LIKE '%innodb%'; +---------------------------------+------------+ | Variable_name | Value | +---------------------------------+------------+ | have_innodb | YES | | innodb_additional_mem_pool_size | 1048576 | | innodb_buffer_pool_size | 8388608 | | innodb_data_file_path | ibdata:30M | | innodb_data_home_dir | | | innodb_force_recovery | 0 | | innodb_thread_concurrency | 8 | | innodb_fast_shutdown | ON | | innodb_flush_method | | | innodb_log_arch_dir | .\ | .... La variable más importante es por supuesto have_innodb que tiene el valor YES. En efecto, una de las principales características de las tablas del tipo InnoDB es que pueden trabajar con transacciones, o sentencias SQL que son agrupadas como una sola. Un ejemplo típico de esto es una transacción bancaria. Por ejemplo, si una cantidad de dinero es transferida de la cuenta de una persona a otra, se requerirán por lo menos dos consultas: UPDATE cuentas SET balance = balance - cantidad_transferida WHERE cliente = persona1; UPDATE cuentas SET balance = balance + cantidad_transferida WHERE cliente = persona2; Estas dos consultas deben trabajar bien, ¿pero qué sucede si ocurre algún imprevisto y "se cae" el sistema después de que se ejecuta la primer consulta, pero la segunda aún no se ha completado?. La persona1 tendrá una cantidad de dinero removida de su cuenta, y creerá que ha realizado su pago, sin embargo, la persona2 estará enfadada puesto que pensará que no se le ha depositado el dinero que le deben. En este ejemplo tan sencillo se ilustra la necesidad de que las consultas sean ejecutadas de manera conjunta, o en su caso, que no se ejecute ninguna de ellas. Es aquí donde las transacciones toman un papel muy importante. procedemos con el código SQL estándar CREATE TABLE. Query OK.08 sec) mysql> INSERT INTO innotest VALUES(2).04 sec) mysql> SELECT * FROM innotest. Vamos a ejecutar algunas consultas para ver cómo trabajan las transacciones.Los pasos para usar transacciones en MySQL son: • • • • Iniciar una transacción con el uso de la sentencia BEGIN. 1 row affected (0. Query OK. y los cambios realizados sobre la tabla no tendrán efecto. 0 rows affected (0. Actualizar.00 sec) .00 sec) De acuerdo. Para crear una tabla InnoDB. 1 row affected (0. Ahora veamos cómo usar transacciones. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | +-------+ 3 rows in set (0. pero debemos especificar que se trata de una tabla del tipo InnoDB (TYPE= InnoDB). mysql> CREATE TABLE innotest (campo INT NOT NULL PRIMARY KEY) TYPE = InnoDB. Query OK. insertar o eliminar registros en la base de datos.10 sec) mysql> INSERT INTO innotest VALUES(1). +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | +-------+ 4 rows in set (0.06 sec) mysql> SELECT * FROM innotest. Únicamente cuando se procesa un COMMIT los cambios hechos por las consultas serán permanentes. 1 row affected (0. Query OK. Query OK. nada espectacular. mysql> BEGIN.01 sec) mysql> INSERT INTO innotest VALUES(4). 0 rows affected (0. Query OK. Si sucede algún problema. pero cuando no se especifica nada.01 sec) mysql> INSERT INTO innotest VALUES(3). mysql> ROLLBACK. la transacción no será completada. 1 row affected (0. MySQL supone que se trata de una tabla MyISAM. Si se quieren los cambios a la base de datos. Esto es aplicable a cualquier tipo de tabla. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | +-------+ 3 rows in set (0. Lo primero que tenemos que hacer es crear una tabla del tipo InnoDB e insertar algunos datos. podemos hacer uso de la sentencia ROLLBACK para cancelar los cambios que han sido realizados por las consultas que han sido ejecutadas hasta el momento. completar la transacción con el uso de la sentencia COMMIT.00 sec) mysql> SELECT * FROM innotest. 0 rows affected (0.00 sec) Si en este momento ejecutamos un ROLLBACK. Query OK. 1 row affected (0. mysql> SELECT * FROM innotest. mysql> BEGIN. Your MySQL connection id is 450 to server version: 4. Welcome to the MySQL monitor. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | +-------+ 3 rows in set (0.00 sec) mysql> EXIT. Query OK. Bye Una vez que hacemos un COMMIT. 1 row affected (0. Query OK.00 sec) mysql> SELECT * FROM innotest. 0 rows affected (0.Ahora vamos a ver qué sucede si perdemos la conexión al servidor antes de que la transacción sea completada. ya que la transacción no fue completada. Query OK. Commands end with . podemos verificar que el registro no se insertó. mysql> BEGIN. la transacción es completada.02 sec) mysql> EXIT. mysql> SELECT * FROM innotest. pero haremos un COMMIT antes de perder la conexión al servidor al salir del monitor de MySQL. Bye Cuando obtengamos de nuevo la conexión. Your MySQL connection id is 449 to server version: 4.' or '\h' for help.13 Type 'help. or \g. Commands end with .13 Type 'help.' or '\h' for help. Type '\c' to clear the buffer. Query OK. Type '\c' to clear the buffer. 0 rows affected (0.0.00 sec) Ahora vamos a repetir la sentencia INSERT ejecutada anteriormente. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | +-------+ 4 rows in set (0. 0 rows affected (0.00 sec) . or \g.00 sec) mysql> INSERT INTO innotest VALUES(4).0. Query OK. y todas las sentencias SQL que han sido ejecutadas previamente afectan de manera permanente a las tablas de la base de datos. Welcome to the MySQL monitor. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | +-------+ 4 rows in set (0.00 sec) mysql> INSERT INTO innotest VALUES(4).00 sec) mysql> COMMIT. Ahora. MySQL regresa los valores presentes en la base de datos hasta la transacción más reciente que ha sido completada. desde la segunda conexión (ID 525) consultamos los datos de nuestra tabla. Your MySQL connection id is 525 to server version: 4. . desde la 1ra. Vamos a crear una sencilla tabla llamada ventas que sea del tipo InnoDB. 1 row affected (0.' or '\h' for help. Welcome to the MySQL monitor. mysql> SELECT * FROM innotest. necesitamos establecer dos conexiones al servidor MySQL.00 sec) Ahora. Para demostrar esto. Esto significa que cuando una sentencia SELECT es ejecutada. Commands end with .00 sec) Otro ejemplo En el ejemplo anterior hemos usado únicamente sentencias INSERT.0. los cambios hechos por alguna sentencia INSERT o UPDATE no serán reflejados. Si alguna transacción está en progreso. Primero agregaremos un registro dentro de una transacción con la primera conexión (ID 524): Welcome to the MySQL monitor.00 sec) mysql> INSERT INTO innotest VALUES(5). Your MySQL connection id is 524 to server version: 4.0. mysql> SELECT * FROM innotest.00 sec) Después de completar la transacción con una sentencia COMMIT en la 1ra. sucede lo mismo con sentencias UPDATE o DELETE. Query OK. conexión los cambios ya son visibles. mysql> SELECT * FROM innotest. Query OK. las tablas InnoDB ejecutan un lectura consistente (consistent read). sin embargo. el registro que hemos insertado desde la 1ra. Sin embargo.00 sec) Como se puede observar.Lecturas consistentes Por default. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | +-------+ 4 rows in set (0.13 Type 'help.' or '\h' for help. Type '\c' to clear the buffer. conexión no es regresado puesto que forma parte de una transacción que no ha sido completada. conexión ejecutamos la misma consulta SELECT. conexión podremos verificar que desde la 2da. Type '\c' to clear the buffer. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | | 5 | +-------+ 5 rows in set (0.13 Type 'help. mysql> BEGIN. existe una excepción: las transacciones abiertas si pueden ver sus propios cambios. or \g. 0 rows affected (0. +-------+ | campo | +-------+ | 1 | | 2 | | 3 | | 4 | | 5 | +-------+ 5 rows in set (0. Commands end with . or \g. cantidad TINYINT NOT NULL) TYPE = InnoDB. +----+------------------+----------+ | id | producto | cantidad | +----+------------------+----------+ | 1 | Gansito marinela | 2 | +----+------------------+----------+ .'Gansito marinela'.00 sec) Vamos a confirmar que deseamos los cambios. 0 rows affected (0.06 sec) Verificamos que se deshicieron los cambios.01 sec) Ahora vamos a iniciar una transacción con la sentencia BEGIN. Query OK. 1 row affected (0. Query OK. mysql> BEGIN. 0 rows affected (0. mysql> BEGIN. +----+------------------+----------+ | id | producto | cantidad | +----+------------------+----------+ | 1 | Gansito marinela | 2 | +----+------------------+----------+ 1 row in set (0.00 sec) mysql> UPDATE ventas SET cantidad=2 WHERE id=1. Query OK. entonces ejecutamos un ROLLBACK. Query OK.00 sec) Líneas correspondientes: 1 Cambiadas: 1 Avisos: 0 mysql> SELECT * FROM ventas. 1 row affected (0. +----+------------------+----------+ | id | producto | cantidad | +----+------------------+----------+ | 1 | Gansito marinela | 3 | +----+------------------+----------+ 1 row in set (0. -> producto VARCHAR(30) NOT NULL. Query OK. +----+------------------+----------+ | id | producto | cantidad | +----+------------------+----------+ | 1 | Gansito marinela | 3 | +----+------------------+----------+ 1 row in set (0. mysql> SELECT * FROM ventas. +----+------------------+----------+ | id | producto | cantidad | +----+------------------+----------+ | 1 | Gansito marinela | 4 | +----+------------------+----------+ 1 row in set (0. mysql> COMMIT. mysql> INSERT INTO ventas VALUES(0.mysql> CREATE TABLE ventas(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT.96 sec) Insertamos un registro. 0 rows affected (0. 0 rows affected (0.07 sec) Lgneas correspondientes: 1 Cambiadas: 1 Avisos: 0 Verificamos que los cambios han sucedido.00 sec) Si queremos deshacer los cambios. mysql> SELECT * FROM ventas.00 sec) Vamos a actualizar el registro usando otra transacción. Después de ejecutar el COMMIT los cambios serán permanentes y definitivos. mysql> ROLLBACK.05 sec) mysql> SELECT * FROM ventas. 1 row affected (0.00 sec) Actualizamos el registro. Query OK.16 sec) mysql> SELECT * FROM ventas. 0 rows affected (0.3). mysql> UPDATE ventas SET cantidad=4 WHERE id=1. Query OK. Query OK. 1 row in set (0.00 sec) .