触发器 TRIGGER
本教程共 46 篇 · 第 36 篇 · 更新于 2026-07-30 · 约 10 分钟阅读
36. 触发器 TRIGGER
本节目标:理解触发器的工作原理,掌握 BEFORE/AFTER 触发时机和 INSERT/UPDATE/DELETE 触发事件的组合,学会用 NEW 和 OLD 引用变更前后的数据,了解触发器的适用场景和坑。
36.1 触发器是什么
触发器(Trigger) 是一种特殊的存储过程,它不需要手动调用,而是在表上发生 INSERT、UPDATE 或 DELETE 操作时自动触发执行。
说白了,触发器就是给表装了个”监视器”—数据一变动,它就自动跑。
典型场景:
- 插入用户时自动记录日志;
- 删除订单前检查状态;
- 更新余额时自动写入审计表。
36.2 创建触发器
基本语法:
CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名
FOR EACH ROW
BEGIN
-- 触发器逻辑
END;
两个关键要素:
- 触发时机:BEFORE(操作前触发)或 AFTER(操作后触发);
- 触发事件:INSERT、UPDATE 或 DELETE。
三者组合,一共六种触发场景:
| 时机 | 事件 | 说明 |
|---|---|---|
| BEFORE | INSERT | 插入前触发,可修改待插入的值 |
| AFTER | INSERT | 插入后触发,可读取已插入的值 |
| BEFORE | UPDATE | 更新前触发,可修改待更新的值 |
| AFTER | UPDATE | 更新后触发,可读取更新前后的值 |
| BEFORE | DELETE | 删除前触发,可读取待删除的值 |
| AFTER | DELETE | 删除后触发,可读取已删除的值 |
NoteMySQL 的触发器是行级触发(
FOR EACH ROW),即每变更一行就触发一次。没有语句级触发。
36.3 NEW 和 OLD 关键字
触发器内用 NEW 和 OLD 引用变更前后的行数据:
| 事件 | OLD | NEW |
|---|---|---|
| INSERT | 不可用 | 插入的新值 |
| UPDATE | 更新前的旧值 | 更新后的新值 |
| DELETE | 删除前的旧值 | 不可用 |
实际例子:记录操作日志
-- 先建日志表
CREATE TABLE user_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
action VARCHAR(20),
log_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 创建触发器:用户插入后自动记录日志
DELIMITER //
CREATE TRIGGER trg_user_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
INSERT INTO user_logs(user_id, action) VALUES(NEW.id, 'INSERT');
END //
DELIMITER ;
插入一条用户数据,日志自动生成:
INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25);
SELECT * FROM user_logs;
-- 自动多了一条记录:user_id=1, action='INSERT'
BEFORE INSERT:修改待插入的值
BEFORE 触发器可以修改 NEW 的值,在数据真正写入前做调整:
DELIMITER //
CREATE TRIGGER trg_user_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
-- 自动把用户名转小写
SET NEW.username = LOWER(NEW.username);
END //
DELIMITER ;
INSERT INTO users(username, email) VALUES('ZHANGSAN', 'test@test.com');
SELECT username FROM users WHERE email = 'test@test.com';
-- 结果:zhangsan(自动变小写了)
TipBEFORE 触发器适合做数据校验和预处理。如果校验不通过,可以用
SIGNAL SQLSTATE抛出错误阻止操作。
用 SIGNAL 阻止非法操作
DELIMITER //
CREATE TRIGGER trg_check_age
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.age < 0 OR NEW.age > 150 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '年龄必须在 0 到 150 之间';
END IF;
END //
DELIMITER ;
INSERT INTO users(username, age) VALUES('test', 200);
-- ERROR 1644 (45000): 年龄必须在 0 到 150 之间
36.4 UPDATE 触发器:同时用 OLD 和 NEW
更新操作时,OLD 是旧值,NEW 是新值,可以对比变化:
DELIMITER //
CREATE TRIGGER trg_order_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
IF OLD.status != NEW.status THEN
INSERT INTO order_logs(order_id, old_status, new_status)
VALUES(OLD.id, OLD.status, NEW.status);
END IF;
END //
DELIMITER ;
只有状态真的变了才记日志,避免无意义的日志。
36.5 查看和删除触发器
-- 查看所有触发器
SHOW TRIGGERS;
-- 查看指定库的触发器
SHOW TRIGGERS WHERE `Trigger` = 'trg_user_insert';
-- 查看触发器定义
SHOW CREATE TRIGGER trg_user_insert\G
-- 查 information_schema
SELECT TRIGGER_NAME, EVENT_MANIPULATION, ACTION_TIMING, EVENT_OBJECT_TABLE
FROM information_schema.TRIGGERS
WHERE TRIGGER_SCHEMA = '你的库名';
-- 删除触发器
DROP TRIGGER IF EXISTS trg_user_insert;
Warning删除表时,表上的触发器会一起被删除。如果重建表,别忘了重建触发器。
36.6 触发器的局限性
触发器好用,但坑也不少:
1. 难以调试
触发器在后台自动执行,出了问题不容易发现。一个表上挂了多个触发器,排查链路很长。
2. 性能影响
每行变更都触发一次触发器逻辑。大批量 INSERT/UPDATE/DELETE 时,触发器会显著拖慢速度。
3. 隐式逻辑
开发者看到一条 INSERT INTO users(...) 语句,不知道背后还跑了一堆触发器逻辑。维护时容易踩坑。
4. 不能返回结果集
触发器内不能有 SELECT 返回结果给客户端(可以 SELECT INTO 变量)。
5. 同表同事件可有多个(需指定顺序)
MySQL 5.7.2+ 已支持同一张表、同一时机、同一事件创建多个触发器,但必须用 FOLLOWS 或 PRECEDES 指定触发顺序。例如:
CREATE TRIGGER trg1 BEFORE INSERT ON users FOR EACH ROW
BEGIN ... END;
CREATE TRIGGER trg2 BEFORE INSERT ON users FOR EACH ROW
FOLLOWS trg1 -- 指定在 trg1 之后触发
BEGIN ... END;
Note本教程基线为 MySQL 26.7.0,基于 8.0+ 内核,此限制早已不存在。如果你还在用 5.7.2 之前的版本,才会遇到”同表同事件只能一个”的限制。
Tip我之前踩过这个坑—线上有个表挂了 AFTER UPDATE 触发器写日志,某天批量更新 10 万条数据,触发器逐行执行,跑了 20 分钟。批量操作场景,慎用触发器,改用存储过程批量处理更可控。
36.7 触发器 vs 存储过程 vs 事件调度
| 特性 | 触发器 | 存储过程 | 事件调度器 |
|---|---|---|---|
| 触发方式 | 数据变更时自动 | 手动 CALL | 定时调度 |
| 参数 | 无(通过 NEW/OLD) | IN/OUT/INOUT | 无 |
| 能否返回结果集 | 不能 | 能 | 不能 |
| 典型场景 | 审计日志、数据校验 | 复杂业务逻辑 | 定时清理、统计 |
36.8 小结
- 触发器在表数据变更时自动执行,不需要手动调用;
- BEFORE 可修改待写入的值,AFTER 可读取已写入的值;
- NEW 引用新值(INSERT/UPDATE),OLD 引用旧值(UPDATE/DELETE);
SIGNAL SQLSTATE可在 BEFORE 触发器中阻止非法操作;- 触发器有调试难、性能差、逻辑隐式等局限,批量操作场景慎用。
下一节进入事务(Transaction),学习如何保证一组操作要么全成功、要么全撤销。