Usar transacciones en la base de datos

MySQL soporta nativamente las transacciones en las tablas InnoDB (y, por lo tanto, MariaDB y Percona).

Pero, ¿qué es eso de las transacciones?, vamos a verlo con un ejemplo muy simplificado.

En un sistema bancario, yo realizo una transferencia de 100 € a mi amigo Luis. Internamente, se hace un update de mi usuario restándome 100 € y se hace un nuevo update del usuario Luis añadiéndole 100 €. Hasta aquí todo correcto (y desde luego, muy simplificado).

Pero ¿qué pasa si después de quitarme a mí los 100 € el sistema falla y, por lo tanto, no le añade los 100 € a Luis?, pues que quedan 100 € perdidos en tierra de nadie.

Para evitar esto, están las transacciones. Tenemos una serie de operaciones, que se deben ejecutar todas, si todo va bien confirmamos y se ejecutan, si algo falla por el camino volvemos atrás y queda todo estaba.

Eso son las transacciones, que iniciamos con START TRANSACTION; o con BEGIN; y si todo es como esperábamos, la confirmamos (y se ejecuta) con COMMIT; o si detectamos algún fallo, volvemos atrás con ROLLBACK;

WordPress no está construido con transacciones, aunque nosotros podemos utilizarlas en nuestras consultas (en el 99 % de los casos no serían necesarias), pero nada nos impide utilizarlas en nuestras pruebas.

Escenario de uso real

En este caso, además de descargar la base de datos y realizarlo en local, era un poco complicado realizar el update y si iba mal, volver a restaurar la Base de Datos y volver a probar, así que usé las transacciones directamente en MySQL.

Se trataba de una web que llevaba online muchos años y había pasado por diversos hostings y de los miles de fotos que tenía, había unas 100 que no podía regenerar desde WP Cli por tener mal la ruta en los metadatos (wp media regenerate --only-missing).

Al ver uno de los errores Warning: Can't find "xxxyyyzzz" (ID 1881). y buscar el ID en la tabla wp_postmeta se veía que el valor de _wp_attached_file era incorrecto, del tipo /home/.xxxx/yyyy/www.zzzz.com/wp-content/uploads/2008/09/xxxyyyzzz.jpg en lugar de 2008/09/xxxyyyzzz.jpg

La solución era efectuar el reemplazo en la base de datos, pero para evitar errores y nuevas pruebas restaurando la base de datos, la solución fue comenzar una transacción, buscar, realizar update, buscar de nuevo, mirar el valor de ese campo ID en concreto y al comprobar que todo está correcto ejecutar el update.

START TRANSACTION;

SELECT * FROM wp_postmeta WHERE meta_value like '/home/.xxxx/yyyy/www.zzzz.com/wp-content/uploads/%';

UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, '/home/.xxxx/yyyy/www.zzzz.com/wp-content/uploads/', '') WHERE meta_value like '/home/.xxxx/yyyy/www.zzzz.com/wp-content/uploads/%';

SELECT * FROM wp_postmeta WHERE meta_value like '/home/.xxxx/yyyy/www.zzzz.com/wp-content/uploads/%';

SELECT * FROM wp_postmeta WHERE post_id=1881;

ROLLBACK;

Iniciamos una transacción.

En la primera consulta buscamos todas las entradas que contengan dicha cadena y nos devuelve unos 100 registros.

En la segunda consulta ejecutamos un UPDATE eliminando esa cadena y manteniendo el resto de la ruta.

En la tercera consulta, volvemos a ejecutar la primera, pero ahora nos da cero resultados.

En la cuarta consulta miramos el postmeta del ID original para comprobar que la ruta está correcta.

Y a continuación volvemos todo al estado anterior.

MySQL transactions

Y todo ha quedado como estaba, sin ningún cambio, pero verificando que nuestra consulta era correcta. En realidad la manera correcta de utilizarlo es con comprobaciones y realizar ROLLBACK o COMMIT según el resultado.

Pero en este caso era algo manual, en el que reviso resultados y al ver que todo está correcto, simplemente ejecuto la consulta de UPDATE ya sin transacciones al comprobar que los resultados son los deseados.

Y como veis, esto mismo lo podemos aplicar a multitud de situaciones dónde no estamos seguros al 100 % de los resultados sobre la BD. Recordad siempre crear backups antes de hacer nada, por muy seguro que sea lo que vamos a hacer y si es posible, trabajad siempre sobre una copia (desarrollo, staging, local, etc.).

Deja un comentario