VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
Creating a Trigger
BEFORE vs AFTER
| BEFORE trigger | AFTER trigger | |
|---|---|---|
| Runs | Before the row is written | After the row is written |
Can modify NEW values | Yes — changes apply to the row | No — row already written |
Can see OLD values | Yes (UPDATE/DELETE) | Yes (UPDATE/DELETE) |
| Use for | Validation, defaulting values, data normalization | Audit logging, cascading to other tables, notifications |
BEFORE INSERT trigger can set default values or reject bad data. An AFTER INSERT trigger can write audit records after the insert succeeds.
NEW and OLD References
| Trigger type | NEW | OLD |
|---|---|---|
INSERT | New row values | Not available |
UPDATE | New values after update | Original values before update |
DELETE | Not available | Values of the deleted row |
NEW values in a BEFORE trigger to change what gets written:
Multiple Triggers Per Event
MySQL 5.7+ supports multiple triggers on the same table and event. TheFOLLOWS and PRECEDES keywords control execution order:
FOLLOWS/PRECEDES, MySQL assigns execution order based on creation time.
Raising Errors in Triggers
UseSIGNAL to abort the operation and return an error to the caller:
BEFORE trigger signals an error, the insert/update/delete is aborted — nothing is written.
Trigger Limitations
MySQL triggers have several restrictions worth knowing:- Triggers cannot call stored procedures that return result sets
- Triggers cannot use
COMMIT,ROLLBACK, orSAVEPOINTdirectly (they participate in the surrounding transaction) - Triggers cannot reference the table they’re defined on (no recursive trigger calls via direct queries on the same table in most contexts)
AFTERtriggers cannot modifyNEWvalues — the row is already written- MySQL does not support statement-level triggers (every trigger is
FOR EACH ROW)
When to Use Triggers
Triggers work well for:- Audit logging — capturing who changed what and when, regardless of application
- Enforcing complex constraints that CHECK constraints can’t express
- Keeping derived data in sync (denormalized summary columns, audit timestamps)
- The logic is complex enough that debugging it requires tracing hidden side effects
- Performance is critical — every trigger fires per row, on every affected statement
- The trigger modifies data in a way that surprises application developers unaware of its existence
Frequently Asked Questions
Do triggers fire during LOAD DATA INFILE?
Yes.INSERT triggers fire for each row loaded by LOAD DATA INFILE. If your trigger does significant work, bulk-loading through LOAD DATA will be much slower than loading into a staging table first, then copying with INSERT…SELECT (which also fires triggers, but you can temporarily disable them with SET @disable_trigger = TRUE and a conditional check inside the trigger).
Can triggers call stored procedures?
Yes, with one exception: the called procedure cannot return a result set (aSELECT without INTO). If it does, MySQL returns an error. Procedures called from triggers can use OUT parameters and SELECT...INTO.
Do triggers fire on REPLACE INTO?
REPLACE executes as a DELETE + INSERT when there’s a conflict. On conflict: BEFORE DELETE, AFTER DELETE, BEFORE INSERT, and AFTER INSERT triggers all fire on the target table. ON DELETE CASCADE rules on related tables do NOT fire from a REPLACE.
Troubleshooting
| Problem | Solution |
|---|---|
| Trigger not firing | Confirm the trigger exists: SHOW TRIGGERS LIKE 'table_name' — also verify the event (INSERT/UPDATE/DELETE) and timing (BEFORE/AFTER) match what you expect |
ERROR 1442: Can't update table in stored function/trigger | A trigger can’t directly INSERT/UPDATE/DELETE the same table it’s defined on in most contexts — write to a different table or use a stored procedure called after the DML |
| AFTER trigger changes not persisting | AFTER triggers cannot modify NEW — switch to BEFORE if you need to alter the row being written |
| Trigger slowing down bulk insert | Triggers fire per row — for bulk loads, consider disabling the trigger temporarily or loading into a staging table |
SIGNAL in trigger not surfacing to app | Some drivers swallow errors from triggers — check SHOW WARNINGS and verify your driver’s error handling configuration |

