Introducción a las transacciones
Una transacción es un conjunto de operaciones SQL que se ejecutan como una unidad indivisible. O se ejecutan todas correctamente, o no se ejecuta ninguna.
¿Por qué necesitamos transacciones?
Imagina un proceso de compra en nuestra tienda:
-- Paso 1: Crear el pedido
INSERT INTO pedidos (cliente_id, fecha, estado, total)
VALUES (1, NOW(), 'pendiente', 1299.99);
-- Paso 2: Agregar el detalle
INSERT INTO detalle_pedidos (pedido_id, producto_id, cantidad, precio_unitario)
VALUES (LAST_INSERT_ID(), 1, 1, 1299.99);
-- Paso 3: Descontar el stock
UPDATE productos SET stock = stock - 1 WHERE id = 1;Si el servidor falla después del paso 1 pero antes del paso 3, tendríamos un pedido sin detalle y sin descuento de stock. Las transacciones evitan este problema.
Propiedades ACID
Las transacciones garantizan cuatro propiedades fundamentales:
| Propiedad | Significado | Ejemplo |
|---|---|---|
| Atomicidad | Todo o nada | Si falla el paso 3, se revierten los pasos 1 y 2 |
| Consistencia | La base de datos pasa de un estado válido a otro | El stock nunca queda negativo |
| Aislamiento | Las transacciones concurrentes no interfieren entre sí | Dos compras simultáneas no venden el mismo producto |
| Durabilidad | Los cambios confirmados persisten ante fallos | Si el servidor se reinicia, los datos están intactos |
Transacciones en MySQL
Motor de almacenamiento
Solo InnoDB soporta transacciones. MyISAM no las soporta.
-- Verificar el motor de una tabla
SHOW TABLE STATUS WHERE Name = 'productos'\G-- Verificar el motor predeterminado
SHOW VARIABLES LIKE 'default_storage_engine';| Variable_name | Value |
|---|---|
| default_storage_engine | InnoDB |
Autocommit
Por defecto, MySQL opera en modo autocommit: cada sentencia individual es una transacción que se confirma automáticamente.
-- Ver el estado de autocommit
SELECT @@autocommit;| @@autocommit |
|---|
| 1 |
-- Desactivar autocommit (cada sentencia requiere COMMIT explícito)
SET autocommit = 0;
-- Reactivar
SET autocommit = 1;Sintaxis básica
-- Iniciar una transacción explícita
START TRANSACTION;
-- Ejecutar operaciones
INSERT INTO pedidos (cliente_id, fecha, estado, total)
VALUES (1, NOW(), 'pendiente', 500.00);
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- Confirmar todos los cambios
COMMIT;-- Si algo falla, revertir todo
START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 1;
-- Revertir los cambios
ROLLBACK;
-- El stock vuelve a su valor originalEjemplo completo: proceso de compra
DELIMITER //
CREATE PROCEDURE sp_crear_pedido(
IN p_cliente_id INT,
IN p_producto_id INT,
IN p_cantidad INT,
OUT p_pedido_id INT
)
BEGIN
DECLARE v_precio DECIMAL(10,2);
DECLARE v_stock INT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SET p_pedido_id = -1;
END;
START TRANSACTION;
-- Verificar stock
SELECT precio, stock INTO v_precio, v_stock
FROM productos
WHERE id = p_producto_id
FOR UPDATE;
IF v_stock < p_cantidad THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Stock insuficiente';
END IF;
-- Crear pedido
INSERT INTO pedidos (cliente_id, fecha, estado, total)
VALUES (p_cliente_id, NOW(), 'pendiente', v_precio * p_cantidad);
SET p_pedido_id = LAST_INSERT_ID();
-- Agregar detalle
INSERT INTO detalle_pedidos (pedido_id, producto_id, cantidad, precio_unitario)
VALUES (p_pedido_id, p_producto_id, p_cantidad, v_precio);
-- Descontar stock
UPDATE productos
SET stock = stock - p_cantidad
WHERE id = p_producto_id;
COMMIT;
END //
DELIMITER ;-- Usar el procedimiento
CALL sp_crear_pedido(1, 3, 2, @pedido_id);
SELECT @pedido_id;Sentencias que causan COMMIT implícito
Algunas sentencias confirman automáticamente cualquier transacción abierta:
| Categoría | Sentencias |
|---|---|
| DDL | CREATE TABLE, ALTER TABLE, DROP TABLE, TRUNCATE TABLE |
| Administración | GRANT, REVOKE, SET PASSWORD |
| Control | LOCK TABLES, UNLOCK TABLES |
| Carga de datos | LOAD DATA |
START TRANSACTION;
INSERT INTO productos (nombre, precio, stock, categoria_id)
VALUES ('Test', 10.00, 5, 1);
-- Esto causa COMMIT implícito del INSERT anterior
CREATE TABLE temp_test (id INT);
-- Ya no se puede hacer ROLLBACK del INSERT
ROLLBACK; -- No revierte nada-- Limpieza
DROP TABLE IF EXISTS temp_test;
DELETE FROM productos WHERE nombre = 'Test';Transacciones de solo lectura
Para consultas que no modifican datos, puedes declarar la transacción como solo lectura, lo cual permite optimizaciones:
START TRANSACTION READ ONLY;
SELECT * FROM productos WHERE precio > 500;
SELECT COUNT(*) FROM pedidos WHERE estado = 'completado';
COMMIT;Cuándo usar transacciones
| Escenario | ¿Transacción? | Razón |
|---|---|---|
| Proceso de compra | Sí | Múltiples tablas deben actualizarse juntas |
| Transferencia entre cuentas | Sí | Debitar y acreditar son inseparables |
| Consulta simple | No | Autocommit es suficiente |
| INSERT individual | No | Autocommit es suficiente |
| Importación masiva | Sí | Rendimiento y consistencia |
| Actualización en lote | Sí | Poder revertir si algo falla |
Limpieza
DROP PROCEDURE IF EXISTS sp_crear_pedido;En el siguiente artículo veremos en detalle COMMIT y ROLLBACK.
Escrito por Eduardo Lázaro
