触发器:数据库的"自动响应"机制

简介: 数据库触发器是“自动响应”机制:当INSERT/UPDATE/DELETE发生时,无需调用即执行预设逻辑。适用于审计日志、数据校验、自动填充、级联操作等场景。支持BEFORE(可修改数据)和AFTER(常用于记录)两种时机,但需警惕性能影响与调试难度。

💡 触发器 = 数据库的"自动触发器",事件发生时自动执行预定义操作!

大家好呀!我是数据库小学妹👋

昨天我们学了存储过程,可以把一堆SQL打包,然后手动 CALL 调用。但今天遇到了一个自动化需求👇

我想自动记录“每次有人修改订单表,就把旧值和新值存到日志表” —— 难道每次更新都要手动调用一个存储过程吗?万一忘写了怎么办?

数据库早就想到了这个问题,它提供了一种 “自动触发” 的机制——触发器。就像你设置的闹钟,到了点自动响,不用你去按。

今天我就把自己学会的触发器分享出来,保证你看完也能让数据库帮你“自动干活”。

一、什么是触发器?

触发器是一种特殊的存储过程,它在特定的数据库操作(INSERT、UPDATE、DELETE)发生时自动触发执行,无需手动调用。

💡 类比:你养了一只猫,每次它跳上桌子(事件),你就喷水(动作)。触发器就是这种“当A发生时,自动做B”的规则。

✅ 触发器的优点

  • 自动化:事件发生时自动执行
  • 数据一致性:强制执行业务规则
  • 审计追踪:自动记录数据变更
  • 业务逻辑封装:复杂逻辑在数据库层实现
  • 安全性:限制非法数据操作

⚠️ 触发器的限制

  • 调试困难:不像存储过程容易调试
  • 性能影响:触发器执行会增加操作时间
  • 维护成本:逻辑分散在数据库和应用层
  • 不可见性;应用层不知道触发器存在
  • 递归触发:触发器可能触发其他触发器

二、触发器实战:三步创建触发器

🎯 基础语法

-- 创建触发器
CREATE TRIGGER 触发器名
BEFORE/AFTER INSERT/UPDATE/DELETE ON 表名
FOR EACH ROW
BEGIN
    -- 触发器逻辑
END;
-- 查看触发器
SHOW TRIGGERS;
-- 删除触发器
DROP TRIGGER 触发器名;

💡 完整示例

-- 创建测试表
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    product_id INT,
    amount DECIMAL(10, 2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE order_audit_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    order_id INT,
    action VARCHAR(20),  -- create/update/delete
    old_status VARCHAR(20),
    new_status VARCHAR(20),
    operator VARCHAR(50),
    action_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ========================
-- 方案1:没有触发器(每次都要手动记录)
-- ========================
-- 创建订单
INSERT INTO orders (user_id, product_id, amount, status) 
VALUES (1, 101, 2999.00, 'pending');
-- 手动记录日志
INSERT INTO order_audit_log (order_id, action, old_status, new_status, operator) 
VALUES (LAST_INSERT_ID(), 'create', NULL, 'pending', 'admin');
-- ========================
-- 方案2:有触发器(自动记录,无需手动)
-- ========================
-- 1. 创建订单创建触发器
DELIMITER $$
CREATE TRIGGER trg_order_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
    INSERT INTO order_audit_log (order_id, action, old_status, new_status, operator)
    VALUES (NEW.id, 'create', NULL, NEW.status, 'system');
END$$
DELIMITER ;
-- 2. 创建订单更新触发器
DELIMITER $$
CREATE TRIGGER trg_order_after_update
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    IF OLD.status != NEW.status THEN
        INSERT INTO order_audit_log (order_id, action, old_status, new_status, operator)
        VALUES (NEW.id, 'update', OLD.status, NEW.status, 'system');
    END IF;
END$$
DELIMITER ;
-- 3. 创建订单删除触发器
DELIMITER $$
CREATE TRIGGER trg_order_before_delete
BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
    INSERT INTO order_audit_log (order_id, action, old_status, new_status, operator)
    VALUES (OLD.id, 'delete', OLD.status, NULL, 'system');
END$$
DELIMITER ;
-- 测试:现在只需要操作orders表,日志自动记录!
INSERT INTO orders (user_id, product_id, amount, status) 
VALUES (2, 102, 1999.00, 'pending');
UPDATE orders SET status = 'paid' WHERE id = 1;
DELETE FROM orders WHERE id = 1;
-- 查看审计日志
SELECT * FROM order_audit_log;

三、触发器的类型和时机

📊 触发器类型

📊 触发时机

💡 时机选择示例

DELIMITER $$
-- BEFORE INSERT:自动填充创建时间
CREATE TRIGGER trg_user_before_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    SET NEW.created_at = NOW();
    SET NEW.created_by = 'system';
END$$
-- AFTER INSERT:记录日志
CREATE TRIGGER trg_user_after_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit_log (user_id, action, action_time)
    VALUES (NEW.id, 'create', NOW());
END$$
-- BEFORE UPDATE:记录修改时间
CREATE TRIGGER trg_user_before_update
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
    SET NEW.updated_at = NOW();
    SET NEW.updated_by = 'system';
END$$
-- AFTER UPDATE:记录变更详情
CREATE TRIGGER trg_user_after_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    IF OLD.email != NEW.email THEN
        INSERT INTO user_change_log (user_id, field_name, old_value, new_value, change_time)
        VALUES (NEW.id, 'email', OLD.email, NEW.email, NOW());
    END IF;
END$$
DELIMITER ;

四、触发器的实际应用场景

🎯 场景1:自动填充字段

DELIMITER $$
-- 自动填充创建和修改时间
CREATE TRIGGER trg_auto_timestamp
BEFORE INSERT ON any_table
FOR EACH ROW
BEGIN
    SET NEW.created_at = NOW();
    SET NEW.updated_at = NOW();
END$$
CREATE TRIGGER trg_auto_update_timestamp
BEFORE UPDATE ON any_table
FOR EACH ROW
BEGIN
    SET NEW.updated_at = NOW();
END$$
DELIMITER ;

🎯 场景2:数据验证

DELIMITER $$
-- 验证订单金额不能为负数
CREATE TRIGGER trg_order_before_insert_validate
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    IF NEW.amount < 0 THEN
        SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = '订单金额不能为负数!';
    END IF;
    
    IF NEW.amount = 0 THEN
        SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = '订单金额不能为0!';
    END IF;
END$$
-- 验证邮箱格式
CREATE TRIGGER trg_user_before_insert_email
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
    IF NEW.email NOT REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$' THEN
        SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = '邮箱格式不正确!';
    END IF;
END$$
DELIMITER ;

🎯 场景3:级联操作

DELIMITER $$
-- 删除用户时,级联删除相关数据
CREATE TRIGGER trg_user_before_delete_cascade
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
    -- 删除用户的订单
    DELETE FROM orders WHERE user_id = OLD.id;
    
    -- 删除用户的评论
    DELETE FROM comments WHERE user_id = OLD.id;
    
    -- 删除用户的收藏
    DELETE FROM favorites WHERE user_id = OLD.id;
END$$
-- 更新产品时,同步更新相关表
CREATE TRIGGER trg_product_after_update_sync
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
    -- 更新订单中的产品名称(如果产品名称变了)
    IF OLD.name != NEW.name THEN
        UPDATE order_items 
        SET product_name = NEW.name 
        WHERE product_id = NEW.id;
    END IF;
END$$
DELIMITER ;

🎯 场景4:业务规则强制执行

DELIMITER $$
-- 库存不足时,禁止创建订单
CREATE TRIGGER trg_order_before_insert_stock_check
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    DECLARE available_stock INT;
    
    SELECT stock INTO available_stock 
    FROM products 
    WHERE id = NEW.product_id;
    
    IF available_stock < NEW.quantity THEN
        SIGNAL SQLSTATE '45000' 
        SET MESSAGE_TEXT = '库存不足,无法创建订单!';
    END IF;
    
    -- 扣减库存
    UPDATE products 
    SET stock = stock - NEW.quantity 
    WHERE id = NEW.product_id;
END$$
-- 订单完成后,自动增加用户积分
CREATE TRIGGER trg_order_after_update_points
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    IF OLD.status != 'completed' AND NEW.status = 'completed' THEN
        UPDATE users 
        SET points = points + FLOOR(NEW.amount / 100) 
        WHERE id = NEW.user_id;
    END IF;
END$$
DELIMITER ;

🎯 场景5:数据审计和追踪

DELIMITER $$
-- 全面的用户操作审计
CREATE TRIGGER trg_user_full_audit
AFTER INSERT ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit (user_id, action, details, audit_time)
    VALUES (
        NEW.id, 
        'INSERT', 
        CONCAT('创建用户: ', NEW.username, ', 邮箱: ', NEW.email),
        NOW()
    );
END$$
CREATE TRIGGER trg_user_update_audit
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    DECLARE changes TEXT DEFAULT '';
    
    IF OLD.username != NEW.username THEN
        SET changes = CONCAT(changes, '用户名: ', OLD.username, ' -> ', NEW.username, '; ');
    END IF;
    
    IF OLD.email != NEW.email THEN
        SET changes = CONCAT(changes, '邮箱: ', OLD.email, ' -> ', NEW.email, '; ');
    END IF;
    
    IF OLD.status != NEW.status THEN
        SET changes = CONCAT(changes, '状态: ', OLD.status, ' -> ', NEW.status, '; ');
    END IF;
    
    IF changes != '' THEN
        INSERT INTO user_audit (user_id, action, details, audit_time)
        VALUES (NEW.id, 'UPDATE', changes, NOW());
    END IF;
END$$
CREATE TRIGGER trg_user_delete_audit
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit (user_id, action, details, audit_time)
    VALUES (
        OLD.id, 
        'DELETE', 
        CONCAT('删除用户: ', OLD.username, ', 邮箱: ', OLD.email),
        NOW()
    );
END$$
DELIMITER ;

五、触发器与存储过程的区别

一句话总结:

  • 需要“自动响应” → 触发器
  • 需要“主动调用” → 存储过程

六、新手避坑指南(血泪总结😭)

七、今日学习心得

今天的内容总结成三句话:

  1. 触发器是自动执行的,当表发生 INSERT/UPDATE/DELETE 时触发
  2. BEFORE 用于校验和修改,AFTER 用于记录日志和同步
  3. 适合审计、校验、自动同步,但别滥用,避免性能陷阱

👋 我是数据库小学妹一个用设计师思维学数据库的转行人。我们一起,把复杂的技术变得简单有趣!💕


本文为个人学习总结,所有命令均在MySQL 8.0环境下验证。触发器虽方便,但请谨慎使用,保持数据库逻辑清晰。


相关文章
|
5月前
|
SQL 关系型数据库 MySQL
主键、外键和约束:让数据库“有规矩”才能不出错!|转行学DB第5天
本文用通俗易懂的语言讲解了主键(数据的唯一标识)、外键(表间关联)以及唯一约束、非空约束等其他常见约束规则。通过具体SQL示例展示了各种约束的使用方法,并分享了新手容易踩的坑和实用建议。
|
6月前
|
运维 负载均衡 Java
微服务基础1-微服务拆分与服务调用
文章摘要: 本文详细介绍了微服务架构的概念、优势及实现方式。对比单体架构的局限性,微服务通过拆分功能模块实现高内聚、低耦合,提升系统可用性和开发效率。重点讲解了微服务拆分策略(纵向按功能、横向抽通用)、服务注册与发现机制(基于Nacos),以及远程调用方案(RestTemplate和OpenFeign)。OpenFeign通过动态代理简化RPC调用,支持连接池和日志配置,使远程调用如同本地方法。文章还探讨了微服务拆分时机(初创期验证后或大型项目初期直接采用),为不同规模团队提供架构演进建议。
|
5月前
|
人工智能 监控 Kubernetes
LoongCollector + ACS Agent Sandbox:构建 AI Agent 生产级运行平台
文章介绍了阿里云ACSAgentSandbox与LoongCollector协同构建的AIAgent生产级运行平台,通过沙箱隔离保障运行时安全,并以高性能、全链路可观测能力解决Agent行为不可预测和执行风险难题。
2950 86
|
5月前
|
SQL 关系型数据库 MySQL
数据量大查询慢?索引让你的SQL秒级响应!|转行学DB第9天
用生活化比喻(如字典目录)详解索引原理:它通过B+树结构加速查询,避免全表扫描;涵盖创建、查看、删除索引方法,联合索引的最左前缀原则,以及读写平衡等实战要点——让查询从“等几秒”变“秒出”!
数据量大查询慢?索引让你的SQL秒级响应!|转行学DB第9天
|
5月前
|
SQL 关系型数据库 MySQL
5款好用的免费MySQL客户端,新手必备!
告别枯燥命令行!数据库小学妹精选5款免费MySQL图形化工具:Workbench(官方全能)、phpMyAdmin(免安装Web版)、DBeaver(多库支持)、HeidiSQL(Windows轻量之选)、TablePlus(高颜值跨平台)。小白友好,语法高亮、自动补全、可视化结构一应俱全,助你高效学SQL!
|
3月前
|
SQL JSON 关系型数据库
MySQL SQL进阶最佳实践:从查询优化到表结构变更的10个核心技巧
从EXPLAIN到生成列,从窗口函数到在线DDL,10个MySQL进阶技巧按"解决什么问题"串起来讲,配合面试话术和避坑清单,帮你在写SQL这件事上真正进阶
|
5月前
|
SQL 关系型数据库 MySQL
事务:让数据操作"要么全成功,要么全失败"!|转行学DB第10天
数据库小学妹带你轻松掌握事务!转账不丢钱、数据不混乱的秘密,就在ACID四大特性——原子性(全做或全不做)、一致性、隔离性、持久性。用START TRANSACTION开启,COMMIT提交,ROLLBACK回滚,保障多步操作安全可靠。
|
5月前
|
SQL 关系型数据库 MySQL
子查询:让SQL像俄罗斯套娃一样嵌套!|转行学DB第8天
数据库小学妹带你轻松入门子查询!用“SELECT里套SELECT”,像俄罗斯套娃一样,一步解决“先算平均分、再查高分学生”等两步难题。支持WHERE(条件筛选)、FROM(临时表)、SELECT(标量列)三种用法,简洁直观,新手友好~
|
5月前
|
SQL 数据库
多表关联查询入门:LEFT JOIN、INNER JOIN一文搞懂|转行学DB第6天
本文通俗易懂地讲解了数据库多表查询的三种JOIN操作:INNER JOIN(内连接)只返回两表匹配的数据,适用于查询交集数据;LEFT JOIN(左连接)保留左表所有记录并匹配右表数据,适用于查询主表完整信息;RIGHT JOIN(右连接)则保留右表所有记录。