Triggers in MySQL: Definition and How to Use Them
Triggers in MySQL are automated rules that run SQL to keep your data consistent when you insert, update, or delete data in a table. Constraints catch the simple checks, but triggers handle more difficult validation, such as syncing a sales_totals table or automatically recording every user role change to an audit_log table.
Generally, you'll want your main business logic in the app layer, but using triggers lets you guarantee these rules are always followed right when the database changes.
Let's break down what MySQL triggers are, their types, practical uses, and how to use them with step-by-step examples.
What is a trigger in MySQL?
A MySQL trigger is a database object that activates whenever an INSERT, UPDATE, or DELETE event happens on a table. It fires before or after the operation you define, and only if you're using SQL queries, not APIs or tools that skip SQL entirely.
You define them using a CREATE TRIGGER statement:
DELIMITER $$
CREATE TRIGGER trigger_name
trigger_time trigger_event
ON table_name
FOR EACH ROW
BEGIN
-- trigger logic here
END;
$$
DELIMITER ;
This doesn't need to be called like a stored procedure. MySQL triggers react to changes automatically, row by row. This means that if you insert 1,000 rows in a batch, the trigger will run 1,000 times, which, coupled with executing its logic, can cause overhead. That's why they're generally used for things where constraints or app code alone won't do (like enforcing rules with subqueries, audit logging, and cross-table dependencies).
Types of triggers in MySQL
Triggers in MySQL are categorized based on when they activate (BEFORE or AFTER) and what they activate for (INSERT, UPDATE, or DELETE). That gives you six possible types of triggers.
BEFORE INSERT trigger
A BEFORE INSERT trigger runs before a new row is added. So, if you want to ensure product codes are uppercase before saving to a products table, you'd write:
DELIMITER $$
CREATE TRIGGER before_product_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
SET NEW.code = UPPER(NEW.code);
END;
$$
DELIMITER ;
AFTER INSERT trigger
This trigger activates right after a row gets inserted. Suppose you have an orders table and want to maintain a running total of all orders in another table. You'd do the following:
DELIMITER $$
CREATE TRIGGER update_order_total_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
UPDATE order_summary SET total_amount = total_amount + NEW.amount;
END;
$$
DELIMITER ;
BEFORE UPDATE trigger
This one is similar to BEFORE INSERT, used to prevent invalid data or apply rules before a row is updated. For instance, to stop anyone from setting an employee's hire date to the past in payroll, you'd run:
DELIMITER $$
CREATE TRIGGER before_employee_update
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
SET NEW.hire_date = OLD.hire_date;
END;
$$
DELIMITER ;
AFTER UPDATE trigger
Use an AFTER UPDATE trigger for logging changes, syncing related tables, or auditing data after a row is modified. Below you have an example to track price changes in a products table:
DELIMITER $$
CREATE TRIGGER after_price_update
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
IF OLD.price != NEW.price THEN
INSERT INTO price_log (product_id, old_price, new_price, updated_at)
VALUES (NEW.id, OLD.price, NEW.price, NOW());
END IF;
END;
$$
DELIMITER ;
BEFORE DELETE trigger
A BEFORE DELETE trigger runs before a row is removed — great for archiving data or preventing unwanted deletions.
DELIMITER $$
CREATE TRIGGER before_customer_delete
BEFORE DELETE ON customers
FOR EACH ROW
BEGIN
INSERT INTO archived_customers (id, name, email, deleted_at)
VALUES (OLD.id, OLD.name, OLD.email, NOW());
END;
$$
DELIMITER ;
Here, we're saving customer data before it's deleted, copying their details into an archives table.
AFTER DELETE trigger
An AFTER DELETE trigger runs after a row's gone, which is useful for audit logs, syncing other tables, or triggering alerts. For example, you can update an inventory count after removing a product from the products table using:
DELIMITER $$
CREATE TRIGGER after_product_delete
AFTER DELETE ON products
FOR EACH ROW
BEGIN
UPDATE inventory SET stock = stock - 1 WHERE product_id = OLD.id;
END;
$$
DELIMITER ;
How to create a trigger in MySQL
As we've seen above, there are a few things we need to define to create a trigger with CREATE trigger:
- trigger_name: Unique, like before_sales_insert
- trigger_time: BEFORE or AFTER
- trigger_event: INSERT, UPDATE, or DELETE
- table_name: The target table
- body: The SQL logic between BEGIN and END, using NEW/OLD to access row data
NEW holds the new or updated data you can modify in INSERT or UPDATE triggers, while OLD only lets you look at the row's values before an UPDATE or DELETE.
Let's see this with another example. We'll create a trigger to cap discounts at 30% before inserting into a sales table.
- Ensure you have root privileges in MySQL.
- Create the table. In this case, we run:
CREATE TABLE sales (
sale_id INT PRIMARY KEY AUTO_INCREMENT,
discount DECIMAL(5,2)
);
- Define the trigger.
DELIMITER $$
CREATE TRIGGER before_sales_insert
BEFORE INSERT ON sales
FOR EACH ROW
BEGIN
IF NEW.discount > 30 THEN
SET NEW.discount = 30;
END IF;
END;
$$
DELIMITER ;
We use DELIMITER here because MySQL's default semicolon would confuse the parser inside the trigger's BEGIN...END block. If you want a simpler trigger with just one statement, you don't need it, but it's always a good practice.
Common errors and debugging triggers
There are a few tricky issues to watch out for when working with MySQL triggers, such as:
- Recursive updates: If you log changes in an orders table and the trigger tries updating the same table, MySQL will block it. Move the update to a stored procedure or a separate table to avoid this.
- Invalid OLD/NEW access: Always verify OLD for DELETE/UPDATE and NEW for INSERT/UPDATE via SHOW TRIGGERS.
- Slow triggers: They can slow down DML operations, so simplify your code and index referenced tables to speed things up a bit.
Practical MySQL trigger examples
Take a look below to see some other examples you can use as templates for your own projects.
Using triggers for audit logs
This is an AFTER UPDATE trigger that logs salary changes to an audit table anytime an employee record is updated:
DELIMITER $$
CREATE TRIGGER after_employee_update
AFTER UPDATE ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (employee_id, old_salary, new_salary, updated_by, updated_at)
VALUES (OLD.id, OLD.salary, NEW.salary, USER(), NOW());
END;
$$
DELIMITER ;
Automating timestamp updates with triggers
Here's a BEFORE UPDATE trigger on an employees table that sets a last_modified column to the current timestamp whenever a record is updated.
DELIMITER $$
CREATE TRIGGER update_last_modified
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
SET NEW.last_modified = NOW();
END;
$$
DELIMITER ;
Restricting invalid data with triggers
The following BEFORE INSERT trigger checks NEW.quantity in the inventory table and throws an error if invalid.
DELIMITER $$
CREATE TRIGGER before_inventory_insert
BEFORE INSERT ON inventory
FOR EACH ROW
BEGIN
IF NEW.quantity < 0 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Quantity cannot be negative';
END IF;
END;
$$
DELIMITER ;
Best practices for creating and using triggers
Triggers are powerful for automating tasks and enforcing rules, but piling on too many can hurt performance and complicate maintenance.
Optimizing trigger performance
Keep triggers simple to minimize overhead and make sure any tables they touch have good indexes.
Also, batching actions outside triggers, when you can, is way better for performance than doing things row by row and causing slowdowns.
Managing triggers in large databases
Managing triggers in large environments gets a lot easier with clear documentation and consistent naming. Here are other good habits:
- Use SHOW TRIGGERS and INFORMATION_SCHEMA.TRIGGERS to monitor triggers.
- When you have multiple triggers on the same event, control their order with FOLLOWS or PRECEDES.
- Stick to essential tasks in triggers and move more complex logic to your app or scheduled jobs.
- Always test triggers thoroughly to catch hidden bugs and avoid slowdowns.
Introducing dbForge Studio for MySQL
As we've seen, working with triggers in MySQL can be tough. The good news is, dbForge Studio for MySQL makes it easier with an intuitive MySQL management GUI that lets you define, handle, and debug triggers visually.
Using the MySQL Table Designer, you can quickly build or modify tables and triggers with just a few clicks. It comes with a bunch of tools to make your work less of a headache, such as auto-generated SQL scripts and real-time syntax checks. There's also a built-in MySQL debugger that helps you step through trigger logic and easily find errors.
When you're ready to deploy, use the Schema Compare and sync features to automatically generate and apply the necessary update scripts. And if performance ever becomes an issue, the Explain Plan tool will show you exactly how your triggers are running, making it easy to pinpoint and fix any slowdowns.
The best way to see how all this works to improve your workflow is to try it for yourself. Download the 30-day free trial today.
Conclusion
A trigger in MySQL helps automate INSERTs, UPDATEs, and DELETEs, especially if you need to reject invalid data automatically, enforcing audit trails, maintaining referential integrity, or syncing related tables. Use them sparingly, though, and always have a solid rollback plan and thorough testing before deploying to production.
Now, go ahead and implement your own triggers using the examples we've covered as a guide. Download dbForge Studio for MySQL free version to speed up the whole process. It'll simplify your workflow and make managing your database much easier.