Triggers en MySQL (disparadores)

Un trigger es un bloque de código SQL que se ejecuta automáticamente al ocurrir un evento INSERT, UPDATE o DELETE sobre una tabla. Son muy útiles para mantener la integridad de los datos, auditar cambios y automatizar reglas de negocio.

Disponibilidad histórica: Los triggers fueron introducidos en MySQL 5.0.2 (año 2005) y su sintaxis sigue el estándar ANSI SQL:2003.
Múltiples triggers por evento/momento: Desde MySQL 5.7.2 es técnicamente posible tener más de un trigger para la misma combinación (ej. dos triggers BEFORE UPDATE), pero esto es una mala práctica (ver sección específica).

¿Qué es un Trigger? Tipos y sintaxis

Un trigger se asocia a una tabla y se define para ejecutarse antes (BEFORE) o después (AFTER) de un evento INSERT, UPDATE o DELETE.

CREATE TRIGGER nombre_trigger
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON nombre_tabla
FOR EACH ROW
BEGIN
    -- instrucciones SQL
END;

FOR EACH ROW es obligatorio. Significa que el trigger se ejecutará una vez por cada fila afectada por la sentencia DML. No existe en MySQL el nivel FOR EACH STATEMENT (por sentencia).

¿Recorre todos los registros de la tabla? No. Solo las filas que realmente se ven afectadas por la operación (las que cumplan la condición WHERE, o todas si no hay filtro). El motor itera directamente sobre esas filas sin construir un record set temporal completo (como un cursor). Solo en situaciones complejas (el trigger lee o modifica la misma tabla de forma conflictiva) puede usar una tabla temporal interna.

Usos principales de los triggers (Auditoría, Validación, Automatización)

Desde los inicios, los triggers se han usado para tres propósitos fundamentales. A continuación se detallan con ejemplos conceptuales:

Estos usos se pueden combinar, pero es importante no mezclar lógica que pueda causar efectos secundarios no deseados. La sección "Automatización vs Auditoría" más abajo muestra dos triggers distintos sobre la misma tabla para que veas la diferencia.

Ejemplo clásico: automatización de precio (BEFORE UPDATE)

Tabla productos y trigger que actualiza el precio automáticamente cuando el costo cambia.

CREATE TABLE productos (
    id INT PRIMARY KEY,
    nombre VARCHAR(100),
    costo DECIMAL(10,2),
    precio DECIMAL(10,2),
    proveedor_id INT
);

DELIMITER $$
CREATE TRIGGER actualizarPrecioProducto
BEFORE UPDATE ON productos
FOR EACH ROW
BEGIN
    IF NEW.costo <> OLD.costo THEN
        SET NEW.precio = NEW.costo * 2;
    END IF;
END$$
DELIMITER ;

Visualización paso a paso (aclaración sobre el momento de ejecución)

Importante: Cuando ejecutamos UPDATE productos SET costo = costo * 1.10 WHERE proveedor_id = 1;, el motor:

  1. Calcula en memoria el nuevo valor de costo para cada fila afectada (ej: 50 → 55).
  2. Antes de escribir cualquier cambio en la tabla, ejecuta el trigger BEFORE UPDATE para esa fila. Dentro del trigger, OLD contiene los valores actuales (aún intactos en la tabla) y NEW contiene los valores calculados (aún no escritos).
  3. El trigger puede modificar NEW.precio.
  4. Una vez que el trigger termina, se escribe la fila completa con los valores finales de NEW.

Tabla de evolución paso a paso (fila por fila)

Partimos de los siguientes registros iniciales:

idnombrecostoprecioproveedor_id
1Teclado RGB50.00100.001
2Mouse inalámbrico30.0060.001
3Monitor 24"200.00400.002

Ejecutamos: UPDATE productos SET costo = costo * 1.10 WHERE proveedor_id = 1; (afecta id=1 y id=2).

Iteración 1 - Fila id=1 (Teclado RGB)
MomentoscostoprecioAcción
OLD (valor actual en tabla)50.00100.00
NEW calculado por la sentencia (antes del trigger)55.00 (50*1.10)100.00
Dentro del trigger: IF 55.00 <> 50.00 -> verdaderoSET NEW.precio = 55.00 * 2 = 110.00
NEW final (después del trigger, se escribe)55.00110.00
Iteración 2 - Fila id=2 (Mouse inalámbrico)
MomentoscostoprecioAcción
OLD (valor actual en tabla)30.0060.00
NEW calculado por la sentencia (antes del trigger)33.00 (30*1.10)60.00
Dentro del trigger: IF 33.00 <> 30.00 -> verdaderoSET NEW.precio = 33.00 * 2 = 66.00
NEW final (después del trigger, se escribe)33.0066.00

Tabla productos después del UPDATE (estado final):

idnombrecostoprecioproveedor_id
1Teclado RGB55.00110.001
2Mouse inalámbrico33.0066.001
3Monitor 24"200.00400.002
Nota: El precio del mouse pasó de 60.00 a 66.00 (corregido, no 606.00).

NEW y OLD en profundidad

En INSERT, OLD no existe. En DELETE, NEW no existe.

Casos donde no necesitamos usar NEW u OLD (ejemplos del PDF original)

Existen situaciones donde el trigger no requiere acceder a los valores específicos de la fila, sino solo a la operación en sí.

Ejemplo 1: Trigger AFTER INSERT sin usar NEW (solo auditoría de operación)

CREATE TRIGGER after_insert_log
AFTER INSERT ON tablita
FOR EACH ROW
BEGIN
    INSERT INTO log_auditoria (accion_ejecutada, nombre_tabla, fecha_hora)
    VALUES ('INSERT', 'tablita', NOW());
END;

No necesitamos NEW porque solo registramos que ocurrió una inserción, no los datos insertados.

Ejemplo 2: Trigger BEFORE DELETE sin usar OLD (limpieza genérica)

CREATE TRIGGER before_delete_limpieza
BEFORE DELETE ON pedidos
FOR EACH ROW
BEGIN
    DELETE FROM detalle_pedido WHERE id_pedido = OLD.id_pedido;
END;

Aquí sí se usa OLD, pero el PDF menciona que podría no necesitarse si la limpieza no depende del valor, por ejemplo eliminando registros más antiguos que una fecha fija.

Ejemplo 3: Trigger AFTER DELETE que borra registros en otra tabla

CREATE TRIGGER after_delete_cleanup
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
    DELETE FROM employee_history WHERE employee_id = OLD.employee_id;
END;

Igual que antes, se usa OLD para identificar la relación.

Disponibilidad de NEW y OLD según la operación

¿Son tablas o CTEs? No. NEW y OLD son referencias temporales a los datos de una fila específica durante la ejecución del trigger. No existen físicamente, no ocupan espacio, y no se pueden consultar fuera del trigger.

Qué SÍ y qué NO se puede hacer dentro de un trigger (tabla ampliada)

CategoríaCapacidad / Restricción¿Permitido?Notas / Versión
Objetos objetivoTablas regulares (InnoDB, MyISAM)Puede leer/modificar cualquier tabla, incluso de otras bases de datos (cross-database: `otra_bd.tabla`).
Tablas del sistema (`mysql`, `information_schema`)NONo se puede asociar un trigger a una tabla del sistema.
Tablas temporalesNONo se puede crear un trigger sobre una tabla temporal.
DML en otras tablasINSERT/UPDATE/DELETE en otras tablasVálido para auditoría o cascada.
Modificar la misma tabla que disparó el trigger (excepto vía NEW)NO`UPDATE productos SET ...` (sin usar NEW) da error: la tabla ya está en uso.
TransaccionesCOMMIT / ROLLBACK explícitosNOProhibido iniciar o finalizar transacciones dentro del trigger.
Ser alcanzado por ROLLBACK externoSi la sentencia DML falla (o se usa SIGNAL), los cambios del trigger se revierten.
Manejo de erroresDefinir HANDLERs (CONTINUE/EXIT)Siempre disponible (desde MySQL 5.0).
Usar SIGNAL / RESIGNALDesde MySQL 5.5.
Control de flujoIF, CASE, LOOP, WHILE, REPEAT, LEAVETodas las estructuras de stored procedures.
CursoresCursores explícitos (DECLARE CURSOR, FETCH)Se puede recorrer un conjunto de resultados.
FOR EACH STATEMENTNOMySQL solo soporta nivel fila.
LenguajeVariables locales (DECLARE)Ámbito dentro del bloque.
Variables de usuario (@var)Pueden comunicar valores después de la ejecución.
Prepared Statements (SQL dinámico)NOPREPARE, EXECUTE no permitidos.
Devolver resultados al cliente (SELECT)NONo puede retornar conjuntos de filas. Alternativa: usar variables de sesión o tablas de log.
ModularidadLlamar a Stored Procedures (CALL)El SP no debe devolver resultados al cliente ni usar instrucciones prohibidas.
DDLCREATE, ALTER, DROPNONo se permite modificar la estructura de objetos.
¿Cómo devuelve datos un trigger? No puede directamente. Un trigger no está diseñado para comunicarse con el cliente. Si necesita informar algo (por ejemplo, "se actualizaron 5 filas"), use variables de sesión (SET @resultado = '...';) que luego el cliente lea, o escriba en una tabla de logs. No intente usar SELECT para mostrar resultados porque generará un error.

Automatización vs Auditoría (dos triggers sobre la misma tabla)

Ejemplo con tabla pedidos y campo estado:

CREATE TABLE pedidos (
    id INT PRIMARY KEY,
    cliente VARCHAR(100),
    estado VARCHAR(20),
    fecha_entrega DATE
);
CREATE TABLE auditoria_pedidos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    pedido_id INT,
    estado_anterior VARCHAR(20),
    estado_nuevo VARCHAR(20),
    usuario VARCHAR(50),
    fecha_cambio TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Automatización (activo): BEFORE UPDATE que asigna fecha_entrega automáticamente.

CREATE TRIGGER auto_fecha_entrega
BEFORE UPDATE ON pedidos FOR EACH ROW
BEGIN
    IF NEW.estado = 'enviado' AND OLD.estado != 'enviado' THEN
        SET NEW.fecha_entrega = CURDATE();
    END IF;
END;

Auditoría (pasivo): AFTER UPDATE que registra el cambio en otra tabla.

CREATE TRIGGER audit_estado_pedido
AFTER UPDATE ON pedidos FOR EACH ROW
BEGIN
    INSERT INTO auditoria_pedidos (pedido_id, estado_anterior, estado_nuevo, usuario)
    VALUES (NEW.id, OLD.estado, NEW.estado, USER());
END;

Múltiples triggers para el mismo evento: mala práctica

Aunque MySQL permite múltiples triggers del mismo tipo desde 5.7.2, es una MALA PRÁCTICA.

Consideraciones de rendimiento y limitaciones adicionales

Administración y consulta de triggers

-- Ver todos los triggers de la BD actual
SHOW TRIGGERS;

-- Ver triggers de una BD específica
SHOW TRIGGERS FROM nombre_base_datos;

-- Ver triggers de una tabla específica
SHOW TRIGGERS FROM tienda WHERE `Table` = 'productos';

-- Consultar usando INFORMATION_SCHEMA (más potente)
SELECT TRIGGER_NAME, EVENT_MANIPULATION, EVENT_OBJECT_TABLE, ACTION_TIMING, ACTION_STATEMENT
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = 'nombre_base_datos';

-- Eliminar trigger
DROP TRIGGER IF EXISTS nombre_trigger;

El comando SHOW TRIGGERS devuelve columnas como: Trigger, Event, Table, Statement, Timing, Created, definer, etc.

Preguntas frecuentes (FAQ)

¿Puedo tener dos triggers BEFORE UPDATE en la misma tabla? Técnicamente sí desde 5.7.2, pero es mala práctica. Use uno solo.

¿Los triggers funcionan con INSERT ... ON DUPLICATE KEY UPDATE? Sí: si es INSERT, dispara triggers de INSERT; si es UPDATE, dispara triggers de UPDATE.

¿Se puede leer el valor autoincremental en BEFORE INSERT? No, NEW.id vale 0 o NULL. Usar AFTER INSERT.

¿Cómo hago para que un trigger revierta la operación sin error? No se puede directamente. Use SIGNAL para abortar con error.

¿Un trigger puede actualizar otra tabla y esa tabla a su vez tener un trigger? Sí, puede haber cascada de triggers.


Documento completo basado en MySQL 8.0. Incluye todos los conceptos del material original: definición, tipos BEFORE/AFTER, sintaxis, ejemplo, NEW/OLD, usos (auditoría, validación, automatización), ejemplos visuales paso a paso (con tablas de evolución), casos sin NEW/OLD, disponibilidad por operación, tabla de capacidades, limitaciones, administración y preguntas frecuentes. Corregido el error del precio del mouse (66.00).