Curso de MySQL

Triggers en MySQL: acciones automáticas en la base de datos

Por Víctor Peña · Publicado el

Hola, ¿cómo están? Continuando con los temas complementarios del curso de MySQL, hoy veremos los triggers: código que se ejecuta solo, sin que nadie lo llame.

Son útiles y peligrosos a partes iguales, así que vamos con las dos caras.

¡Empecemos!

Qué es un trigger

Un trigger —o disparador— es un bloque de código asociado a una tabla, que MySQL ejecuta automáticamente cuando ocurre un INSERT, UPDATE o DELETE sobre ella.

La diferencia con un procedimiento almacenado es esa: el procedimiento se llama con CALL; el trigger se dispara solo.

Anatomía de un trigger

DELIMITER //

CREATE TRIGGER tr_citas_antes_insertar
BEFORE INSERT ON citas
FOR EACH ROW
BEGIN
    IF NEW.costo < 0 THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'El costo no puede ser negativo';
    END IF;
END //

DELIMITER ;

Las piezas:

  • BEFORE o AFTER — antes o después de la operación.
  • INSERT, UPDATE o DELETE — qué lo dispara.
  • ON citas — sobre qué tabla.
  • FOR EACH ROW — se ejecuta por cada fila afectada. Si un UPDATE toca 500 filas, el trigger corre 500 veces.

NEW y OLD

Dentro del trigger tienes acceso a los valores de la fila:

Evento NEW OLD
INSERT Los valores que entran No existe
UPDATE Los valores nuevos Los anteriores
DELETE No existe Los que se borran
-- En un UPDATE
IF NEW.costo <> OLD.costo THEN
    -- el costo cambió
END IF;

En un trigger BEFORE puedes modificar NEW. En uno AFTER no, porque la operación ya ocurrió.

Caso 1: validar y normalizar datos

DELIMITER //

CREATE TRIGGER tr_pacientes_antes_insertar
BEFORE INSERT ON pacientes
FOR EACH ROW
BEGIN
    -- Normalizar: quitar espacios y capitalizar
    SET NEW.nombre = TRIM(NEW.nombre);
    SET NEW.apellido = TRIM(NEW.apellido);
    SET NEW.correo = LOWER(TRIM(NEW.correo));

    -- Validar
    IF NEW.fecha_nacimiento > CURDATE() THEN
        SIGNAL SQLSTATE '45000'
        SET MESSAGE_TEXT = 'La fecha de nacimiento no puede ser futura';
    END IF;
END //

DELIMITER ;

SIGNAL SQLSTATE '45000' es la forma de lanzar un error personalizado que aborta la operación. El código 45000 es el genérico para errores definidos por el usuario.

Caso 2: auditoría

Este es el uso donde los triggers son realmente insustituibles: registrar quién cambió qué y cuándo.

CREATE TABLE auditoria_citas (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    cita_id INT UNSIGNED NOT NULL,
    campo VARCHAR(50) NOT NULL,
    valor_anterior VARCHAR(255),
    valor_nuevo VARCHAR(255),
    usuario VARCHAR(100),
    fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
DELIMITER //

CREATE TRIGGER tr_citas_auditoria
AFTER UPDATE ON citas
FOR EACH ROW
BEGIN
    IF NEW.estado <> OLD.estado THEN
        INSERT INTO auditoria_citas (cita_id, campo, valor_anterior, valor_nuevo, usuario)
        VALUES (NEW.id, 'estado', OLD.estado, NEW.estado, USER());
    END IF;

    IF NEW.costo <> OLD.costo THEN
        INSERT INTO auditoria_citas (cita_id, campo, valor_anterior, valor_nuevo, usuario)
        VALUES (NEW.id, 'costo', OLD.costo, NEW.costo, USER());
    END IF;
END //

DELIMITER ;

Lo valioso: funciona pase lo que pase. Da igual si el cambio vino de tu aplicación, de un script, de phpMyAdmin o de alguien escribiendo SQL a mano. La aplicación puede olvidarse de registrar; el trigger no.

Por eso en sistemas médicos, contables o legales la auditoría suele implementarse así.

Caso 3: mantener datos calculados

-- Añadimos una columna con el total gastado
ALTER TABLE pacientes ADD COLUMN total_gastado DECIMAL(12,2) DEFAULT 0;

DELIMITER //

CREATE TRIGGER tr_citas_actualizar_total
AFTER UPDATE ON citas
FOR EACH ROW
BEGIN
    IF NEW.estado = 'atendida' AND OLD.estado <> 'atendida' THEN
        UPDATE pacientes
        SET total_gastado = total_gastado + NEW.costo
        WHERE id = NEW.paciente_id;
    END IF;
END //

DELIMITER ;

Es la desnormalización que mencionamos en la lección de optimización: guardar un total precalculado para no sumarlo en cada consulta.

El trigger se encarga de mantenerlo sincronizado.

Caso 4: borrado lógico en cascada

DELIMITER //

CREATE TRIGGER tr_pacientes_borrado_logico
AFTER UPDATE ON pacientes
FOR EACH ROW
BEGIN
    IF NEW.eliminado_en IS NOT NULL AND OLD.eliminado_en IS NULL THEN
        UPDATE citas
        SET estado = 'cancelada'
        WHERE paciente_id = NEW.id AND estado = 'programada';
    END IF;
END //

DELIMITER ;

Al marcar un paciente como eliminado, sus citas pendientes se cancelan solas.

Gestionar triggers

-- Listar
SHOW TRIGGERS FROM clinica;

-- Ver la definición
SHOW CREATE TRIGGER tr_citas_auditoria;

-- Eliminar
DROP TRIGGER IF EXISTS tr_citas_auditoria;

Igual que con los procedimientos, para modificar uno hay que borrarlo y recrearlo.

Sobre nombres, una convención útil: tr_tabla_momento_evento, como tr_citas_antes_insertar. Cuando tengas quince triggers, saber qué hace cada uno por el nombre vale mucho.

Por qué hay que usarlos con cuidado

Y aquí viene la parte importante, porque los triggers tienen mala fama y en buena medida se la ganaron.

Son invisibles. Este es el problema de fondo. Alguien ejecuta un UPDATE sencillo y se disparan tres triggers que modifican otras cuatro tablas. Nada en la sentencia lo sugiere. Depurar un sistema así es frustrante: los efectos aparecen sin causa aparente.

Se ejecutan por cada fila. Un UPDATE que afecta a 100.000 filas ejecuta el trigger 100.000 veces. Si dentro hace un INSERT, son 100.000 inserciones extra. Operaciones masivas que deberían tardar segundos tardan horas.

Pueden encadenarse. Un trigger sobre citas actualiza pacientes, que tiene otro trigger que actualiza algo más. Seguir esa cadena es difícil, y un bucle infinito es posible.

No se versionan. Igual que los procedimientos, viven en la base y no en Git.

Complican las pruebas. Insertar un registro de prueba puede tener efectos colaterales que no esperabas.

Cuándo sí y cuándo no

Sí conviene:

  • Auditoría. Es el caso donde ninguna alternativa da la misma garantía.
  • Validaciones críticas que deben cumplirse pase lo que pase.
  • Normalización simple de datos, como pasar correos a minúsculas.

No conviene:

  • Lógica de negocio. Va en la aplicación, donde se lee, se prueba y se versiona.
  • Operaciones pesadas, como enviar notificaciones o llamar a servicios externos.
  • Cálculos complejos encadenados entre tablas.

La regla que aplico: un trigger debe ser corto, obvio y sin efectos colaterales sorprendentes. Si necesitas más de veinte líneas, probablemente eso pertenece a la aplicación.

Documéntalos

Como son invisibles, hay una práctica que ahorra muchos disgustos: mantener un archivo en el repositorio con todos los triggers de la base, aunque estén creados en el servidor.

Así quien llegue nuevo al proyecto puede leer qué pasa por debajo sin tener que descubrirlo depurando.

Errores comunes

  • Lógica de negocio metida en triggers.
  • Operaciones pesadas dentro de un FOR EACH ROW.
  • Triggers encadenados que nadie puede seguir.
  • No documentarlos, dejando el sistema lleno de comportamiento oculto.
  • Modificar NEW en un trigger AFTER, que no tiene efecto.
  • Olvidar que existen al hacer una carga masiva, y esperar un rendimiento que no llega.

Para cerrar

Los triggers son la herramienta correcta para una cosa muy concreta: garantizar que algo ocurra siempre, sin importar quién toque los datos. La auditoría es su caso ideal.

Para todo lo demás, la lógica en la aplicación es más fácil de leer, probar y mantener.

En la siguiente lección veremos las copias de seguridad.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp