PostgreSQL 触发器(手把手讲解)

什么是 PostgreSQL 触发器?

如果你把数据库比作一个智能仓库,那么表就是存放货物的货架,而 PostgreSQL 触发器就像是仓库里的“自动警报系统”。当某个特定动作发生时——比如有人往货架上放货、搬走货物,或者修改了库存信息——系统会自动触发一个预设的反应,比如更新库存日志、发送通知,甚至阻止非法操作。

在 PostgreSQL 中,触发器(Trigger)是一种在特定事件发生时自动执行的数据库对象。这些事件通常包括 INSERT、UPDATE 或 DELETE 操作。你不需要手动调用它,只要数据变更发生,它就会“自动上线”。

想象一下,你每天下班前都要检查是否关了灯。但如果安装了一个智能感应器,只要有人离开办公室,它就自动关灯,是不是省心多了?这正是 PostgreSQL 触发器的作用——让数据库在关键时刻“自动反应”。

触发器的工作机制

触发器的运行流程可以拆解为三个阶段:

  1. 事件触发:用户执行 INSERT、UPDATE 或 DELETE 操作。
  2. 条件判断:触发器根据预设的条件(如某列值是否改变)决定是否执行。
  3. 执行动作:执行定义好的函数逻辑,比如记录日志、校验数据、同步其他表。

这个过程完全在数据库内部完成,对应用程序来说是“透明”的。你写代码时不用关心触发器是否存在,但它的存在让数据更安全、更一致。


如何创建一个基础的 PostgreSQL 触发器?

我们来动手创建一个最简单的触发器。假设你有一个用户表 users,每当有新用户注册时,系统希望自动记录一条“注册时间”日志。

步骤 1:创建目标表

-- 创建用户表,包含用户ID、姓名和注册时间
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

这里 SERIAL 是自增主键,NOW() 函数返回当前时间戳。

步骤 2:编写触发函数

触发器本身不直接执行逻辑,而是调用一个函数。我们先创建这个函数:

-- 创建一个函数,用于在插入用户时记录日志
CREATE OR REPLACE FUNCTION log_user_insert()
RETURNS TRIGGER AS $$
BEGIN
    -- 向日志表中插入一条记录
    -- NEW 表示即将插入的新行数据
    INSERT INTO user_logs (user_id, action, timestamp)
    VALUES (NEW.id, 'INSERT', NOW());

    -- 返回 NEW,表示允许继续执行插入操作
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

🔍 注释说明

  • CREATE OR REPLACE FUNCTION:如果函数已存在,则替换它。
  • RETURNS TRIGGER:表示这个函数是为触发器设计的。
  • NEW 是一个特殊变量,代表即将插入或更新的行数据。
  • plpgsql 是 PostgreSQL 的过程语言,类似存储过程。
  • RETURN NEW 表示让原始操作继续进行,否则会中断。

步骤 3:创建触发器

-- 创建触发器,监听 users 表的 INSERT 操作
CREATE TRIGGER trigger_user_insert
    AFTER INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION log_user_insert();

🔍 注释说明

  • AFTER INSERT:表示在插入操作之后触发。
  • FOR EACH ROW:表示每插入一行就触发一次(区别于 FOR EACH STATEMENT,后者只触发一次)。
  • EXECUTE FUNCTION:指定要执行的函数名。

现在,当你插入一条用户数据时,user_logs 表就会自动记录日志。


一个实用案例:防止误删重要数据

在生产环境中,误删数据是最常见的灾难之一。我们可以通过 PostgreSQL 触发器来实现“软删除”或“删除前确认”。

场景:禁止删除已审核的订单

假设有一个订单表 orders,其中包含 status 字段,值为 'pending' 或 'approved'。我们希望禁止删除任何已审核的订单。

步骤 1:创建订单表

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    product_name VARCHAR(200),
    status VARCHAR(50) DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT NOW()
);

步骤 2:创建触发函数

-- 创建一个函数,用于在删除前检查订单状态
CREATE OR REPLACE FUNCTION prevent_approved_delete()
RETURNS TRIGGER AS $$
BEGIN
    -- 如果要删除的订单状态是 'approved',则抛出错误
    IF OLD.status = 'approved' THEN
        RAISE EXCEPTION '无法删除已审核的订单(ID: %)', OLD.id;
    END IF;

    -- 允许删除操作继续
    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

🔍 注释说明

  • OLD 是删除前的原始行数据。
  • RAISE EXCEPTION 会中断操作并返回错误信息,阻止删除。
  • OLD.status = 'approved' 是判断条件。

步骤 3:创建触发器

-- 创建触发器,监听 orders 表的 DELETE 操作
CREATE TRIGGER trigger_prevent_delete
    BEFORE DELETE ON orders
    FOR EACH ROW
    EXECUTE FUNCTION prevent_approved_delete();

🔍 关键点:使用 BEFORE DELETE,可以在删除前做校验。如果校验失败,操作被终止。

现在,如果你尝试执行:

DELETE FROM orders WHERE id = 1 AND status = 'approved';

系统会返回错误:无法删除已审核的订单(ID: 1),保护了关键数据。


触发器的常见使用场景

场景 说明 适用触发器类型
数据审计日志 自动记录数据变更时间、操作人、旧值新值 AFTER INSERT/UPDATE/DELETE
数据一致性校验 确保某些字段值满足业务规则 BEFORE INSERT/UPDATE
多表数据同步 一个表更新,自动更新另一个表 AFTER UPDATE
软删除支持 将删除改为“标记为已删除”,保留历史数据 BEFORE DELETE
自动更新统计字段 比如更新用户评论总数 AFTER INSERT/DELETE

这些场景都体现了 PostgreSQL 触发器的强大实用性。它像一位永不离岗的“数据守卫”,在后台默默确保数据的完整性。


高级技巧:使用触发器处理复杂逻辑

有时候我们需要在触发器中执行复杂的业务逻辑,比如调用外部 API、更新多个表、或进行条件判断。

示例:插入用户后,自动创建默认角色

假设每个新用户都应被分配一个“普通用户”角色,我们可以通过触发器实现:

-- 创建角色表
CREATE TABLE user_roles (
    user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
    role_name VARCHAR(50) NOT NULL,
    PRIMARY KEY (user_id, role_name)
);

-- 创建触发函数
CREATE OR REPLACE FUNCTION assign_default_role()
RETURNS TRIGGER AS $$
BEGIN
    -- 插入默认角色 'user' 到 user_roles 表
    INSERT INTO user_roles (user_id, role_name)
    VALUES (NEW.id, 'user');

    -- 返回 NEW,允许继续插入
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER trigger_assign_role
    AFTER INSERT ON users
    FOR EACH ROW
    EXECUTE FUNCTION assign_default_role();

这个例子展示了触发器如何“自动补全”业务逻辑,减少应用层代码的复杂度。


常见问题与最佳实践

1. 触发器会影响性能吗?

是的。每次触发器执行都会增加额外开销。因此,不要滥用触发器。只在必要时使用,比如数据校验、日志记录等核心场景。

2. 如何调试触发器?

  • 使用 RAISE NOTICE '调试信息: %', variable; 输出调试信息。
  • 查看 PostgreSQL 日志文件(通常在 data/log/ 目录下)。
  • 在函数中添加 RETURN NULL; 来测试触发器是否执行。

3. 触发器能嵌套吗?

可以,但要小心。避免循环触发。比如:A 表的触发器修改 B 表,B 表的触发器又修改 A 表,可能导致死循环。

4. 如何禁用或删除触发器?

-- 禁用触发器
ALTER TABLE users DISABLE TRIGGER trigger_user_insert;

-- 启用触发器
ALTER TABLE users ENABLE TRIGGER trigger_user_insert;

-- 删除触发器
DROP TRIGGER IF EXISTS trigger_user_insert ON users;

总结

PostgreSQL 触发器是一个强大而灵活的工具,它让你的数据库不仅能“存数据”,还能“懂逻辑”。无论是数据审计、业务校验,还是自动同步,它都能帮你把重复、易错的逻辑交给数据库来处理。

掌握它,意味着你不再只是“写 SQL 的人”,而是能设计“智能数据库系统”的开发者。它像一个看不见的“数据管家”,在你不知情时,默默守护着数据的完整与安全。

下一次当你写完 INSERT 语句后,不妨想一想:是否该加一个 PostgreSQL 触发器?它可能就是你系统中最安静、最可靠的“守护者”。