mysql菜鸟教程
16.2 创建触发器
在了解了触发器的概念和应用场景后,本节将详细介绍如何在 MySQL 中创建触发器。我们将从基本语法开始,通过多个示例逐步深入,并讲解创建过程中的注意事项。
一、创建触发器的语法
CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON 表名
FOR EACH ROW
触发器体;语法解析
二、准备:修改分隔符
在 MySQL 中,默认的语句分隔符是分号 ;。但在触发器体内可能会包含多条 SQL 语句,每条都以 ; 结尾。为了让 MySQL 正确地将整个触发器定义视为一个完整的语句,我们需要在创建触发器之前临时修改分隔符。
DELIMITER // -- 将分隔符改为 // CREATE TRIGGER 触发器名 ... BEGIN ...; -- 内部语句仍以 ; 结尾 ...; END // DELIMITER ; -- 恢复分隔符为 ;
这是一个固定模式,所有包含 BEGIN ... END 块的触发器定义都需要这样做。
示例1:记录插入日志
假设我们有一个 students 表,希望在每次插入新学生时自动记录一条日志到 student_log 表。
首先创建日志表:
CREATE TABLE student_log ( log_id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, action VARCHAR(20) NOT NULL, log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
创建触发器:
DELIMITER // CREATE TRIGGER after_insert_students AFTER INSERT ON students FOR EACH ROW BEGIN INSERT INTO student_log (student_id, action) VALUES (NEW.id, 'INSERT'); END // DELIMITER ;
解释:
AFTER INSERT:在插入成功之后触发。
FOR EACH ROW:对于每一行新插入的数据,都会执行一次 BEGIN ... END 中的内容。
NEW.id:NEW 代表即将插入(或刚插入)的新行,NEW.id 就是新行的 id 列值。对于自增列,NEW.id 是在插入后生成的值,在 BEFORE INSERT 触发器中可能为 0 或 NULL。
测试:
INSERT INTO students (name, age) VALUES ('张三', 20);
SELECT * FROM student_log;示例2:数据验证(BEFORE INSERT)
在插入数据前检查年龄是否合法,如果非法则阻止操作。
DELIMITER // CREATE TRIGGER before_insert_students BEFORE INSERT ON students 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 ;
说明:
BEFORE INSERT:在插入之前触发,有机会修改 NEW 的值或阻止插入。
SIGNAL 语句用于抛出自定义错误,导致当前操作失败并回滚。
测试:
INSERT INTO students (name, age) VALUES ('李四', 200); -- 将被拒绝,并显示错误消息示例3:自动计算列(BEFORE INSERT/UPDATE)
假设有一个 order_items 表,包含 quantity、unit_price 和 total_price 列。我们希望 total_price 自动计算为 quantity * unit_price。
DELIMITER // CREATE TRIGGER before_insert_order_items BEFORE INSERT ON order_items FOR EACH ROW BEGIN SET NEW.total_price = NEW.quantity * NEW.unit_price; END // DELIMITER ;
对于更新操作,同样需要维护该列:
DELIMITER // CREATE TRIGGER before_update_order_items BEFORE UPDATE ON order_items FOR EACH ROW BEGIN SET NEW.total_price = NEW.quantity * NEW.unit_price; END // DELIMITER ;
示例4:记录更新和删除的旧值(AFTER UPDATE / AFTER DELETE)
使用 OLD 关键字访问更新前的值或删除的值。
记录更新前和更新后的姓名变化:
CREATE TABLE students_audit ( audit_id INT PRIMARY KEY AUTO_INCREMENT, student_id INT, old_name VARCHAR(100), new_name VARCHAR(100), change_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); DELIMITER // CREATE TRIGGER after_update_students AFTER UPDATE ON students FOR EACH ROW BEGIN IF OLD.name != NEW.name THEN INSERT INTO students_audit (student_id, old_name, new_name) VALUES (OLD.id, OLD.name, NEW.name); END IF; END // DELIMITER ;
记录删除的数据:
DELIMITER // CREATE TRIGGER after_delete_students AFTER DELETE ON students FOR EACH ROW BEGIN INSERT INTO student_log (student_id, action) VALUES (OLD.id, 'DELETE'); END // DELIMITER ;
三、查看已有触发器
-- 查看当前数据库中的所有触发器 SHOW TRIGGERS; -- 查看特定触发器的定义 SHOW CREATE TRIGGER 触发器名;
四、删除触发器
DROP TRIGGER [IF EXISTS] 触发器名;
五、创建触发器的注意事项
1、同一个表不能有两个相同的触发时间和事件组合。例如,不能有两个 AFTER INSERT 触发器,但可以有 BEFORE INSERT 和 AFTER INSERT 各一个。
2、触发器主体中不能使用:
显式或隐式地开始或结束事务的语句(如 START TRANSACTION、COMMIT、ROLLBACK)。
返回结果集的语句(如普通 SELECT 不赋值给变量)。
对自身表进行修改的语句,可能引发递归(除非合理控制)。
3、递归限制:MySQL 默认不允许递归调用触发器(max_sp_recursion_depth = 0)。如果触发器内又操作了同一张表,可能导致错误或死循环。
4、性能影响:触发器会增加 DML 操作的开销,对于频繁操作的表,应谨慎使用。
5、使用 SIGNAL 抛错:在 BEFORE 触发器中,如果条件不满足,可以使用 SIGNAL 语句抛出错误,阻止操作继续。
6、NEW 和 OLD 的访问权限:
在 BEFORE 触发器中,可以修改 NEW 的列值(但不能修改 OLD)。
在 AFTER 触发器中,不能修改数据,只能读取。
7、字符集和校对规则:触发器内的字符串比较可能与表定义不同,需要注意一致性。
六、综合练习
创建一个 products 表和一个 product_price_history 表,每当 products 表中的价格发生变化时,自动记录旧价格、新价格和修改时间。
表结构:
CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100), price DECIMAL(10,2) ); CREATE TABLE price_history ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT, old_price DECIMAL(10,2), new_price DECIMAL(10,2), changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
触发器:
DELIMITER // CREATE TRIGGER after_update_products AFTER UPDATE ON products FOR EACH ROW BEGIN IF OLD.price != NEW.price THEN INSERT INTO price_history (product_id, old_price, new_price) VALUES (OLD.id, OLD.price, NEW.price); END IF; END // DELIMITER ;
测试:
INSERT INTO products (name, price) VALUES ('手机', 2999.00);
UPDATE products SET price = 2799.00 WHERE id = 1;
SELECT * FROM price_history;小结
创建触发器使用 CREATE TRIGGER 语句,需指定触发时间、事件、表名和主体。
对于多语句主体,必须使用 DELIMITER 修改分隔符,并用 BEGIN ... END 包裹。
使用 NEW 和 OLD 访问行数据,BEFORE 触发器中可修改 NEW。
可用 SHOW TRIGGERS 查看,用 DROP TRIGGER 删除。
触发器强大但需谨慎使用,注意性能和递归问题。

发表评论
所有评论