触发器概述

触发器是PostgreSQL数据库中一种特殊的存储过程,它会在特定的数据库事件(如INSERT、UPDATE、DELETE操作)发生时自动执行。触发器为数据库提供了强大的自动化能力,可以用于实现数据完整性约束、审计日志、数据同步等复杂业务逻辑。本章将详细介绍PostgreSQL触发器的概念、类型和使用场景。

触发器的基本概念

触发器是一种特殊的数据库对象,它与表相关联,并在特定的数据库操作发生时自动执行。触发器的主要特点包括:

  • 自动执行:触发器在指定的事件发生时自动执行,无需显式调用
  • 事件驱动:触发器响应特定的数据库事件,如数据的插入、更新或删除
  • 表关联:触发器必须与特定的表关联,不能独立存在
  • 时机控制:触发器可以在事件发生之前或之后执行

触发器的工作原理

触发器的工作流程如下:

  1. 用户执行触发事件的操作(INSERT、UPDATE、DELETE等)
  2. 数据库检查是否存在与该表和事件相关的触发器
  3. 如果存在触发器,则按照定义的时机和顺序执行触发器函数
  4. 触发器函数执行完毕后,继续执行原始操作
  5. 如果触发器函数中出现错误,则可能回滚整个操作

触发器的类型

PostgreSQL支持多种类型的触发器,可以根据不同的维度进行分类。

按事件类型分类

-- 行级触发器 - 对每一行执行一次
CREATE TRIGGER row_trigger
    BEFORE INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION log_user_insert();

-- 语句级触发器 - 对整个SQL语句执行一次
CREATE TRIGGER statement_trigger
    AFTER INSERT ON users
    FOR EACH STATEMENT
    EXECUTE FUNCTION log_user_insert_summary();

两种类型的触发器区别:

类型 行级触发器 (FOR EACH ROW) 语句级触发器 (FOR EACH STATEMENT)
执行次数 对受影响的每一行执行一次 对整个SQL语句执行一次
适用场景 需要对每一行进行单独处理 需要对整个操作进行汇总处理
性能影响 大量数据操作时性能开销较大 性能开销相对较小
访问数据 可以访问NEW和OLD行数据 不能直接访问行数据

按触发时机分类

-- BEFORE触发器 - 在事件发生之前执行
CREATE TRIGGER before_insert_trigger
    BEFORE INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION validate_user_data();

-- AFTER触发器 - 在事件发生之后执行
CREATE TRIGGER after_insert_trigger
    AFTER INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION send_welcome_email();

-- INSTEAD OF触发器 - 替代原始操作执行(主要用于视图)
CREATE TRIGGER instead_of_insert_trigger
    INSTEAD OF INSERT ON user_view
    FOR EACH ROW
    EXECUTE FUNCTION handle_view_insert();

三种触发时机的特点:

时机 BEFORE触发器 AFTER触发器 INSTEAD OF触发器
执行时间 事件发生前 事件发生后 替代原始操作
数据修改 可以修改NEW数据 不能修改数据 实现视图的可更新性
事务控制 可以跳过原始操作 原始操作已完成 完全替代原始操作
适用场景 数据验证、默认值设置 审计日志、通知发送 可更新视图

触发器的使用场景

触发器在数据库应用中有广泛的应用场景,主要包括以下几个方面:

数据完整性约束

-- 确保员工薪资不能低于最低工资标准
CREATE OR REPLACE FUNCTION check_minimum_salary()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.salary < 1500 THEN
        RAISE EXCEPTION '薪资不能低于最低工资标准1500元';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER salary_check_trigger
    BEFORE INSERT OR UPDATE ON employees
    FOR EACH ROW
    EXECUTE FUNCTION check_minimum_salary();

审计日志记录

-- 创建审计日志表
CREATE TABLE user_audit_log (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    operation VARCHAR(10),
    old_values JSONB,
    new_values JSONB,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    user_name TEXT
);

-- 记录用户表的变更日志
CREATE OR REPLACE FUNCTION log_user_changes()
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'DELETE') THEN
        INSERT INTO user_audit_log (user_id, operation, old_values, user_name)
        VALUES (OLD.id, TG_OP, row_to_json(OLD), USER);
        RETURN OLD;
    ELSIF (TG_OP = 'INSERT') THEN
        INSERT INTO user_audit_log (user_id, operation, new_values, user_name)
        VALUES (NEW.id, TG_OP, row_to_json(NEW), USER);
        RETURN NEW;
    ELSIF (TG_OP = 'UPDATE') THEN
        INSERT INTO user_audit_log (user_id, operation, old_values, new_values, user_name)
        VALUES (NEW.id, TG_OP, row_to_json(OLD), row_to_json(NEW), USER);
        RETURN NEW;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_audit_trigger
    AFTER INSERT OR UPDATE OR DELETE ON users
    FOR EACH ROW
    EXECUTE FUNCTION log_user_changes();

数据同步

-- 在用户表插入新记录时,同步到历史表
CREATE OR REPLACE FUNCTION sync_user_to_history()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO user_history (user_id, username, email, created_at, sync_time)
    VALUES (NEW.id, NEW.username, NEW.email, NEW.created_at, CURRENT_TIMESTAMP);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER user_sync_trigger
    AFTER INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION sync_user_to_history();

复杂业务逻辑

-- 订单创建时自动更新库存
CREATE OR REPLACE FUNCTION update_inventory_on_order()
RETURNS TRIGGER AS $$
DECLARE
    product_record RECORD;
BEGIN
    -- 遍历订单项
    FOR product_record IN 
        SELECT product_id, quantity 
        FROM order_items 
        WHERE order_id = NEW.id
    LOOP
        -- 更新产品库存
        UPDATE products 
        SET stock_quantity = stock_quantity - product_record.quantity,
            last_updated = CURRENT_TIMESTAMP
        WHERE id = product_record.product_id;
        
        -- 检查库存是否足够
        IF (SELECT stock_quantity FROM products WHERE id = product_record.product_id) < 0 THEN
            RAISE EXCEPTION '产品ID % 库存不足', product_record.product_id;
        END IF;
    END LOOP;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER order_inventory_trigger
    AFTER INSERT ON orders
    FOR EACH ROW
    EXECUTE FUNCTION update_inventory_on_order();

触发器的优缺点

触发器作为一种强大的数据库特性,有其明显的优点和潜在的缺点。

优点

  • 自动化执行:无需应用程序显式调用,自动响应数据库事件
  • 数据一致性:确保在所有情况下都执行关键的业务规则
  • 集中管理:业务逻辑集中在数据库层,便于维护
  • 性能优化:在数据库服务器上执行,减少网络往返
  • 安全性:隐藏复杂逻辑,防止应用程序绕过关键约束

缺点

  • 调试困难:触发器的自动执行特性使得问题排查变得复杂
  • 性能影响:不恰当的触发器可能严重影响数据库性能
  • 维护复杂:触发器之间的依赖关系可能使维护变得困难
  • 移植性差:不同数据库系统的触发器语法和特性可能不同
  • 意外副作用:触发器可能产生意想不到的级联效应

触发器使用建议:

  1. 谨慎使用:只在确实需要自动化执行的场景下使用触发器
  2. 文档化:详细记录每个触发器的功能、触发条件和预期行为
  3. 性能测试:在生产环境部署前充分测试触发器的性能影响
  4. 错误处理:合理处理触发器中的异常,避免影响主操作
  5. 定期审查:定期审查触发器的必要性和正确性