Una transacción es una unidad de trabajo en una base de datos. En una transacción, varias operaciones SQL pueden ejecutarse como un bloque. Las transacciones son importantes porque permiten que las operaciones sean atómicas, lo que significa que todas las operaciones dentro de la transacción deben completarse correctamente, o ninguna de ellas se aplica.
Las transacciones se basan en las características ACID:
Atomicidad: Todo o nada. O se realizan todas las operaciones de la transacción, o ninguna.
Consistencia: La base de datos pasa de un estado válido a otro estado válido.
Aislamiento: Las operaciones de una transacción no son visibles para otras transacciones hasta que se completen.
Durabilidad: Una vez que se confirma una transacción, sus efectos son permanentes.
3.6.2 Sintaxis de transacciones en SQL
START TRANSACTION o BEGIN: Comienza una nueva transacción.
COMMIT: Confirma la transacción y hace permanentes los cambios realizados.
ROLLBACK: Revierte la transacción y deshace todos los cambios realizados.
Estas instrucciones son amplamente utilizadas en la declaración de procedimientos y funciones almacenadas. Cuano estas instrucciones tienen que realizar cambios en la base de datos, ejecutando diferentes sentencias SQL, es importante que todas las operaciones se realicen correctamente. Si alguna de ellas falla, es necesario revertir todos los cambios realizados hasta ese momento.
Vamos a describir por ejemplo el caso de la actualización de una cuenta bancaria. Imaginemos que tenemos una tabla cuentas con los siguientes campos:
id: Identificador de la cuenta.
saldo: Saldo actual de la cuenta.
estado: Estado de la cuenta (activa, bloqueada, etc.).
Supongamos que queremos transferir dinero de una cuenta a otra. Para ello, necesitamos realizar dos operaciones:
Restar el dinero de la cuenta de origen.
Sumar el dinero a la cuenta de destino.
Si alguna de estas operaciones falla, no podemos dejar la base de datos en un estado inconsistente. Por lo tanto, utilizamos una transacción para asegurarnos de que ambas operaciones se realicen correctamente o ninguna de ellas.
Si alguna de las operaciones falla, podemos revertir la transacción utilizando ROLLBACK:
ROLLBACK;
Esto deshará todos los cambios realizados en la transacción, dejando la base de datos en su estado original. Llegados a este punto podemos hacer un ejercicio práctico utiliando nuestro editor de SQL favorito.
1 2 3 4 5 6 7 8 91011121314151617181920
-- Crear la tabla cuentasCREATETABLEcuentas(idINTPRIMARYKEY,saldoDECIMAL(10,2)CHECK(saldo>=0),estadoENUM('activa','bloqueada'));-- Insertar datos de ejemploINSERTINTOcuentas(id,saldo,estado)VALUES(1,1000.00,'activa'),(2,500.00,'activa');-- Iniciar la transacciónSTARTTRANSACTION;-- Restar 100 de la cuenta 1UPDATEcuentasSETsaldo=saldo-100WHEREid=1;-- Sumar 100 a la cuenta 2UPDATEcuentasSETsaldo=saldo+100WHEREid=2;-- Confirmar la transacciónCOMMIT;-- Verificar los resultadosSELECT*FROMcuentas;
En este ejemplo, hemos creado una tabla cuentas y hemos realizado una transferencia de 100 unidades de una cuenta a otra. Si todo sale bien, confirmamos la transacción. Si algo falla, podemos revertirla. Vamos a generar un error para ver cómo funciona el ROLLBACK.
1 2 3 4 5 6 7 8 910
-- Iniciar la transacciónSTARTTRANSACTION;-- Restar 500 a la cuenta 1 UPDATEcuentasSETsaldo=saldo-500WHEREid=1;-- Intentar sumar los 500 a la cuenta 3. Esto generará un error porque la cuenta 3 no existe.UPDATEcuentasSETsaldo=saldo+500WHEREid=3;RealizarunrollbackROLLBACK;-- Verificar que las cuentas no han cambiadoSELECT*FROMcuentas;
3.6.3 Transacciones con MySQLi y PDO
En MySQLi y PDO, podemos manejar transacciones utilizando funciones específicas para iniciar, confirmar y revertir las transacciones.
MySQLi (procedimental y orientado a objetos):
Iniciar una transacción: Utilizamos begin_transaction() o START TRANSACTION.
Confirmar una transacción: Usamos commit().
Revertir una transacción: Usamos rollback().
PDO:
Iniciar una transacción: Usamos $conexion->beginTransaction().
Confirmar una transacción: Usamos $conexion->commit().
Revertir una transacción: Usamos $conexion->rollBack().
3.6.4 Ejemplo práctico de transacciones
Vamos a realizar una operación que implique una transacción: Vamos a actualizar el salario de un empleado y registrar ese cambio en una tabla salary_changes.
1. Crear la tabla salary_changes
Primero, necesitamos crear una tabla salary_changes para almacenar el historial de cambios de salario. Esta tabla tendrá los campos emp_no, old_salary, new_salary, change_date.
2. Procedimiento para actualizar el salario y registrar el cambio en salary_changes
Vamos a crear una transacción que primero registre el cambio de salario en la tabla salary_changes y luego actualice el salario del empleado en la tabla salaries.
Ejemplo de transacción con MySQLi (orientado a objetos)
<?php$conexion=newmysqli("localhost","root","","employees");if($conexion->connect_error){die("Conexión fallida: ".$conexion->connect_error);}// Comienza la transacción$conexion->begin_transaction();try{// Parámetros de ejemplo$emp_id=1001;$nuevo_salario=65000;// Obtener el salario actual$query="SELECT salary FROM salaries WHERE emp_no = ? AND to_date = '9999-01-01'";$stmt=$conexion->prepare($query);$stmt->bind_param("i",$emp_id);$stmt->execute();$stmt->bind_result($salario_actual);$stmt->fetch();$stmt->close();// Registrar el cambio de salario en salary_changes$query="INSERT INTO salary_changes (emp_no, old_salary, new_salary, change_date) VALUES (?, ?, ?, NOW())";$stmt=$conexion->prepare($query);$stmt->bind_param("idd",$emp_id,$salario_actual,$nuevo_salario);$stmt->execute();$stmt->close();// Actualizar el salario en salaries$query="UPDATE salaries SET salary = ?, from_date = NOW() WHERE emp_no = ? AND to_date = '9999-01-01'";$stmt=$conexion->prepare($query);$stmt->bind_param("di",$nuevo_salario,$emp_id);$stmt->execute();$stmt->close();// Confirmar la transacción$conexion->commit();echo"Salario actualizado correctamente.";}catch(Exception$e){// Si hay un error, revertir la transacción$conexion->rollback();echo"Error: ".$e->getMessage();}$conexion->close();?>
<?php// Crear conexión$conexion=mysqli_connect("localhost","root","","employees");if(!$conexion){die("Conexión fallida: ".mysqli_connect_error());}// Comienza la transacciónmysqli_begin_transaction($conexion);try{// Parámetros de ejemplo$emp_id=1001;$nuevo_salario=65000;// Obtener el salario actual$query="SELECT salary FROM salaries WHERE emp_no = $emp_id AND to_date = '9999-01-01'";$resultado=mysqli_query($conexion,$query);if(!$resultado){thrownewException("Error al obtener el salario: ".mysqli_error($conexion));}$row=mysqli_fetch_assoc($resultado);$salario_actual=$row['salary'];// Registrar el cambio de salario en salary_changes$query="INSERT INTO salary_changes (emp_no, old_salary, new_salary, change_date) VALUES ($emp_id, $salario_actual, $nuevo_salario, NOW())";$resultado=mysqli_query($conexion,$query);if(!$resultado){thrownewException("Error al insertar el cambio de salario: ".mysqli_error($conexion));}// Actualizar el salario en salaries$query="UPDATE salaries SET salary = $nuevo_salario, from_date = NOW() WHERE emp_no = $emp_id AND to_date = '9999-01-01'";$resultado=mysqli_query($conexion,$query);if(!$resultado){thrownewException("Error al actualizar el salario: ".mysqli_error($conexion));}// Confirmar la transacciónmysqli_commit($conexion);echo"Salario actualizado correctamente.";}catch(Exception$e){// Si hay un error, revertir la transacciónmysqli_rollBack($conexion);echo"Error: ".$e->getMessage();}// Cerrar la conexiónmysqli_close($conexion);?>
<?phptry{$conexion=newPDO("mysql:host=localhost;dbname=employees","root","");$conexion->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);// Comienza la transacción$conexion->beginTransaction();// Parámetros de ejemplo$emp_id=1001;$nuevo_salario=65000;// Obtener el salario actual$query="SELECT salary FROM salaries WHERE emp_no = :emp_no AND to_date = '9999-01-01'";$stmt=$conexion->prepare($query);$stmt->bindParam(':emp_no',$emp_id,PDO::PARAM_INT);$stmt->execute();$stmt->bindColumn(1,$salario_actual);$stmt->fetch(PDO::FETCH_ASSOC);// Registrar el cambio de salario en salary_changes$query="INSERT INTO salary_changes (emp_no, old_salary, new_salary, change_date) VALUES (:emp_no, :old_salary, :new_salary, NOW())";$stmt=$conexion->prepare($query);$stmt->bindParam(':emp_no',$emp_id,PDO::PARAM_INT);$stmt->bindParam(':old_salary',$salario_actual,PDO::PARAM_STR);$stmt->bindParam(':new_salary',$nuevo_salario,PDO::PARAM_STR);$stmt->execute();// Actualizar el salario en salaries$query="UPDATE salaries SET salary = ?, from_date = NOW() WHERE emp_no = ? AND to_date = '9999-01-01'";$stmt=$conexion->prepare($query);$stmt->bindParam(1,$nuevo_salario,PDO::PARAM_STR);$stmt->bindParam(2,$emp_id,PDO::PARAM_INT);$stmt->execute();// Confirmar la transacción$conexion->commit();echo"Salario actualizado correctamente.";}catch(PDOException$e){// Si hay un error, revertir la transacción$conexion->rollBack();echo"Error: ".$e->getMessage();}$conexion=null;?>
3.6.5 Ejercicios prácticos
Ejercicio 1: Crea una transacción en PHP que cambien a un empleado de departamento, debe actualizar el departamento actual y crear un registro en dept_emp. Crealo con MySQLi y con PDO. Asegurate de que si to va bien la transacción se confirme, y si no, se revierta.
Ejercicio 2: Crea una transacción que canbiel el jefe de un departamento. Debe actualizar el jefe actual y crear un registro en dept_manager. Asegurate de que si to va bien la transacción se confirme, y si no, se revierta.
Resumen de la sección 3.6
Transacciones: Aprendimos cómo gestionar las transacciones en bases de datos para asegurar la atomicidad, consistencia y durabilidad de las operaciones.
Procedimientos: Vimos cómo crear procedimientos almacenados para realizar tareas como actualizar el salario de un empleado y registrar el cambio.
Funciones almacenadas: Aprendimos cómo trabajar con funciones que devuelven un valor, como calcular el salario promedio.
Errores y rollback: Aseguramos que las transacciones se manejen correctamente, utilizando commit() y rollback().