En este momento estás viendo [MySQL] Transacciones en bases de datos

[MySQL] Transacciones en bases de datos

Seguro que muchos habéis oído hablar de las transacciones SQL. Sus usos, sus ventajas, sus inconvenientes y toda la teoría relacionada están bastante bien documentados.

Así que, en lugar de volver a explicar lo mismo, vamos directamente a un caso práctico y sencillo para entender cuándo pueden resultar realmente útiles.

El problema

Supongamos que tenemos dos tablas en nuestra base de datos:

  • Una tabla de categorías.
  • Una tabla de productos.

En nuestro backend mostramos un listado de productos junto con la categoría a la que pertenece cada uno.

Hasta aquí, ningún misterio.

Pero imaginemos que necesitamos cambiar la categoría de más de 100 productos.

¿Vamos producto por producto?

Y todavía podemos complicarlo un poco más: queremos modificar también el nombre de la categoría y necesitamos que todos los productos asociados continúen apuntando correctamente a ella.

Es decir, necesitamos realizar varias operaciones relacionadas y queremos que todas se completen correctamente o que no se aplique ninguna.

Ahí es donde entran en juego las transacciones.

La idea fundamental

Una transacción nos permite agrupar varias operaciones de base de datos y tratarlas como una única unidad de trabajo. Si todo sale bien, hacemos COMMIT. Si algo falla, hacemos ROLLBACK y deshacemos los cambios realizados durante la transacción.

En nuestro caso, necesitamos actualizar las dos tablas dentro de la misma operación para evitar que la base de datos quede en un estado intermedio si algo falla.

Vistazo general del listado de productos con las categorías a las que pertenece cada uno de ellos.

Y ahora que tenemos claro el escenario, vamos a ponerlo en práctica.

La transacción con PDO

Si estamos utilizando PDO, podemos iniciar una transacción mediante:

$query->beginTransaction();

A partir de ese momento, las operaciones que realicemos formarán parte de la transacción.

Si todo termina correctamente:

$query->commit();

Si se produce algún problema:

$query->rollBack();

Vamos a verlo con nuestro ejemplo.

Ejemplo completo

try {

    $query = $this->dbh;

    // Comenzamos la transacción
    $query->beginTransaction();

    // Actualizamos la categoría
    $sql = "
        UPDATE tipoproducto
        SET tipoProducto = :tipoProducto,
            imgCategoria = :imgCategoria
        WHERE idTipo = :idTipo
    ";

    $stmt = $query->prepare($sql);

    $stmt->execute([
        ':idTipo'       => $idTipo,
        ':tipoProducto' => $tipoProducto,
        ':imgCategoria' => $imgCategoria
    ]);

    // Actualizamos los productos asociados
    $sql = "
        UPDATE productos
        SET tipoProducto = :tipoProducto
        WHERE idTipo = :idTipo
    ";

    $stmt = $query->prepare($sql);

    $stmt->execute([
        ':idTipo'       => $idTipo,
        ':tipoProducto' => $tipoProducto
    ]);

    // Si todo ha ido bien, confirmamos los cambios
    $query->commit();

} catch (Exception $e) {

    // Si algo ha fallado, deshacemos todos los cambios
    if ($query->inTransaction()) {
        $query->rollBack();
    }

    // Aquí podemos registrar el error en un log
    // error_log($e->getMessage());
}

¿Qué está ocurriendo aquí?

El proceso es bastante sencillo.

Primero iniciamos la transacción:

$query->beginTransaction();

A continuación actualizamos la primera tabla:

UPDATE tipoproducto
SET tipoProducto = :tipoProducto,
    imgCategoria = :imgCategoria
WHERE idTipo = :idTipo

Después actualizamos los productos relacionados:

UPDATE productos
SET tipoProducto = :tipoProducto
WHERE idTipo = :idTipo

Y finalmente, si ninguna operación ha producido una excepción, confirmamos los cambios:

$query->commit();

Si alguna de las operaciones falla, entramos en el bloque catch y ejecutamos:

$query->rollBack();

De esta forma, los cambios realizados durante la transacción se deshacen.

Importante: una transacción no hace magia

Para que este mecanismo funcione correctamente, las tablas y el motor de almacenamiento deben soportar transacciones. En MySQL y MariaDB, por ejemplo, InnoDB es el motor habitual cuando necesitamos este comportamiento.

¿Por qué utilizamos parámetros en la consulta?

Hay otro detalle importante en el código anterior.

En el código original las variables se concatenaban directamente dentro de las consultas SQL:

$sql = "UPDATE productos SET tipoProducto = '$tipoProducto' WHERE idTipo = '$idTipo'";

Aunque posteriormente se utilizara prepare(), la consulta ya había sido construida mediante concatenación.

Es mejor utilizar sentencias preparadas y parámetros desde el principio:

$sql = "
    UPDATE productos
    SET tipoProducto = :tipoProducto
    WHERE idTipo = :idTipo
";

$stmt = $query->prepare($sql);

$stmt->execute([
    ':tipoProducto' => $tipoProducto,
    ':idTipo'       => $idTipo
]);

Además de hacer el código más limpio, evitamos construir consultas mediante concatenación de valores proporcionados por variables.

Una buena costumbre

Si estamos utilizando PDO, acostumbrémonos a utilizar parámetros en nuestras consultas en lugar de concatenar directamente los valores.

¿Qué ocurre si algo falla?

Imaginemos que la primera actualización funciona correctamente:

UPDATE tipoproducto ...

pero la segunda operación falla:

UPDATE productos ...

Sin una transacción, podríamos terminar con la primera operación aplicada y la segunda sin aplicar.

Eso puede dejarnos la información en un estado inconsistente.

Con una transacción, en cambio, podemos hacer:

$query->rollBack();

y deshacer los cambios realizados dentro de esa transacción.

La idea puede resumirse de una forma muy sencilla:

Todo correcto → COMMIT

Algo falla → ROLLBACK

¿Cuándo tiene sentido utilizar transacciones?

No necesitamos utilizar una transacción para absolutamente cualquier consulta.

Son especialmente útiles cuando tenemos varias operaciones que dependen unas de otras y necesitamos garantizar que se ejecutan como una única unidad lógica.

Algunos ejemplos habituales:

  • Crear un pedido y sus líneas.
  • Actualizar el stock y registrar el movimiento correspondiente.
  • Transferir dinero entre cuentas.
  • Modificar información relacionada en varias tablas.
  • Crear registros dependientes entre sí.
  • Realizar procesos de migración o actualización de datos.

En todos estos casos puede ser importante evitar que una parte de la operación se complete mientras otra queda a medias.

En resumen

Las transacciones SQL nos permiten ejecutar varias operaciones como una unidad de trabajo.

En nuestro ejemplo:

  1. Iniciamos la transacción.
  2. Actualizamos la categoría.
  3. Actualizamos los productos relacionados.
  4. Si todo funciona, hacemos COMMIT.
  5. Si algo falla, hacemos ROLLBACK.

Y con esto evitamos uno de esos problemas que aparecen cuando menos nos interesa: tener una parte de nuestros datos actualizada y otra parte a medias.

La idea importante

Una transacción no consiste simplemente en ejecutar varias consultas seguidas. Consiste en conseguir que esas operaciones formen parte de una misma unidad lógica: o se aplican todas, o deshacemos los cambios.

Y para un caso como el que planteábamos al principio, donde necesitamos modificar información relacionada en varias tablas, es precisamente lo que necesitamos.

Un saludo.

Visitas: 702

Website |  + posts

Automation & Data Specialist | Web Development | Data Processing & Integration

Serna Studio

Automation & Data Specialist | Web Development | Data Processing & Integration

Deja una respuesta