必须用 BEFORE 触发器,AFTER 无法阻止修改——数据已写入;BEFORE 可在落盘前校验、修改 NEW 值或 RAISE EXCEPTION 中断事务,而 AFTER 仅能响应结果且失败会导致整个事务回滚。

必须用 BEFORE 触发器,AFTER 触发器无法阻止修改——数据已经写入,报错也晚了。
触发器类型和执行时机必须是 BEFORE
PostgreSQL 的 AFTER 触发器在语句执行完成、数据落盘后才运行,此时再 RAISE EXCEPTION 只会让事务回滚触发器自身逻辑,原 UPDATE 或 INSERT 已生效。只有 BEFORE 触发器能在数据写入前拦截。
- 必须声明为
FOR EACH ROW,否则拿不到单行的修改意图 - 不能用
INSTEAD OF——它只支持视图,不适用于普通表 - 函数返回
NULL可中止操作,但更明确的做法是直接RAISE EXCEPTION
时间判断要用 CURRENT_TIME 和 EXTRACT(DOW FROM ...)
别用 NOW() 或 CURRENT_TIMESTAMP 做小时比对:它们带日期和时区,容易因时区转换出错或隐式类型转换失败。PostgreSQL 推荐用 CURRENT_TIME(TIME WITHOUT TIME ZONE 类型)做时间段匹配,用 EXTRACT(DOW FROM CURRENT_TIMESTAMP) 判断星期几(0=周日,1=周一,…,6=周六)。
- 工作日条件:
EXTRACT(DOW FROM CURRENT_TIMESTAMP) NOT IN (0, 6)(排除周日、周六) - 工作时间条件:
CURRENT_TIME BETWEEN '09:00' AND '17:59'(字符串格式必须严格匹配TIME类型) - 如果数据库时区不是业务所在地时区(比如服务器在 UTC,业务按北京时间),得先用
AT TIME ZONE转换:(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Shanghai')::TIME
触发器函数里禁止查表或调用含查询的函数
触发器运行在目标表 DML 事务上下文中,任何对本表或其它表的 SELECT 都可能引发 42P01(表不存在)或更常见的 40001(序列化失败),尤其在并发更新时。ORA-04091 类错误虽是 Oracle 术语,但在 PostgreSQL 中类似逻辑会触发 42883 或死锁。
- 所有时间判断必须基于内置函数,不能查配置表获取“工作时间区间”
- 不要在触发器里调用自定义函数,除非该函数被明确定义为
STABLE且不含任何SELECT - 避免使用
pg_sleep()、http_get()等耗时或 I/O 操作,会拖垮整个事务
示例:一个可直接部署的 BEFORE UPDATE 触发器
以下代码限制对 financial_records 表的 UPDATE,仅允许周一至周五 9:00–17:59:
CREATE OR REPLACE FUNCTION check_work_hours()
RETURNS TRIGGER AS $$
BEGIN
IF EXTRACT(DOW FROM CURRENT_TIMESTAMP) IN (0, 6)
OR CURRENT_TIME < '09:00'::TIME
OR CURRENT_TIME > '17:59'::TIME
THEN
RAISE EXCEPTION 'Updates to financial_records are only allowed Mon–Fri 09:00–17:59';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_financial_work_hours
BEFORE UPDATE ON financial_records
FOR EACH ROW EXECUTE FUNCTION check_work_hours();
注意:如果还要拦 INSERT 或 DELETE,需额外绑定对应事件,不能复用同一个触发器定义——PostgreSQL 不支持多事件共用一个 TRIGGER 定义。
最易被忽略的是时区一致性:CURRENT_TIME 默认用数据库 timezone 参数,而该参数可能被会话级 SET timezone = 'UTC' 覆盖。上线前务必用 SHOW timezone; 和真实连接验证结果。

















