4. 触发器:自动执行的特殊程序
触发器(Trigger)是绑定在表上的特殊存储程序,当表发生 INSERT/UPDATE/DELETE 时自动激活,无需手动调用。
四要素:
- ① 触发时机(BEFORE / AFTER)——决定触发器在事件发生之前还是发生之后执行:BEFORE 可提前拦截或修改数据,AFTER 适合事后记录。
- ② 触发事件(INSERT / UPDATE / DELETE)——指定由哪种数据操作激活触发器,三者选其一。
- ③ 触发对象(表)——触发器绑定在哪张表上(ON 表名),只能针对某一张具体的表。
- ④ 行级(FOR EACH ROW)——表示受影响的每一行都会执行一次触发逻辑,而不是整条语句只执行一次。
标准语法(注意 DELIMITER 改结束符):
DELIMITER //
CREATE TRIGGER 触发器名
BEFORE/AFTER INSERT/UPDATE/DELETE
ON 表名 FOR EACH ROW
BEGIN
-- 触发逻辑,可用 NEW / OLD
END //
DELIMITER ;⚠️ BEFORE 可在数据写入前修改 NEW 值甚至拦截(用 SIGNAL 报错);AFTER 适合写日志、更新统计。严禁在触发器里对本表再 INSERT/UPDATE/DELETE,会造成循环触发!
4.1 使用限制
- 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。
- 不能对本表执行 INSERT / UPDATE / DELETE,避免循环触发。
- 触发器执行失败,主 SQL 也会回滚。
4.2 BEFORE 与 AFTER 选择判断指南
一、核心本质区别
1. BEFORE:在 insert / update / delete 执行之前触发
- 数据还未写入 / 更新 / 删除到表中
- 可以修改 NEW 的值(即将要写入的数据)
- 可以拦截、终止当前操作
2. AFTER:在 insert / update / delete 执行之后触发
- 数据已经真实写入 / 更新 / 删除
- 不能修改 NEW 值,修改无效
- 适合做日志、统计更新、关联表同步
二、通用判断规则
- 需要校验数据、修改即将写入的值、拦截非法操作 → 用 BEFORE
- 需要根据最新数据做统计、写日志、更新其他表 → 用 AFTER
- 涉及人数、平均值、总数等统计计算 → 必须用 AFTER
三、按操作类型细分
| 操作类型 | BEFORE(操作前) | AFTER(操作后) |
|---|---|---|
| INSERT | 数据校验(年龄不能为负、格式检查) 自动填充字段(默认值、创建时间) 拦截非法插入 |
记录插入日志 更新关联表统计(部门人数、平均年龄) |
| UPDATE | 校验修改是否合法 修改即将更新的字段值(格式化、修正) |
记录修改日志 更新关联统计数据 |
| DELETE | 校验是否允许删除 备份删除前的数据到历史表 |
更新关联表统计数据 |
四、SIGNAL SQLSTATE的语法
在 MySQL 中,SIGNAL SQLSTATE 用于在存储过程、函数或触发器中抛出自定义错误。它允许你定义特定的错误代码和错误信息,从而更好地处理和管理异常情况。以下是其详细语法及介绍:
基本语法:
SIGNAL SQLSTATE 'sqlstate_value' SET MESSAGE_TEXT = 'message';
语法要素解释
1、SQLSTATE 'sqlstate_value':SQLSTATE 是一个标准的 5 字符代码,用于标识 SQL 错误类型。不同的数据库系统遵循这个标准,尽管具体的错误代码可能有所不同。例如,'45000' 常用于表示用户定义的异常。在存储过程、函数或触发器中,当你想要抛出自定义的错误时,可以使用这个 SQLSTATE 值,并通过 SET MESSAGE_TEXT 来设置具体的错误信息。你可以选择 MySQL 预定义的 SQLSTATE 值,也可以自定义一个值,但建议遵循标准规范以确保可移植性和一致性。
2、SET MESSAGE_TEXT ='message':这部分用于设置与该错误关联的详细错误信息。message 是一个字符串,可以包含任何有助于诊断问题的文本。例如,SET MESSAGE_TEXT = '操作错误'。
📝 本节例题
以下例题均基于 教学实例 库(student / course / sc / teacher / grades / dept / emp),开始前先执行 USE 教学实例;。建议先自己写一遍,再展开答案对照。
查看参考答案(可复制)
DROP TRIGGER IF EXISTS trg_emp_salary;
DELIMITER //
CREATE TRIGGER trg_emp_salary
BEFORE INSERT ON emp FOR EACH ROW
BEGIN
IF NEW.salary IS NULL THEN
SET NEW.salary = 3000;
END IF;
END//
DELIMITER ;
INSERT INTO emp(name, age, job, entrydate, dept_id) VALUES('新同事', 22, '开发', '2025-09-01', 1);
SELECT name, salary FROM emp WHERE name = '新同事';查看参考答案(可复制)
CREATE TABLE IF NOT EXISTS stu_log (
Sno varchar(10), Sname varchar(10), optime datetime
);
DROP TRIGGER IF EXISTS trg_stu_ins;
CREATE TRIGGER trg_stu_ins
AFTER INSERT ON student FOR EACH ROW
INSERT INTO stu_log VALUES(NEW.Sno, NEW.Sname, NOW());
INSERT INTO student(Sno, Sname, Ssex, sage, class) VALUES('022', '周测', '女', 16, '01');
SELECT * FROM stu_log;查看参考答案(可复制)
DROP TRIGGER IF EXISTS trg_sc_check;
DELIMITER //
CREATE TRIGGER trg_sc_check
BEFORE UPDATE ON sc FOR EACH ROW
BEGIN
IF NEW.score < 0 OR NEW.score > 100 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '成绩必须在 0~100 之间';
END IF;
END//
DELIMITER ;
UPDATE sc SET score = 120 WHERE Sno = '001' AND Cno = '01'; -- 报错被拦截