404 Not Found

404 Not Found


nginx

触发器与事件

触发器与事件

生活类比

想象一个智能家居系统:

核心概念

什么是触发器

触发器(Trigger)是与表关联的数据库对象,当表上发生特定事件时自动执行:

触发时机:BEFORE vs AFTER

时机 说明 典型用途
BEFORE 在操作执行前触发 数据验证、自动修改值
AFTER 在操作执行后触发 审计日志、级联更新

触发事件类型

NEW 和 OLD 引用

在触发器中可以访问被操作的数据:

事件 NEW OLD
INSERT 新插入的行 不可用
UPDATE 更新后的值 更新前的值
DELETE 不可用 被删除的行
SQL
-- SQLite 示例:访问 NEW 和 OLD
-- NEW.column_name:引用新数据
-- OLD.column_name:引用旧数据

触发器的典型使用场景

  1. 审计日志:记录谁在什么时间修改了什么数据
  2. 级联更新:自动更新关联表的数据
  3. 数据验证:在数据写入前进行复杂的业务规则验证
  4. 自动计算:自动计算派生字段的值
  5. 维护冗余数据:同步更新汇总表或缓存表

扩展知识:其他数据库的事件调度器 EVENT SCHEDULER

⚠️ 注意:SQLite 不支持事件调度器。以下内容为扩展知识,展示 MySQL 的事件调度器语法作为参考。

事件调度器允许按照时间计划自动执行任务:

SQL
-- MySQL 事件调度器示例
-- 启用事件调度器
SET GLOBAL event_scheduler = ON;

-- 创建定时事件:每天凌晨清理过期数据
CREATE EVENT cleanup_expired_orders
ON SCHEDULE EVERY 1 DAY
STARTS '2024-01-01 00:00:00'
DO
    DELETE FROM orders 
    WHERE status = 'expired' 
    AND created_at < DATE_SUB(NOW(), INTERVAL 90 DAY);

-- 创建一次性事件
CREATE EVENT one_time_report
ON SCHEDULE AT '2024-12-31 23:59:59'
DO
    INSERT INTO annual_report (year, total_sales)
    SELECT YEAR(NOW()), SUM(amount) FROM orders WHERE YEAR(created_at) = YEAR(NOW());

-- 查看所有事件
SHOW EVENTS;

-- 禁用事件
ALTER EVENT cleanup_expired_orders DISABLE;

-- 删除事件
DROP EVENT IF EXISTS cleanup_expired_orders;

基本语法

SQLite 触发器语法

SQLite 支持触发器,语法如下:

SQL
-- 创建触发器基本语法
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
[FOR EACH ROW]
[WHEN condition]
BEGIN
    -- 触发器逻辑
END;

MySQL 触发器语法

SQL
-- MySQL 触发器语法
DELIMITER //
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
BEGIN
    -- 触发器逻辑
    -- 使用 NEW 和 OLD 引用数据
END //
DELIMITER ;

PostgreSQL 触发器语法

SQL
-- PostgreSQL 需要先创建触发器函数
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
    -- 触发器逻辑
    RETURN NEW; -- 或 RETURN OLD
END;
$$ LANGUAGE plpgsql;

-- 然后创建触发器
CREATE TRIGGER trigger_name
{BEFORE | AFTER} {INSERT | UPDATE | DELETE} ON table_name
FOR EACH ROW
EXECUTE FUNCTION trigger_function_name();
💡 提示:触发器会增加数据库操作的复杂度,应谨慎使用,避免创建过于复杂的触发器逻辑。

示例

示例:创建审计日志触发器

创建一个触发器,自动记录员工薪资的修改历史。

SQL
-- SQLite 版本
-- 首先创建审计日志表
CREATE TABLE IF NOT EXISTS salary_audit_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    employee_id INTEGER,
    old_salary REAL,
    new_salary REAL,
    changed_by TEXT,
    changed_at TEXT DEFAULT (datetime('now', 'localtime')),
    action TEXT
);

-- 创建触发器:记录薪资更新
CREATE TRIGGER audit_salary_update
AFTER UPDATE ON employees
FOR EACH ROW
WHEN OLD.salary != NEW.salary
BEGIN
    INSERT INTO salary_audit_log (
        employee_id, 
        old_salary, 
        new_salary, 
        changed_by, 
        action
    )
    VALUES (
        NEW.id,
        OLD.salary,
        NEW.salary,
        'system',  -- 实际应用中可使用当前用户
        'UPDATE'
    );
END;

-- 测试触发器
UPDATE employees SET salary = 12000 WHERE id = 1;

-- 查看审计日志
SELECT * FROM salary_audit_log;
▶ 试一试

示例:BEFORE INSERT 触发器自动填充数据

创建一个触发器,在插入订单时自动设置订单日期和初始状态。

SQL
-- 创建订单表
CREATE TABLE IF NOT EXISTS orders_new (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    customer_name TEXT NOT NULL,
    product_id INTEGER,
    quantity INTEGER DEFAULT 1,
    order_date TEXT,
    status TEXT DEFAULT 'pending',
    created_at TEXT
);

-- 创建 BEFORE INSERT 触发器
CREATE TRIGGER set_order_defaults
BEFORE INSERT ON orders_new
FOR EACH ROW
BEGIN
    -- 自动设置订单日期为当前日期
    SET NEW.order_date = COALESCE(NEW.order_date, date('now', 'localtime'));
    -- 自动设置创建时间
    SET NEW.created_at = COALESCE(NEW.created_at, datetime('now', 'localtime'));
    -- 如果数量为空或0,默认设为1
    SET NEW.quantity = CASE WHEN NEW.quantity IS NULL OR NEW.quantity <= 0 THEN 1 ELSE NEW.quantity END;
END;

-- 测试:插入时不指定 order_date 和 status
INSERT INTO orders_new (customer_name, product_id, quantity) VALUES ('张三', 1, 3);

-- 验证自动填充的结果
SELECT * FROM orders_new;
▶ 试一试

预期输出

TEXT
id  customer_name  product_id  quantity  order_date   status   created_at
1   张三           1           3         2026-06-28   pending  2026-06-28 10:30:00
💡 SQLite 限制:SQLite 的触发器语法与 MySQL/PostgreSQL 有所不同。MySQL 支持 SET NEW.column = value,而 PostgreSQL 需要使用 NEW.column := valueBEFORE 触发器中修改新值。

应用场景

场景1:自动更新订单汇总表

SQL
-- SQLite 版本
-- 创建订单汇总表
CREATE TABLE IF NOT EXISTS order_summary (
    customer_id INTEGER PRIMARY KEY,
    total_orders INTEGER DEFAULT 0,
    total_amount REAL DEFAULT 0,
    last_order_date TEXT
);

-- 创建触发器:插入订单时自动更新汇总
CREATE TRIGGER update_order_summary_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
    INSERT OR REPLACE INTO order_summary (customer_id, total_orders, total_amount, last_order_date)
    SELECT 
        NEW.customer_id,
        COALESCE(os.total_orders, 0) + 1,
        COALESCE(os.total_amount, 0) + NEW.amount,
        NEW.order_date
    FROM (SELECT 1) AS dummy
    LEFT JOIN order_summary os ON os.customer_id = NEW.customer_id;
END;

-- 创建触发器:删除订单时自动更新汇总
CREATE TRIGGER update_order_summary_delete
AFTER DELETE ON orders
FOR EACH ROW
BEGIN
    UPDATE order_summary
    SET 
        total_orders = total_orders - 1,
        total_amount = total_amount - OLD.amount
    WHERE customer_id = OLD.customer_id;
    
    -- 如果订单数为0,删除汇总记录
    DELETE FROM order_summary 
    WHERE customer_id = OLD.customer_id AND total_orders <= 0;
END;

场景2:数据验证与自动填充

SQL
-- SQLite 版本:自动计算订单总价
CREATE TRIGGER calculate_order_total
BEFORE INSERT ON orders
FOR EACH ROW
BEGIN
    -- 自动设置创建时间
    SELECT NEW.created_at IS NULL THEN
        SET NEW.created_at = datetime('now', 'localtime');
    END IF;
    
    -- 验证金额必须为正数
    IF NEW.amount < 0 THEN
        SELECT RAISE(ABORT, '订单金额不能为负数');
    END IF;
END;

❓ 常见问题

Q:触发器会影响数据库性能吗? A: 会的。每次触发器激活都会执行额外的SQL操作,大量触发器或复杂逻辑会降低写入性能。建议保持触发器逻辑简洁,避免在触发器中执行耗时操作。

Q:一个表可以有多个触发器吗? A: SQLite 对同一事件和时机只允许一个触发器。MySQL 和 PostgreSQL 允许多个触发器,可以通过 FOLLOWS/PRECEDES 指定执行顺序。

Q:触发器中可以访问其他表吗? A: 可以。触发器中可以查询和修改其他表的数据,但要注意避免循环触发和死锁问题。

Q:如何调试触发器? A: 可以在触发器中插入临时测试数据到日志表,查看执行流程。MySQL 可以使用 SIGNAL 语句抛出自定义错误信息。

📖 小节

本课我们学习了:

📝 作业

  1. 基础练习:创建一个触发器,当删除员工时,自动将其信息备份到 employees_backup 表中。

  2. 进阶练习:创建一个触发器系统,实现以下功能:

    • 当插入新订单时,自动减少产品库存
    • 当库存不足时,阻止订单插入并提示错误
  3. 思考题:触发器和应用程序代码都可以实现业务逻辑,各自适用于什么场景?如何选择?


下一课→24-practice-advanced.md

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏