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 对比
特性
BEFORE
AFTER
触发时间
事件执行之前
事件执行之后
能否修改当前操作的行(NEW)
可以(仅 INSERT/UPDATE)
不能
能否阻止事件
可以(通过抛出错误)
不能(事件已完成)
对 OLD 的访问
只读
只读
主要用途
验证、计算默认值、数据清洗
记录日志、级联更新、同步数据
性能影响
略微增加操作前的开销
略微增加操作后的开销
示例详解
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 触发器只能读取,不能修改。
-
合理选择触发时机,可以让数据库自动完成许多工作,提升数据的一致性和完整性。

发表评论
所有评论