SQL触发器不能直接实现跨表数据一致性,只能在逻辑简单、无递归、不跨库、不依赖外部状态等前提下辅助维护;BEFORE中校验并修正NEW值最安全,AFTER更新汇总表须严格区分INSERT/UPDATE/DELETE操作类型并防递归。

SQL触发器不能直接“实现”跨表数据一致性,它只能在特定条件下辅助维护——前提是逻辑简单、无递归、不跨库、不依赖外部状态。一旦涉及并发更新、字段语义转换或事务边界模糊,一致性就会失效。
BEFORE触发器中修改NEW值是最安全的起点
跨表一致性最稳的方式,是在BEFORE INSERT或BEFORE UPDATE里校验并修正NEW行数据,而不是等写入后再去改别的表。
- 比如订单表插入时,检查
NEW.customer_id是否存在于customers表:不存在就SIGNAL SQLSTATE '45000'阻断,而不是悄悄设成默认值 - 避免在
BEFORE里查大表或做复杂JOIN,否则会拖慢主DML;用覆盖索引加速单点查询 - MySQL 8.0+ 支持
NEW.column := value赋值,但PostgreSQL需用RETURN NEW显式返回,写法不同
AFTER触发器更新汇总表必须加条件判断
像order_summary这类统计表,用AFTER INSERT/UPDATE/DELETE更新是常见做法,但极易出错。
- 必须区分操作类型:
INSERT时累加,DELETE时减去,UPDATE要对比OLD.total_amount和NEW.total_amount差值,不能直接SET total_spent = total_spent + NEW.total_amount - 如果
orders表允许UPDATE多行,触发器里的inserted可能含多条记录,得用循环或聚合(MySQL不支持游标,得靠GROUP BY子查询) - 禁止在触发器里再
UPDATE orders——哪怕只是设个状态字段,也会触发自身,造成隐式递归(MySQL报ERROR 1442)
跨表引用必须避开锁竞争和幻读
触发器里查其他表做判断时,隔离级别和锁范围稍不注意,就会导致死锁或中间态暴露。
- 不要写
SELECT COUNT(*) FROM order_summary WHERE customer_id = NEW.customer_id后才决定是否INSERT——这没加锁,高并发下可能重复插入 - 真要校验存在性,用
SELECT 1 FROM customers WHERE id = NEW.customer_id LOCK IN SHARE MODE(MySQL)或SELECT ... FOR SHARE(PG) - 避免
SELECT ... FOR UPDATE无WHERE条件,否则锁整张表;尤其别在触发器里锁汇总行后又去更新主表,顺序反了就是死锁温床
MySQL和PostgreSQL处理UPDATE多行的方式完全不同
这是最容易被忽略的兼容性断层。同一份业务逻辑,在两个系统里触发器行为可能完全相反。
- MySQL的
INSERTED和DELETED是伪表,支持JOIN和GROUP BY,可一次处理多行变更 - PostgreSQL的
NEW/OLD只是单行记录,AFTER EACH ROW触发器对每行各执行一次;想批量聚合得提前在BEFORE里用临时表或json_agg()缓存 - 若业务要求“订单状态变paid时,同步更新客户最近下单时间”,MySQL可在
AFTER UPDATE里直接UPDATE customers SET last_order_at = NOW() WHERE id IN (SELECT customer_id FROM inserted);PG必须拆成FOR EACH ROW单条更新,或改用BEFORE+TEMP TABLE
跨表一致性真正的难点不在语法,而在于你是否清楚每一行变更背后有多少隐式锁、事务边界在哪、以及下游表有没有被其他路径同时写入。触发器只是刀,握刀的手才是关键。

















