mysql菜鸟教程
16.3 触发时机:BEFORE与AFTER
在创建触发器时,必须指定触发时机——BEFORE 或 AFTER。这两个关键字决定了触发器是在触发事件之前还是之后执行。理解它们的区别以及各自的适用场景,是合理设计触发器的关键。
一、BEFORE 触发器
BEFORE 触发器 在触发事件(INSERT、UPDATE、DELETE)执行之前被激活。此时,数据尚未写入磁盘(对于 INSERT 和 UPDATE)或尚未被删除(对于 DELETE)。
特点
可以修改即将插入或更新的数据:通过 NEW 关键字,可以在 BEFORE INSERT 和 BEFORE UPDATE 触发器中修改列的值。
可以阻止操作:如果检查到数据不符合业务规则,可以通过 SIGNAL 语句抛出错误,从而阻止事件的执行。
不能访问旧值(OLD)的修改:OLD 在 BEFORE UPDATE 和 BEFORE DELETE 中只读,不可修改。
适用场景
数据验证:检查插入或更新的数据是否满足业务规则(如年龄范围、格式校验)。如果不满足,直接抛出错误,阻止无效数据进入数据库。
默认值填充:为某些列自动设置值(例如创建时间、更新时间、自动计算列)。
数据清洗:对输入数据进行规范化,例如去除字符串前后空格、统一大小写等。
二、AFTER 触发器
AFTER 触发器 在触发事件执行完成之后被激活。此时,数据已经写入磁盘(对于 INSERT 和 UPDATE)或已被删除(对于 DELETE)。
特点
不能修改当前操作的数据:因为事件已经完成,数据已定。但可以通过其他 UPDATE 语句修改本表或其他表(注意避免递归)。
主要用于记录操作的结果:例如记录日志、更新汇总表、触发后续处理等。
可以读取 OLD 和 NEW,但无法修改它们。
适用场景
审计日志:记录数据变更的历史,例如将修改前后的值存入日志表。
级联更新:当主表数据变化时,自动更新其他相关表中的数据(但要注意避免循环触发)。
更新统计信息:例如,当订单明细插入后,自动更新订单的总金额。
三、BEFORE 与 AFTER 对比
示例详解
1. BEFORE INSERT:数据验证与默认值
假设有一个 users 表,要求用户名不能为空,邮箱格式基本正确,并自动设置注册时间。
CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100), register_time DATETIME ); DELIMITER // CREATE TRIGGER before_insert_users BEFORE INSERT ON users FOR EACH ROW BEGIN -- 1. 验证用户名不为空 IF NEW.username IS NULL OR NEW.username = '' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户名不能为空'; END IF; -- 2. 简单验证邮箱格式(必须包含 @) IF NEW.email IS NOT NULL AND NEW.email NOT LIKE '%@%' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱格式不正确'; END IF; -- 3. 自动设置注册时间(如果未提供) IF NEW.register_time IS NULL THEN SET NEW.register_time = NOW(); END IF; END // DELIMITER ;
2. BEFORE UPDATE:自动更新时间戳
许多表需要一个“最后更新时间”字段,可以在 BEFORE UPDATE 触发器中自动维护。
ALTER TABLE users ADD COLUMN last_update TIMESTAMP; DELIMITER // CREATE TRIGGER before_update_users BEFORE UPDATE ON users FOR EACH ROW BEGIN SET NEW.last_update = NOW(); END // DELIMITER ;
3. AFTER INSERT:记录日志
当新用户注册后,自动向日志表插入一条记录。
CREATE TABLE user_log ( log_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, action VARCHAR(20), log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); DELIMITER // CREATE TRIGGER after_insert_users AFTER INSERT ON users FOR EACH ROW BEGIN INSERT INTO user_log (user_id, action) VALUES (NEW.id, 'INSERT'); END // DELIMITER ;
4. AFTER UPDATE:记录变更审计
记录用户关键信息(如邮箱)的修改历史。
CREATE TABLE user_audit ( audit_id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, old_email VARCHAR(100), new_email VARCHAR(100), change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); DELIMITER // CREATE TRIGGER after_update_users AFTER UPDATE ON users FOR EACH ROW BEGIN IF OLD.email != NEW.email THEN INSERT INTO user_audit (user_id, old_email, new_email) VALUES (OLD.id, OLD.email, NEW.email); END IF; END // DELIMITER ;
5. BEFORE DELETE:记录删除前的数据
在删除数据前,将其备份到历史表(但注意:BEFORE DELETE 中还可以访问 OLD,可以用于记录,但数据尚未删除,如果希望在删除后记录,则用 AFTER DELETE)。
CREATE TABLE deleted_users LIKE users; DELIMITER // CREATE TRIGGER before_delete_users BEFORE DELETE ON users FOR EACH ROW BEGIN INSERT INTO deleted_users SELECT * FROM users WHERE id = OLD.id; END // DELIMITER ;
四、选择建议
如果你需要修改即将插入/更新的数据,或者在数据真正写入前进行验证并可能阻止操作,选择 BEFORE。
如果你需要在操作完成后记录日志、更新其他表,并且不需要修改当前操作的数据,选择 AFTER。
对于同一类操作,可以同时使用 BEFORE 和 AFTER 触发器,分别完成不同阶段的任务。
五、注意事项
不能对同一张表的同一个触发事件定义多个同一时机的触发器。例如,不能有两个 AFTER INSERT 触发器,但可以有一个 BEFORE INSERT 和一个 AFTER INSERT。
在 BEFORE 触发器中抛出错误,整个操作(包括触发器自身)会回滚。对于 InnoDB 表,即使触发器失败,语句也会回滚。
避免在 AFTER 触发器中修改同一张表,这可能引发递归调用。例如,在 AFTER UPDATE 中再次 UPDATE 同一行,会再次触发同一个触发器,导致死循环。MySQL 默认禁止递归(max_sp_recursion_depth = 0),但仍有风险。
性能影响:触发器会增加每次 DML 操作的开销,尤其在高并发场景下,应避免在触发器内执行复杂的查询或长时间操作。
六、小结
BEFORE 和 AFTER 决定了触发器在事件之前还是之后执行。
BEFORE 适合数据验证、默认值填充、数据清洗;AFTER 适合审计日志、级联更新。
BEFORE 触发器可以修改 NEW 的值并阻止操作;AFTER 触发器只能读取,不能修改。
合理选择触发时机,可以让数据库自动完成许多工作,提升数据的一致性和完整性。

发表评论
所有评论