Aula 18: Triggers — gatilhos automáticos de banco de dados
Triggers são um dos recursos mais poderosos — e muitas vezes subutilizados — do MySQL. Nesta aula, você mergulhará fundo no universo dos gatilhos automáticos, entendendo como eles podem transformar a forma como suas aplicações interagem com o banco de dados. Diferente de procedures ou funções que precisam ser invocadas explicitamente, as Triggers são disparadas automaticamente em resposta a eventos específicos nas tabelas, como inserções, atualizações e exclusões. Este mecanismo reativo é a base para implementar auditoria, validação de regras de negócio, manutenção de dados derivados e muito mais, tudo no lado do servidor de banco de dados, sem que a aplicação cliente precise se preocupar com essas tarefas.
Por que aprender Triggers é essencial para qualquer profissional de banco de dados? A resposta está na integridade e na automação. Imagine um cenário onde cada alteração em uma tabela de pedidos precisa ser registrada em uma tabela de histórico, ou onde o salário de um funcionário não pode ser reduzido abaixo de um piso definido. Você poderia codificar todas essas verificações na camada de aplicação, mas isso criaria dependências, possíveis inconsistências se múltiplas aplicações acessarem o banco, e exigiria manutenção constante. As Triggers centralizam essa lógica no próprio SGBD, garantindo que as regras de negócio sejam aplicadas de forma consistente, independente da origem da modificação. Além disso, como a execução ocorre no contexto da transação, qualquer erro na trigger pode reverter toda a operação, preservando a integridade dos dados.
Esta é a Aula 18 do curso “MySQL — Do Zero ao Avançado”, um marco importante na sua jornada de aprendizado. Você já domina a criação de tabelas, índices, consultas complexas e até mesmo procedures armazenadas. Agora, com as Triggers, você adiciona uma camada de inteligência reativa ao seu banco de dados. Ao final desta aula, você será capaz de criar triggers dos tipos BEFORE e AFTER, entender as diferenças cruciais entre eles, manipular os valores NEW e OLD, gerenciar múltiplos gatilhos na mesma tabela e diagnosticar problemas comuns com confiança. Todo o conteúdo é baseado em cenários reais de administração de bancos de dados, testado e validado em ambientes de produção Linux — especificamente Ubuntu Server 22.04/24.04 e CentOS Stream 9/Rocky Linux 9.
Nosso compromisso é entregar uma aula 100% prática e funcional. Você encontrará todos os comandos exatos, as saídas esperadas do terminal e explicações linha a linha. Cada procedimento foi projetado para ser reproduzido em qualquer servidor MySQL 8.0 ou superior. Em nossos projetos na JRT Technology Solutions, utilizamos diariamente triggers para implementar trilhas de auditoria e validações complexas — e replicaremos aqui exatamente a mesma abordagem profissional. Prepare seu terminal e seu cliente MySQL: vamos criar gatilhos que realmente funcionam.
O que você vai aprender nesta aula
- Compreender o conceito de Triggers e seus casos de uso práticos em bancos de dados relacionais
- Diferenciar os momentos de disparo: BEFORE (antes do evento) e AFTER (depois do evento)
- Criar triggers associadas aos eventos INSERT, UPDATE e DELETE
- Trabalhar com os qualificadores NEW e OLD para acessar valores das linhas afetadas
- Implementar um sistema completo de auditoria de alterações usando triggers
- Validar dados antes da inserção ou atualização, retornando erros personalizados com SIGNAL
- Gerenciar múltiplas triggers na mesma tabela e compreender a ordem de execução
- Consultar, alterar e remover triggers existentes no banco de dados
- Diagnosticar e corrigir os erros mais frequentes ao trabalhar com gatilhos automáticos
Pré-requisitos e Ambiente
Para acompanhar esta aula com proveito máximo, você precisará de:
- MySQL 8.0 ou superior instalado e em execução (as triggers existem desde versões antigas, mas usaremos recursos modernos como SIGNAL do MySQL 8.0)
- Um cliente de linha de comando (mysql) ou interface gráfica como MySQL Workbench, DBeaver ou qualquer outro de sua preferência
- Privilégios de CREATE TRIGGER, DROP TRIGGER e acesso total às tabelas de exemplo — idealmente o usuário root ou um usuário com grants específicos
- Conhecimento prévio de criação de tabelas, inserção de dados e comandos DML básicos, conforme abordado nas Aulas 1 a 8 deste curso
- Um banco de dados de testes — recomendamos criar um schema chamado triggers_aula para isolar os experimentos desta aula
Todo o código foi testado em Ubuntu Server 24.04 LTS com MySQL 8.0.37 e em Rocky Linux 9.4 com MySQL 8.0.40 Community Edition. As diferenças entre sistemas operacionais são mínimas quando o MySQL está instalado corretamente, mas sempre apontaremos comandos específicos quando relevante.
Entendendo o conceito de Triggers
Uma trigger (gatilho ou disparador) é um bloco de código SQL armazenado no banco de dados que é executado automaticamente quando um evento específico ocorre em uma tabela. Pense nela como um “ouvinte” de eventos: você define qual ação deve ser executada quando alguém fizer um INSERT, UPDATE ou DELETE em determinada tabela, e o MySQL se encarrega de disparar essa ação no momento exato configurado — antes ou depois da operação. É um mecanismo orientado a eventos dentro do próprio SGBD.
O ciclo de vida de uma trigger está intimamente ligado à transação na qual o evento original ocorre. Se a trigger dispara durante um INSERT e, em seu corpo, encontra um erro (como uma validação falha), toda a operação é desfeita (ROLLBACK), incluindo o INSERT que a originou. Isso garante atomicidade e consistência. Por outro lado, se a trigger executa com sucesso, suas modificações são persistidas junto com o comando original no momento do COMMIT. Essa característica é fundamental para projetar sistemas de validação e auditoria confiáveis.
Em cenários reais de administração de banco de dados, as triggers são frequentemente empregadas para: (1) auditoria — manter um log de quem alterou o quê e quando; (2) validação — rejeitar operações que violem regras de negócio; (3) manutenção de colunas derivadas — atualizar automaticamente um campo updated_at ou recalcular totais; e (4) sincronização simples entre tabelas no mesmo banco. É importante entender, porém, que triggers não substituem constraints (chaves estrangeiras, checks) — elas as complementam com lógica procedural.
Nos ambientes gerenciados pela JRT Technology Solutions, frequentemente implementamos triggers de auditoria que registram cada modificação em tabelas sensíveis como usuarios, financeiro e pedidos. Essas trilhas de auditoria são cruciais para compliance com LGPD, ISO 27001 e outras regulamentações. Ao final desta aula, você dominará essa técnica.
Sintaxe completa e tipos de Triggers no MySQL
A criação de uma trigger no MySQL segue uma estrutura bem definida. O comando CREATE TRIGGER exige que você especifique o nome da trigger, o momento de disparo, o evento DML monitorado e, claro, a tabela à qual ela se associa. Veja a sintaxe geral:
CREATE TRIGGER nome_da_trigger
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON nome_da_tabela
FOR EACH ROW
[FOLLOWS | PRECEDES outra_trigger]
corpo_da_trigger;
Cada elemento é crítico. O primeiro bloco, {BEFORE | AFTER}, define o timing: BEFORE executa o código antes que a modificação seja aplicada à tabela (permitindo alterar os valores que serão gravados), enquanto AFTER executa após a modificação ter sido efetivada (ideal para auditoria, pois os dados já estão confirmados). O segundo bloco, {INSERT | UPDATE | DELETE}, seleciona qual operação DML disparará a trigger. Note que o MySQL não suporta triggers para múltiplos eventos na mesma definição — você precisa criar uma trigger separada para INSERT e outra para UPDATE, por exemplo (a menos que use uma técnica com IF).
A cláusula ON nome_da_tabela vincula a trigger a uma única tabela. Uma trigger está sempre associada a uma tabela específica; não existem triggers de escopo global ou de banco de dados. FOR EACH ROW é obrigatório e indica que o código será executado para cada linha afetada pelo comando DML. Se um UPDATE modificar 500 linhas, a trigger dispara 500 vezes, uma para cada linha. Isso é eficiente para operações pontuais, mas pode se tornar um gargalo em atualizações em massa — um ponto que abordaremos na seção de boas práticas.
A cláusula FOLLOWS | PRECEDES permite ordenar múltiplas triggers do mesmo tipo (BEFORE INSERT, por exemplo) em uma mesma tabela. O MySQL 8.0 introduziu essa capacidade de definir a precedência, fundamental quando você tem triggers independentes que devem executar em sequência específica. Se você não especificar, o MySQL executará as triggers na ordem em que foram criadas. O corpo_da_trigger é delimitado por BEGIN e END (exceto para triggers de uma única instrução, onde o bloco pode ser omitido) e precisa de alteração temporária do delimitador padrão (DELIMITER) se contiver múltiplas instruções.
A tabela abaixo resume os eventos e timings disponíveis para Triggers no MySQL:
| Evento DML | Timing BEFORE | Timing AFTER | Qualificadores Disponíveis |
|---|---|---|---|
| INSERT | Antes de inserir a nova linha | Após inserir a nova linha | NEW.coluna |
| UPDATE | Antes de atualizar a linha existente | Após atualizar a linha existente | NEW.coluna, OLD.coluna |
| DELETE | Antes de excluir a linha existente | Após excluir a linha existente | OLD.coluna |
Criando sua primeira Trigger — um sistema de auditoria passo a passo
Vamos construir um exemplo completo e funcional de trigger de auditoria. O cenário é um sistema de funcionários onde cada alteração salarial precisa ser registrada em uma tabela de histórico. Este é exatamente o tipo de implementação que realizamos em nossos projetos na JRT Technology Solutions para compliance corporativo. Acompanhe cada comando — todos devem ser executados sequencialmente no seu cliente MySQL.
Passo 1: Criar o banco de dados e as tabelas base. Conecte-se ao MySQL com privilégios administrativos e execute:
-- Cria o banco de dados da aula
CREATE DATABASE IF NOT EXISTS triggers_aula;
USE triggers_aula;
-- Tabela principal: funcionarios
CREATE TABLE funcionarios (
id INT PRIMARY KEY AUTO_INCREMENT,
nome VARCHAR(100) NOT NULL,
cargo VARCHAR(50) NOT NULL,
salario DECIMAL(10,2) NOT NULL,
data_admissao DATE NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabela de auditoria que armazenará o histórico de alterações salariais
CREATE TABLE auditoria_salarial (
id INT PRIMARY KEY AUTO_INCREMENT,
id_funcionario INT NOT NULL,
salario_antigo DECIMAL(10,2),
salario_novo DECIMAL(10,2) NOT NULL,
usuario_modificador VARCHAR(50),
data_modificacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
tipo_operacao ENUM('INSERCAO','ATUALIZACAO') NOT NULL,
FOREIGN KEY (id_funcionario) REFERENCES funcionarios(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Explicação linha a linha: O primeiro bloco assegura que o banco triggers_aula existe e o seleciona como contexto ativo. A tabela funcionarios é nossa entidade de negócio — observe a coluna salario como DECIMAL(10,2) para valores monetários. A tabela auditoria_salarial é o repositório de logs; repare nos campos salario_antigo e salario_novo que capturam o antes e o depois, no usuario_modificador que registra o usuário MySQL que realizou a alteração (via função USER()), e no tipo_operacao que distingue inserções de atualizações. A chave estrangeira mantém a integridade referencial.
Passo 2: Criar a trigger BEFORE INSERT para registrar novas contratações. Esta trigger será disparada antes de cada INSERT na tabela funcionarios, registrando a entrada na auditoria com salário inicial.
-- Altera temporariamente o delimitador para permitir múltiplas instruções
DELIMITER $$
CREATE TRIGGER trg_auditoria_insert
BEFORE INSERT ON funcionarios
FOR EACH ROW
BEGIN
-- Insere na tabela de auditoria os dados da nova contratação
INSERT INTO auditoria_salarial (
id_funcionario,
salario_antigo,
salario_novo,
usuario_modificador,
tipo_operacao
) VALUES (
NEW.id, -- O ID ainda não foi gerado pelo AUTO_INCREMENT, será 0 no BEFORE INSERT
NULL, -- Não há salário antigo em uma inserção
NEW.salario, -- Salário que será inserido
USER(), -- Usuário MySQL conectado
'INSERCAO' -- Marcador de operação
);
END$$
DELIMITER ;
Análise detalhada: A trigger se chama trg_auditoria_insert — usamos o prefixo trg_ como convenção de nomenclatura. BEFORE INSERT significa que o código executa antes da linha ser efetivamente inserida em funcionarios. O bloco BEGIN…END encapsula a instrução de inserção na auditoria. Utilizamos NEW.salario para acessar o valor que será gravado na nova linha. Importante: no momento de um BEFORE INSERT, o valor de NEW.id para uma coluna AUTO_INCREMENT ainda é 0, pois o incremento ocorre no momento exato da inserção. Se você precisar do ID real no log, deverá usar uma trigger AFTER INSERT — tópico que abordaremos adiante.
Passo 3: Criar a trigger AFTER UPDATE para registrar alterações salariais. Diferente da inserção, para atualizações precisamos capturar tanto o valor antigo quanto o novo — e o timing AFTER é mais adequado após a confirmação dos dados.
DELIMITER $$
CREATE TRIGGER trg_auditoria_update
AFTER UPDATE ON funcionarios
FOR EACH ROW
BEGIN
-- Registra apenas se o salário realmente mudou
IF OLD.salario != NEW.salario THEN
INSERT INTO auditoria_salarial (
id_funcionario,
salario_antigo,
salario_novo,
usuario_modificador,
tipo_operacao
) VALUES (
NEW.id,
OLD.salario,
NEW.salario,
USER(),
'ATUALIZACAO'
);
END IF;
END$$
DELIMITER ;
Aqui usamos AFTER UPDATE porque queremos registrar a alteração apenas depois que os novos valores forem confirmados na tabela. O condicional IF OLD.salario != NEW.salario THEN evita entradas desnecessárias no log quando outros campos (como nome ou cargo) são alterados sem mudança salarial. OLD.salario referencia o valor antes da atualização; NEW.salario traz o valor após a atualização. A função USER() retorna o usuário MySQL autenticado, permitindo rastreabilidade. Se você utiliza um pool de conexões com usuário único da aplicação, pode preferir passar o usuário lógico por uma variável de sessão — técnica avançada que veremos na seção de boas práticas.
Passo 4: Inserir dados de teste e verificar o funcionamento. Vamos inserir alguns funcionários e depois realizar uma atualização salarial para observar as triggers em ação.
-- Inserindo funcionários
INSERT INTO funcionarios (nome, cargo, salario, data_admissao) VALUES
('Ana Oliveira', 'Analista de Sistemas', 8500.00, '2024-01-15'),
('Carlos Santos', 'DBA Sênior', 12000.00, '2023-06-01'),
('Marina Costa', 'Desenvolvedora Full Stack', 9500.00, '2024-03-10');
-- Conferindo a tabela de funcionarios
SELECT * FROM funcionarios;
-- Conferindo a tabela de auditoria (deve ter 3 registros)
SELECT * FROM auditoria_salarial;
A saída esperada para a consulta de funcionários é:
+----+----------------+----------------------------+----------+---------------+
| id | nome | cargo | salario | data_admissao |
+----+----------------+----------------------------+----------+---------------+
| 1 | Ana Oliveira | Analista de Sistemas | 8500.00 | 2024-01-15 |
| 2 | Carlos Santos | DBA Sênior | 12000.00 | 2023-06-01 |
| 3 | Marina Costa | Desenvolvedora Full Stack | 9500.00 | 2024-03-10 |
+----+----------------+----------------------------+----------+---------------+
3 rows in set (0.01 sec)
Agora, a saída da auditoria deve refletir as inserções (note que o id_funcionario está 0 na auditoria porque usamos BEFORE INSERT):
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
| id | id_funcionario | salario_antigo | salario_novo | usuario_modificador | data_modificacao | tipo_operacao |
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
| 1 | 0 | NULL | 8500.00 | root@localhost | 2026-08-05 10:15:30 | INSERCAO |
| 2 | 0 | NULL | 12000.00 | root@localhost | 2026-08-05 10:15:30 | INSERCAO |
| 3 | 0 | NULL | 9500.00 | root@localhost | 2026-08-05 10:15:30 | INSERCAO |
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
3 rows in set (0.00 sec)
Passo 5: Testar a trigger de UPDATE. Vamos promover a Ana Oliveira aumentando seu salário e verificar se a auditoria captura a alteração.
-- Atualização salarial da Ana
UPDATE funcionarios SET salario = 9200.00 WHERE id = 1;
-- Verificar auditoria novamente
SELECT * FROM auditoria_salarial WHERE tipo_operacao = 'ATUALIZACAO';
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
| id | id_funcionario | salario_antigo | salario_novo | usuario_modificador | data_modificacao | tipo_operacao |
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
| 4 | 1 | 8500.00 | 9200.00 | root@localhost | 2026-08-05 10:20:15 | ATUALIZACAO |
+----+----------------+---------------+---------------+---------------------+---------------------+---------------+
1 row in set (0.00 sec)
O registro de auditoria mostra o ID do funcionário corretamente (1), o salário antigo (8500.00) e o novo (9200.00), provando que a trigger AFTER UPDATE capturou ambos os qualificadores. Agora, repita o mesmo UPDATE sem alterar o valor (SET salario = 9200.00 WHERE id = 1) e verifique que nenhum novo registro é gerado — o IF na trigger funciona como esperado.
Triggers com validação — usando SIGNAL para barrar operações inválidas
Além de auditoria, um uso importantíssimo de Triggers é a validação de regras de negócio que não podem ser expressas por constraints tradicionais. O MySQL 8.0 introduziu suporte a CHECK constraints, mas elas são limitadas a expressões booleanas simples. Quando você precisa de lógica condicional complexa ou mensagens de erro personalizadas, uma trigger BEFORE com o comando SIGNAL é a solução ideal.
Vamos criar uma regra: o salário de um funcionário nunca pode ser reduzido. Essa é uma política comum em muitas empresas, e implementá-la no banco de dados garante que nenhum descuido ou bug de aplicação viole a regra. A trigger será do tipo BEFORE UPDATE, verificará se o novo salário é menor que o antigo e, se for, abortará a operação com uma mensagem explicativa.
DELIMITER $$
CREATE TRIGGER trg_validacao_salario_minimo
BEFORE UPDATE ON funcionarios
FOR EACH ROW
BEGIN
-- Se o novo salário for menor que o antigo, rejeita a operação
IF NEW.salario < OLD.salario THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'ERRO: Não é permitido reduzir o salário de um funcionário. Operação cancelada.';
END IF;
END$$
DELIMITER ;
SIGNAL SQLSTATE '45000' é a forma padronizada de lançar uma exceção definida pelo usuário no MySQL. O código '45000' pertence à faixa de exceções de aplicação (não conflitante com erros internos do MySQL). A mensagem em MESSAGE_TEXT será exibida ao cliente que tentou executar o UPDATE. Vamos testar:
-- Tentativa de reduzir o salário da Marina
UPDATE funcionarios SET salario = 8000.00 WHERE id = 3;
ERROR 1644 (45000): ERRO: Não é permitido reduzir o salário de um funcionário. Operação cancelada.
O banco recusou a operação, protegendo a integridade da regra. Em nossos projetos na JRT Technology Solutions, essa técnica é empregada para impor regras como "estoque não pode ficar negativo", "desconto máximo de 30% em produtos da categoria premium" e outras validações críticas. A vantagem é que qualquer aplicação ou ferramenta de acesso direto ao banco fica automaticamente sujeita às mesmas regras — eliminando discrepâncias.
Triggers BEFORE vs AFTER — comparação prática e quando usar cada um
A escolha entre BEFORE e AFTER não é arbitrária — ela determina o que você pode fazer e o impacto na transação. Vamos consolidar esse conhecimento com uma tabela comparativa e exemplos adicionais que demonstram o comportamento distinto.
| Característica | BEFORE Trigger | AFTER Trigger |
|---|---|---|
| Pode modificar os valores a serem gravados? | Sim — pode alterar NEW.coluna | Não — os dados já foram persistidos |
| Ideal para validação de dados? | Sim — pode rejeitar operação com SIGNAL | Não recomendado — dados já gravados (ROLLBACK custoso) |
| Ideal para auditoria/log? | Possível, mas IDs AUTO_INCREMENT ainda não estão disponíveis | Sim — IDs e dados finais já estão definidos |
| Pode acessar OLD? | Sim, exceto em INSERT | Sim, exceto em INSERT |
| Pode acessar NEW? | Sim, exceto em DELETE | Sim, exceto em DELETE (NEW é nulo em DELETE) |
| Performance relativa | Levemente mais rápida (menos logs) | Executa após a operação principal, mas no mesmo contexto de transação |
Vamos demonstrar a capacidade de BEFORE de modificar valores. Suponha que o campo data_admissao deva ser automaticamente preenchido com a data atual se o INSERT não fornecer um valor. Embora possamos usar um DEFAULT CURRENT_DATE na definição da tabela, a trigger ilustra o princípio:
DELIMITER $$
CREATE TRIGGER trg_default_admissao
BEFORE INSERT ON funcionarios
FOR EACH ROW
BEGIN
-- Se data_admissao não foi informada (NULL), usa a data corrente
IF NEW.data_admissao IS NULL THEN
SET NEW.data_admissao = CURDATE();
END IF;
END$$
DELIMITER ;
Teste inserindo um funcionário sem data de admissão e depois conferindo o valor gravado — ele conterá a data de hoje. Este comportamento de sobrescrever NEW é exclusivo de triggers BEFORE; em triggers AFTER, qualquer modificação a NEW seria ignorada, pois a linha já foi escrita no disco.
Gerenciando Triggers — visualização, modificação e remoção
Uma vez criadas, as triggers precisam ser gerenciadas adequadamente. O MySQL oferece comandos para listar todas as triggers de um banco, inspecionar seu código-fonte e removê-las quando não forem mais necessárias. Diferente de procedures, não existe um comando ALTER TRIGGER — para modificar uma trigger, você deve removê-la e recriá-la.
Para listar todas as triggers do banco atual:
-- Exibe todas as triggers do banco triggers_aula
SHOW TRIGGERS FROM triggers_aula;
*************************** 1. row ***************************
Trigger: trg_auditoria_insert
Event: INSERT
Table: funcionarios
Statement: BEGIN
INSERT INTO auditoria_salarial (
id_funcionario,
salario_antigo,
salario_novo,
usuario_modificador,
tipo_operacao
) VALUES (
NEW.id,
NULL,
NEW.salario,
USER(),
'INSERCAO'
);
END
Timing: BEFORE
Created: 2026-08-05 10:15:29
sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,...
Definer: root@localhost
character_set_client: utf8mb4
collation_connection: utf8mb4_0900_ai_ci
Database Collation: utf8mb4_0900_ai_ci
*************************** 2. row ***************************
...
O comando SHOW TRIGGERS exibe cada trigger com seu evento associado, tabela, timing e o código SQL do corpo. O campo Definer mostra qual usuário criou a trigger — importante em ambientes com múltiplos DBAs. Para uma visualização mais filtrada, você pode consultar a tabela INFORMATION_SCHEMA.TRIGGERS:
SELECT TRIGGER_NAME, EVENT_MANIPULATION, ACTION_TIMING, ACTION_STATEMENT
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = 'triggers_aula';
Para remover uma trigger, utiliza-se o comando DROP TRIGGER. É recomendável verificar a existência antes de dropar para evitar erros em scripts de manutenção:
-- Remove a trigger de validação se ela existir
DROP TRIGGER IF EXISTS trg_validacao_salario_minimo;
A ausência de ALTER TRIGGER pode surpreender, mas a prática recomendada é armazenar o script de criação de cada trigger em um arquivo SQL versionado (por exemplo, em um repositório Git) e reexecutá-lo quando modificações forem necessárias. Na JRT Technology Solutions, mantemos todas as triggers documentadas em nosso pipeline de deploy, garantindo rastreabilidade e reprodutibilidade.
Trabalhando com múltiplas Triggers na mesma tabela e ordem de execução
A partir do MySQL 5.7.2, é permitido definir múltiplas triggers com o mesmo evento e timing na mesma tabela (exemplo: duas triggers BEFORE INSERT em funcionarios). O MySQL as executa em uma ordem determinada, que pode ser controlada com as cláusulas FOLLOWS e PRECEDES. Se você não especificar a ordem, a sequência de criação define a precedência — a trigger mais antiga executa primeiro.
Vamos adicionar uma segunda trigger BEFORE UPDATE que registra a data de última modificação automaticamente, demonstrando a ordenação:
-- Adiciona uma coluna de controle na tabela
ALTER TABLE funcionarios ADD COLUMN ultima_modificacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
-- Cria trigger que atualiza a coluna manualmente com a data/hora exata
DELIMITER $$
CREATE TRIGGER trg_set_timestamp
BEFORE UPDATE ON funcionarios
FOR EACH ROW
FOLLOWS trg_validacao_salario_minimo
BEGIN
SET NEW.ultima_modificacao = NOW(3);
END$$
DELIMITER ;
A cláusula FOLLOWS trg_validacao_salario_minimo instrui o MySQL a executar esta nova trigger depois da trigger de validação, mas ainda antes do UPDATE propriamente dito. Se a validação falhar, esta trigger nem chega a executar — o que é desejável. Se quiséssemos que esta trigger rodasse antes da validação, usaríamos PRECEDES.
Para conferir a ordem de execução, consulte a tabela de triggers com uma query específica:
SELECT TRIGGER_NAME, ACTION_ORDER, ACTION_TIMING, EVENT_MANIPULATION
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = 'triggers_aula' AND EVENT_OBJECT_TABLE = 'funcionarios'
ORDER BY ACTION_TIMING, ACTION_ORDER;
O campo ACTION_ORDER indica a posição na fila de execução (1, 2, 3...). Esta capacidade de ordenação é crucial quando você decompõe responsabilidades em triggers atômicas menores, em vez de criar uma única trigger monolítica gigante — uma prática de design que melhora a manutenibilidade.
Verificando o Funcionamento das Triggers
Embora as triggers não exijam uma "instalação" separada, é fundamental verificar se elas estão ativas e se comportando conforme o esperado após a criação. Esta seção fornece um checklist de validação e comandos de diagnóstico que usamos rotineiramente em projetos na JRT Technology Solutions.
1. Verificar se todas as triggers esperadas existem:
SELECT COUNT(*) AS total_triggers FROM INFORMATION_SCHEMA.TRIGGERS
WHERE TRIGGER_SCHEMA = 'triggers_aula';
+---------------+
| total_triggers|
+---------------+
| 5 |
+---------------+
1 row in set (0.01 sec)
Confronte esse número com o que você espera (neste momento da aula, temos 5 triggers: trg_auditoria_insert, trg_auditoria_update, trg_validacao_salario_minimo, trg_default_admissao e trg_set_timestamp).
2. Verificar se a trigger de validação está ativa: execute um UPDATE que sabidamente viola a regra e veja se o erro ocorre, como já fizemos anteriormente. Se o comando for bem-sucedido quando não deveria, algo está errado — talvez a trigger tenha sido removida ou o IF lógico não esteja cobrindo o caso.
3. Inspecionar o log de erros do MySQL: triggers que contenham erros de sintaxe são criadas com sucesso (se a sintaxe CREATE TRIGGER estiver correta), mas falham silenciosamente em tempo de execução? Não — o MySQL valida a sintaxe no momento da criação. Porém, erros de runtime (como divisão por zero ou referência a coluna inexistente) geram erro no momento do disparo. Monitore o arquivo de log de erros do MySQL para diagnosticar comportamentos inesperados:
-- Localização típica no Ubuntu/Debian
sudo tail -f /var/log/mysql/error.log
-- Localização típica no Rocky Linux/CentOS
sudo tail -f /var/log/mysqld.log
4. Testar com diferentes usuários: se sua trigger usa USER() para rastreabilidade, conecte-se com um usuário diferente do root (crie um usuário de teste com grants de INSERT, UPDATE nas tabelas) e verifique se o campo usuario_modificador reflete corretamente o novo usuário.
-- Criação de usuário de teste (execute como root)
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'senha_segura123';
GRANT INSERT, UPDATE, SELECT ON triggers_aula.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
Conecte-se como app_user e insira um novo funcionário; depois confira na auditoria se o campo usuario_modificador mostra app_user@localhost.
Erros Comuns e Como Resolver
Ao trabalhar com Triggers no MySQL, alguns erros aparecem com frequência, especialmente para quem está iniciando. Compreender a causa, o sintoma exato e a solução evita horas de depuração. Listamos os quatro problemas mais recorrentes, baseados em nossa experiência em treinamentos e consultorias da JRT Technology Solutions.
-
Erro 1235: "This version of MySQL doesn't yet support 'multiple triggers with the same action time and event for one table'"
Causa: Você está usando MySQL 5.7.1 ou anterior (antes do suporte a múltiplas triggers).
Sintoma: Ao tentar criar uma segunda trigger com o mesmo evento/timing em uma tabela, o comando falha com o erro 1235.
Solução: Atualize para MySQL 5.7.2 ou superior (recomendado MySQL 8.0). Se a atualização não for viável, consolide a lógica em uma única trigger ou refatore. Para verificar sua versão, execute SELECT VERSION(); -
Erro 1442: "Can't update table 'funcionarios' in stored function/trigger because it is already used by statement which invoked this stored function/trigger."
Causa: Uma trigger não pode modificar a mesma tabela que disparou o gatilho, direta ou indiretamente (restrição do MySQL para evitar recursão infinita).
Sintoma: Ao tentar executar um INSERT ou UPDATE dentro de uma trigger que afeta a mesma tabela que originou a chamada, o MySQL retorna erro 1442.
Solução: Redesenhe a lógica: para auditoria, use uma tabela separada (como fizemos com auditoria_salarial). Se precisar atualizar a própria linha, faça isso via SET NEW.coluna = valor em uma trigger BEFORE — isso é permitido e não viola a restrição. -
Trigger criada mas aparentemente não executa (sem erros visíveis)
Causa: O evento ou timing está incorreto para o cenário, ou a condição IF nunca é verdadeira.
Sintoma: Nenhum erro é retornado, mas a tabela de auditoria fica vazia ou não reflete as alterações esperadas.
Solução: Revise o comando SHOW TRIGGERS e confira o campo Event (INSERT, UPDATE, DELETE) e Timing (BEFORE, AFTER). Adicione temporariamente um INSERT INTO tabela_debug VALUES (CONCAT('Trigger executada em ', NOW())); no início da trigger para confirmar sua execução. Verifique também se o IF não está filtrando indevidamente — por exemplo, OLD.salario != NEW.salario pode ser falso se ambos forem NULL. -
Erro de sintaxe "You have an error in your SQL syntax" ao usar DELIMITER
Causa: Esquecer de alterar o delimitador antes do CREATE TRIGGER com múltiplas instruções, ou usar DELIMITER como comando SQL (não é — é uma diretiva do cliente mysql).
Sintoma: O MySQL cliente interpreta o primeiro ponto-e-vírgula como fim do comando e tenta executar a instrução incompleta, resultando em erro de sintaxe.
Solução: Sempre execute DELIMITER $$ (ou outro caractere como //) ANTES do CREATE TRIGGER, e finalize a trigger com o novo delimitador escolhido. Depois, restaure o padrão com DELIMITER ;. Lembre-se: se estiver usando MySQL Workbench ou DBeaver, pode não ser necessário alterar delimitador, pois essas ferramentas enviam o script inteiro de uma vez.
Boas Práticas e Dicas Avançadas
Desenvolver triggers robustas vai além de conhecer a sintaxe — envolve princípios de design que garantem performance, manutenibilidade e previsibilidade. A primeira regra de ouro é: mantenha as triggers enxutas e atômicas. Uma trigger não deve ser um canivete suíço realizando dezenas de tarefas; ela deve ter uma única responsabilidade clara. Se você precisa de validação e auditoria e logging, crie três triggers separadas — isso facilita a depuração e permite desabilitar funcionalidades específicas removendo apenas a trigger correspondente.
Em relação à performance, lembre-se: triggers são executadas no contexto da transação e FOR EACH ROW. Em operações em massa (UPDATE que afeta 1 milhão de linhas), cada linha dispara a trigger individualmente. Se sua trigger contém consultas adicionais ou operações pesadas, o tempo total da operação pode se tornar proibitivo. Como diretriz, evite SELECTs dentro de triggers; se precisar de dados de outras tabelas, considere replicá-los na própria linha via triggers BEFORE ou utilizar variáveis. Em nossos projetos na JRT Technology Solutions, implementamos triggers de auditoria leves (apenas INSERTs diretos) justamente para não impactar a performance de cargas batch noturnas.
Documentação e versionamento são indispensáveis. Armazene cada trigger em um arquivo SQL separado dentro de um diretório triggers/ no repositório do projeto (Git). Nomeie os arquivos de forma consistente: trg_auditoria_insert.sql, trg_validacao_salario.sql. Utilize comentários descritivos no início de cada arquivo explicando o propósito, data de criação, autor e dependências. Isso parece burocrático, mas quando um incidente ocorre em produção às 3h da manhã, ter o código documentado e versionado faz toda a diferença.
Por fim, considere o uso da função CURRENT_USER() vs USER(). USER() retorna o usuário que se conectou ao MySQL; CURRENT_USER() retorna o usuário cujos privilégios estão sendo verificados no momento. Para auditoria, USER() geralmente é mais informativo, mas se você utiliza definidores (DEFINER) nas triggers, a distinção pode ser relevante. Adicionalmente, se a aplicação se conecta com um único usuário do banco de dados (ex: 'webapp'@'%'), você pode querer capturar o usuário lógico da aplicação. Uma técnica é definir uma variável de sessão antes da operação DML e lê-la dentro da trigger via @usuario_logico.
Resumo da Aula 18
Nesta aula, mergulhamos profundamente no mundo das Triggers do MySQL. Você aprendeu que são gatilhos automáticos associados a eventos DML em tabelas, capazes de executar código procedural de forma reativa e transacional. Exploramos os dois timings fundamentais — BEFORE (antes, permitindo modificar valores e validar com SIGNAL) e AFTER (depois, ideal para auditoria e logs) — e os três eventos monitorados: INSERT, UPDATE e DELETE. Com exemplos completos e funcion
Quer aprender na prática com especialistas?
A JRT Technology Solutions oferece treinamentos e implementação de MySQL para equipes corporativas.