PostgreSQL 触发器 #

触发器概述 #

触发器是在特定事件发生时自动执行的函数。

text
┌─────────────────────────────────────────────────────────────┐
│                    触发器类型                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  触发时机:                                                  │
│  ├── BEFORE  - 事件发生前执行                               │
│  ├── AFTER   - 事件发生后执行                               │
│  └── INSTEAD OF - 替代事件执行(用于视图)                  │
│                                                             │
│  触发事件:                                                  │
│  ├── INSERT                                                 │
│  ├── UPDATE                                                 │
│  ├── DELETE                                                 │
│  └── TRUNCATE                                               │
│                                                             │
│  触发范围:                                                  │
│  ├── FOR EACH ROW  - 每行触发                               │
│  └── FOR EACH STATEMENT - 每条语句触发                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

创建触发器 #

基本触发器 #

sql
-- 创建示例表
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    salary DECIMAL(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建触发器函数
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at := CURRENT_TIMESTAMP;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER trg_employees_updated_at
    BEFORE UPDATE ON employees
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at();

-- 测试
INSERT INTO employees (name, salary) VALUES ('Alice', 80000);

SELECT * FROM employees;
-- updated_at: 2026-03-29 10:00:00

UPDATE employees SET salary = 85000 WHERE name = 'Alice';

SELECT * FROM employees;
-- updated_at: 2026-03-29 10:01:00 (自动更新)

BEFORE 触发器 #

sql
-- 数据验证触发器
CREATE OR REPLACE FUNCTION validate_salary()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.salary < 0 THEN
        RAISE EXCEPTION 'Salary cannot be negative';
    END IF;
    
    IF NEW.salary > 1000000 THEN
        RAISE EXCEPTION 'Salary exceeds maximum limit';
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

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

-- 测试
INSERT INTO employees (name, salary) VALUES ('Bob', -1000);
-- ERROR: Salary cannot be negative

AFTER 触发器 #

sql
-- 创建审计日志表
CREATE TABLE employee_audit (
    id SERIAL PRIMARY KEY,
    employee_id INTEGER,
    action VARCHAR(10),
    old_data JSONB,
    new_data JSONB,
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    changed_by VARCHAR(100) DEFAULT CURRENT_USER
);

-- 创建审计触发器
CREATE OR REPLACE FUNCTION audit_employee_changes()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO employee_audit (employee_id, action, new_data)
        VALUES (NEW.id, 'INSERT', to_jsonb(NEW));
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO employee_audit (employee_id, action, old_data, new_data)
        VALUES (NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW));
    ELSIF TG_OP = 'DELETE' THEN
        INSERT INTO employee_audit (employee_id, action, old_data)
        VALUES (OLD.id, 'DELETE', to_jsonb(OLD));
    END IF;
    
    RETURN NULL;  -- AFTER 触发器返回值被忽略
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_employees_audit
    AFTER INSERT OR UPDATE OR DELETE ON employees
    FOR EACH ROW
    EXECUTE FUNCTION audit_employee_changes();

-- 测试
INSERT INTO employees (name, salary) VALUES ('Charlie', 70000);
UPDATE employees SET salary = 75000 WHERE name = 'Charlie';
DELETE FROM employees WHERE name = 'Charlie';

SELECT * FROM employee_audit;

INSTEAD OF 触发器 #

sql
-- 创建视图
CREATE VIEW employee_view AS
SELECT 
    id,
    name,
    salary,
    salary * 12 AS annual_salary
FROM employees;

-- 创建 INSTEAD OF 触发器
CREATE OR REPLACE FUNCTION update_employee_view()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO employees (name, salary)
        VALUES (NEW.name, NEW.salary);
        RETURN NEW;
    ELSIF TG_OP = 'UPDATE' THEN
        UPDATE employees SET
            name = NEW.name,
            salary = NEW.salary
        WHERE id = OLD.id;
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        DELETE FROM employees WHERE id = OLD.id;
        RETURN OLD;
    END IF;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_employee_view
    INSTEAD OF INSERT OR UPDATE OR DELETE ON employee_view
    FOR EACH ROW
    EXECUTE FUNCTION update_employee_view();

-- 现在可以通过视图更新
INSERT INTO employee_view (name, salary) VALUES ('Diana', 60000);

触发器变量 #

text
┌─────────────────┬─────────────────────────────────────────────┐
│ 变量            │ 说明                                        │
├─────────────────┼─────────────────────────────────────────────┤
│ NEW             │ 新行的数据(INSERT/UPDATE)                │
│ OLD             │ 旧行的数据(UPDATE/DELETE)                │
│ TG_NAME         │ 触发器名称                                  │
│ TG_WHEN         │ 触发时机(BEFORE/AFTER/INSTEAD OF)        │
│ TG_OP           │ 触发操作(INSERT/UPDATE/DELETE/TRUNCATE)  │
│ TG_TABLE_NAME   │ 表名                                        │
│ TG_TABLE_SCHEMA │ 模式名                                      │
│ TG_ARGV         │ 触发器参数数组                              │
│ TG_NARGS        │ 参数数量                                    │
└─────────────────┴─────────────────────────────────────────────┘

使用触发器变量 #

sql
CREATE OR REPLACE FUNCTION log_trigger_info()
RETURNS TRIGGER AS $$
BEGIN
    RAISE NOTICE 'Trigger: %, Operation: %, Table: %', 
        TG_NAME, TG_OP, TG_TABLE_NAME;
    
    IF TG_OP = 'UPDATE' THEN
        RAISE NOTICE 'Old salary: %, New salary: %', OLD.salary, NEW.salary;
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

使用触发器参数 #

sql
CREATE OR REPLACE FUNCTION check_value_with_param()
RETURNS TRIGGER AS $$
DECLARE
    min_value DECIMAL;
BEGIN
    min_value := TG_ARGV[0]::DECIMAL;
    
    IF NEW.salary < min_value THEN
        RAISE EXCEPTION 'Salary must be at least %', min_value;
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建带参数的触发器
CREATE TRIGGER trg_employees_min_salary
    BEFORE INSERT OR UPDATE ON employees
    FOR EACH ROW
    EXECUTE FUNCTION check_value_with_param(10000);

语句级触发器 #

sql
-- 语句级触发器(每条 SQL 语句触发一次)
CREATE OR REPLACE FUNCTION log_statement()
RETURNS TRIGGER AS $$
BEGIN
    RAISE NOTICE 'Statement % executed on %', TG_OP, TG_TABLE_NAME;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_employees_statement
    AFTER INSERT OR UPDATE OR DELETE ON employees
    FOR EACH STATEMENT
    EXECUTE FUNCTION log_statement();

-- 执行批量操作
INSERT INTO employees (name, salary) VALUES
    ('User1', 50000),
    ('User2', 60000),
    ('User3', 70000);
-- 只触发一次

条件触发器 #

sql
-- 带条件的触发器
CREATE TRIGGER trg_employees_salary_change
    AFTER UPDATE ON employees
    FOR EACH ROW
    WHEN (OLD.salary IS DISTINCT FROM NEW.salary)
    EXECUTE FUNCTION audit_employee_changes();

-- 只在薪资变化时触发

常见应用场景 #

自动生成计算列 #

sql
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    quantity INTEGER,
    unit_price DECIMAL(10, 2),
    total_price DECIMAL(10, 2)
);

CREATE OR REPLACE FUNCTION calculate_total()
RETURNS TRIGGER AS $$
BEGIN
    NEW.total_price := NEW.quantity * NEW.unit_price;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_orders_calculate_total
    BEFORE INSERT OR UPDATE ON orders
    FOR EACH ROW
    EXECUTE FUNCTION calculate_total();

INSERT INTO orders (quantity, unit_price) VALUES (10, 25.00);
SELECT * FROM orders;  -- total_price 自动计算为 250.00

维护派生数据 #

sql
CREATE TABLE departments (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    employee_count INTEGER DEFAULT 0,
    total_salary DECIMAL(15, 2) DEFAULT 0
);

CREATE OR REPLACE FUNCTION update_department_stats()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        UPDATE departments SET
            employee_count = employee_count + 1,
            total_salary = total_salary + NEW.salary
        WHERE id = NEW.department_id;
    ELSIF TG_OP = 'UPDATE' THEN
        IF OLD.department_id = NEW.department_id THEN
            UPDATE departments SET
                total_salary = total_salary - OLD.salary + NEW.salary
            WHERE id = NEW.department_id;
        ELSE
            UPDATE departments SET
                employee_count = employee_count - 1,
                total_salary = total_salary - OLD.salary
            WHERE id = OLD.department_id;
            
            UPDATE departments SET
                employee_count = employee_count + 1,
                total_salary = total_salary + NEW.salary
            WHERE id = NEW.department_id;
        END IF;
    ELSIF TG_OP = 'DELETE' THEN
        UPDATE departments SET
            employee_count = employee_count - 1,
            total_salary = total_salary - OLD.salary
        WHERE id = OLD.department_id;
    END IF;
    
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

软删除实现 #

sql
ALTER TABLE employees ADD COLUMN deleted_at TIMESTAMP;
ALTER TABLE employees ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;

CREATE OR REPLACE FUNCTION soft_delete()
RETURNS TRIGGER AS $$
BEGIN
    NEW.deleted_at := CURRENT_TIMESTAMP;
    NEW.is_deleted := TRUE;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_employees_soft_delete
    BEFORE UPDATE OF is_deleted ON employees
    FOR EACH ROW
    WHEN (NEW.is_deleted = TRUE AND OLD.is_deleted = FALSE)
    EXECUTE FUNCTION soft_delete();

数据同步 #

sql
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER,
    total DECIMAL(10, 2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE order_history (
    id SERIAL PRIMARY KEY,
    order_id INTEGER,
    status VARCHAR(20),
    changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE OR REPLACE FUNCTION sync_order_history()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO order_history (order_id, status)
    VALUES (NEW.id, NEW.status);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_orders_history
    AFTER INSERT OR UPDATE OF status ON orders
    FOR EACH ROW
    EXECUTE FUNCTION sync_order_history();

管理触发器 #

查看触发器 #

sql
-- 查看表的所有触发器
SELECT 
    tgname AS trigger_name,
    proname AS function_name,
    tgenabled AS enabled
FROM pg_trigger t
JOIN pg_proc p ON t.tgfoid = p.oid
WHERE tgrelid = 'employees'::regclass;

-- 使用 psql 命令
\dS+ employees

禁用和启用触发器 #

sql
-- 禁用特定触发器
ALTER TABLE employees DISABLE TRIGGER trg_employees_audit;

-- 禁用所有触发器
ALTER TABLE employees DISABLE TRIGGER ALL;

-- 启用触发器
ALTER TABLE employees ENABLE TRIGGER trg_employees_audit;
ALTER TABLE employees ENABLE TRIGGER ALL;

-- 启用副本触发器
ALTER TABLE employees ENABLE REPLICA TRIGGER trg_employees_audit;

删除触发器 #

sql
-- 删除触发器
DROP TRIGGER trg_employees_audit ON employees;

-- 如果存在则删除
DROP TRIGGER IF EXISTS trg_employees_audit ON employees;

学习路径 #

text
高级阶段
├── 索引
├── 视图
├── 存储过程
├── 触发器(本文)
└── 事务与锁

下一步 #

掌握了触发器后,接下来学习 事务与锁,了解并发控制机制!

最后更新:2026-03-29