必须用 before insert or update。它在数据落盘前执行,可通过 raise exception 或 signal 中断事务,确保脏数据不写入;after insert 已无法回滚,强一致性被破坏。

触发器里用 AFTER INSERT 还是 BEFORE INSERT?
BEFORE INSERT 是唯一能真正阻止脏数据写入的时机。它在数据落盘前执行,一旦触发器抛出异常(比如 RAISE EXCEPTION),整个事务就会回滚;而 AFTER INSERT 已经写进表了,再报错也晚了——脏数据已存在,只能靠后续清理,强一致性就破了。
- PostgreSQL 中必须用
BEFORE INSERT OR UPDATE,配合RAISE EXCEPTION - MySQL 的
BEFORE INSERT可以用SIGNAL SQLSTATE '45000'中断 - SQL Server 的
INSTEAD OF INSERT也能拦截,但逻辑要自己重写插入动作,容易漏字段
校验逻辑写在触发器里,为什么常出现性能崩塌?
触发器不是“加个 if 就完事”。每次插入都执行全量校验逻辑,如果里面包含子查询、JOIN 或调用函数,会迅速拖垮吞吐量。
- 避免在触发器里查大表:比如校验用户状态时去
users表查is_active = true,不如把必要字段冗余到当前表或用缓存键值对 - 不要调用外部 API 或执行文件 I/O —— 触发器必须是纯数据库内操作
- PostgreSQL 中慎用
EXECUTE动态 SQL,解析开销高,且无法被计划器优化
示例(PostgreSQL):
CREATE OR REPLACE FUNCTION check_order_amount() RETURNS TRIGGER AS $$
BEGIN
IF NEW.amount 100 THEN
RAISE EXCEPTION 'user % exceeded order limit', NEW.user_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
第二条 SELECT COUNT(*) 就是典型隐患:每插一条都扫一遍该用户的全部订单。
MySQL 和 PostgreSQL 的触发器行为差异在哪?
最致命的不是语法,而是事务可见性。MySQL(InnoDB)中,BEFORE 触发器能看到本事务尚未提交的其他变更;PostgreSQL 则严格按语句级快照隔离,触发器里查不到同事务中前面 INSERT 的行。
- MySQL:可以依赖同事务内其他表的中间状态做校验(比如先插
temp_validations,再在订单触发器里查它) - PostgreSQL:不能这么干,必须把校验所需数据提前塞进当前行(如用
GENERATED列预计算)或改用应用层+数据库约束组合方案 - SQLite 的触发器不支持事务回滚控制,
RAISE(ABORT, ...)虽能中断,但嵌套触发器行为难预测,不建议用于强一致场景
为什么很多人用触发器校验后,还是有脏数据漏网?
根本原因在于「触发器只管 DML,不管数据来源」。以下情况它完全无感:
- 直接
COPY/LOAD DATA INFILE/pg_restore导入数据,默认跳过触发器(PostgreSQL 需显式加TRIGGERS选项,MySQL 默认禁用) - 使用
INSERT ... ON CONFLICT DO NOTHING(PostgreSQL)或INSERT IGNORE(MySQL)时,冲突行不进入触发器流程 - 应用层拼接 SQL 字符串绕过 ORM,或用批量插入(
INSERT INTO ... VALUES (...), (...))时,个别值错误可能被静默吞掉(取决于驱动和模式)
所以真要强一致,得配合同步约束:CHECK 约束防基础格式,UNIQUE 防重复,触发器只处理跨表/复杂业务逻辑——而且上线前必须用真实导入路径压测一遍。










