Por qué no uso Triggers ni Procedures en SQL
Triggers/Procedures versus Observers
Llevo más de 30 años trabajando con SQL en sistemas UNIX, dando soporte a miles de clientes y actuando como bombero IT. Sí, bombero, porque apago fuegos técnicos que surgen como resultado de malas prácticas, o, lo que es peor, de seguir utilizando reliquias del pasado sin actualizarse.
Una de esas reliquias son los TRIGGERS y las PROCEDURES.
Qué bonitas eran en su día, qué problemáticas eran y qué problemáticas siguen siendo.
La cuestión es que un motor de bases de datos es lo que es, y en mi opinión, introducir lógicas de programación dentro del motor de bases de datos, a día de hoy, está obsoleto y es peligroso (siempre lo fue).
- Un cambio de versión del motor SGBD.
- Un cambio de SGBD.
- Un problema que obliga a restaurar desde un backup y este es incompleto por error del comando usado en la copia de seguridad.
Y seguramente habrá más.
Lo cierto es que todo eso incumple los principios SOLID, al desplazar en un nivel superior, ya no en la aplicación, sino en el motor de datos, unas lógicas que son responsabilidad del programa. Al menos así lo veo en el año 2024.
Por ejemplo, podemos utilizar Observers a nivel de aplicación.
- Fáciles de programar.
- Dentro de la app.
- Cumplen el objetivo de ser sólidos en nuestra aplicación, y dotan de la responsabilidad al modelo sobre el que se ejecutarían esos triggers.
- Fácilmente testeables (o los tests, esa cosa que se sigue odiando en muchos lugares, quizás por la falta de... visión o ganas de actualizarse).
Anécdota
En el caso que me ocupa, un Magento 1.9, obsoleto, corriendo en un MariaDB 10.4, PHP 5.4, etc., y que está en producción con decenas de ventas al día.
No se puede trabajar en producción, y mucho menos si la aplicación se llama Magento, paradigma de los problemas en el comercio electrónico.
Así que cuando me enfrenté a ello, y pese a ser root, estar acostumbrado a lidiar con MySQL (y sus forks), me entró la pena negra al ver que había triggers heredados de un intento de migración con la herramienta oficial de Magento para tratar de pasar a Magento 2.
No era la primera vez. Alguna vez lo había visto en un restore de urgencia de otro cliente.
Algo que te rompe cuando estás con bases de datos de 5 a 10Gb, bajo presión, en producción,...
ERROR 1449 (HY000) at line 924: The user specified as a definer ('migration'@'99.99.99.99') does not exist
Bueno, como ya sabía de qué iba al ver la palabra migration y el contexto Magento, sabía que había más de un trigger.
Procesos de reparación
Obtener todos los triggers
mysql> SHOW TRIGGERS;
Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation
trg_catalog_category_entity_after_insert INSERT catalog_category_entity BEGIN\nINSERT INTO m2_cl_catalog_category_entity (`entity_id`, `operation`) VALUES (NEW.entity_id, 'INSERT')ON DUPLICATE KEY UPDATE operation = 'INSERT';\nEND AFTER NULL migration@176.31.31.251 utf8 utf8_general_ci latin1_swedish_ci
...
...
Script para eliminar triggers
Mejor un script, que llamaremos drop_triggers.sh
.
# Reemplaza los valores de `usuario`, `password`, y `nombre_de_tu_base` con tus propios valores.
MYSQL_USER="usuario"
MYSQL_PASS="password"
DATABASE_NAME="nombre_de_tu_base"
# Obtiene la lista de triggers y los elimina uno por uno
mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -D"$DATABASE_NAME" -e "SELECT TRIGGER_NAME FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = '$DATABASE_NAME';" | grep -v TRIGGER_NAME | while read TRIGGER_NAME; do
mysql -u"$MYSQL_USER" -p"$MYSQL_PASS" -D"$DATABASE_NAME" -e "DROP TRIGGER IF EXISTS \`$TRIGGER_NAME\`;";
done
Por favor, en serio. Este es algo a tener en cuenta. Backups y backups, y de los que sabes que funcionarán. Yo no soy responsable de tus actos (ni nadie).
Pruebas en docker
Bueno, si antes de hacer líos, necesitas trabajar en un entorno de desarrollo, con docker, te dejo unos pasos, ya que el otro puede ser algo más completo.
Obtener la lista
Puedes como alternativa obtener la lista de trigger de la base de datos, con TablePlus en formato CSV, y crear un fichero con la columna que define el nombre del trigger, por ejemplo triggers.txt
.
Docker
Obtenemos el nombre del container de mysql
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b49e62fa5b29 web4-apache-httpd "httpd-foreground" 19 hours ago Up 19 hours 0.0.0.0:8080->80/tcp web4-apache-httpd-1
bfd56c6f6c65 web4-php-fpm "docker-php-entrypoi…" 19 hours ago Up 19 hours 9000/tcp web4-php-fpm-1
d804a906babd mariadb:10.1 "docker-entrypoint.s…" 19 hours ago Up 19 hours 0.0.0.0:3337->3306/tcp web4-mysql-db-1
Subimos el fichero
docker cp triggers.sh web4-mysql-db-1:/root/
Y ejecutarlo en el container.
$ docker exec -it web4-mysql-db-1 bash
root@d804a906babd:/# cd /root
# chmod 755 delete_triggers.sh
# ./delete_triggers.sh
Eliminando trigger: trg_catalog_category_entity_after_insert
Eliminando trigger: trg_catalog_category_entity_after_update
Eliminando trigger: trg_catalog_category_entity_after_delete
Eliminando trigger: trg_catalog_category_entity_datetime_after_insert
Eliminando trigger: trg_catalog_category_entity_datetime_after_update
Eliminando trigger: trg_catalog_category_entity_datetime_after_delete
Eliminando trigger: trg_catalog_category_entity_decimal_after_insert
Eliminando trigger: trg_catalog_category_entity_decimal_after_update
Eliminando trigger: trg_catalog_category_entity_decimal_after_delete
Eliminando trigger: trg_catalog_category_entity_int_after_insert
Eliminando trigger: trg_catalog_category_entity_int_after_update
Eliminando trigger: trg_catalog_category_entity_int_after_delete
Eliminando trigger: trg_catalog_category_entity_text_after_insert
Eliminando trigger: trg_catalog_category_entity_text_after_update
Eliminando trigger: trg_catalog_category_entity_text_after_delete
Eliminando trigger: trg_catalog_category_entity_varchar_after_insert
Eliminando trigger: trg_catalog_category_entity_varchar_after_update
Eliminando trigger: trg_catalog_category_entity_varchar_after_delete
Eliminando trigger: trg_catalog_category_product_after_insert
Eliminando trigger: trg_catalog_category_product_after_update
Eliminando trigger: trg_catalog_category_product_after_delete
Eliminando trigger: trg_catalog_compare_item_after_insert
Eliminando trigger: trg_catalog_compare_item_after_update
Eliminando trigger: trg_catalog_compare_item_after_delete
Eliminando trigger: trg_catalog_product_bundle_option_after_insert
Eliminando trigger: trg_catalog_product_bundle_option_after_update
Eliminando trigger: trg_catalog_product_bundle_option_after_delete
Eliminando trigger: trg_catalog_product_bundle_option_value_after_insert
Eliminando trigger: trg_catalog_product_bundle_option_value_after_update
Eliminando trigger: trg_catalog_product_bundle_option_value_after_delete
Eliminando trigger: trg_catalog_product_bundle_selection_after_insert
Eliminando trigger: trg_catalog_product_bundle_selection_after_update
Eliminando trigger: trg_catalog_product_bundle_selection_after_delete
Eliminando trigger: trg_catalog_product_bundle_selection_price_after_insert
Eliminando trigger: trg_catalog_product_bundle_selection_price_after_update
Eliminando trigger: trg_catalog_product_bundle_selection_price_after_delete
Eliminando trigger: trg_catalog_product_entity_after_insert
Eliminando trigger: trg_catalog_product_entity_after_update
Eliminando trigger: trg_catalog_product_entity_after_delete
Eliminando trigger: trg_catalog_product_entity_datetime_after_insert
Eliminando trigger: trg_catalog_product_entity_datetime_after_update
Eliminando trigger: trg_catalog_product_entity_datetime_after_delete
Eliminando trigger: trg_catalog_product_entity_decimal_after_insert
Eliminando trigger: trg_catalog_product_entity_decimal_after_update
Eliminando trigger: trg_catalog_product_entity_decimal_after_delete
Eliminando trigger: trg_catalog_product_entity_group_price_after_insert
Eliminando trigger: trg_catalog_product_entity_group_price_after_update
Eliminando trigger: trg_catalog_product_entity_group_price_after_delete
Toda una aventura, que en modo stress, no es buena para nadie.
Finales
Muchas veces oigo en mi trabajo, "Es que se puede hacer así".
Con mil cosas, caracteres extendidos, desarrollos con millones de ficheros en la misma carpeta, etc. Y la misma cantinela. "Ahora el límite..." "Se puede..."
La realidad es muy distinta. Y eso se aprende en el infierno de los sistemas, cuando llaman a nuestra puerta.
Una cosa es que se pueda y otra muy distinta, es que se deba.
Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris
Si necesitas soporte profesional puedes contratar con Castris soporte profesional.