mysql触发器中禁止对同一表执行select ... for update,须用new/old增量更新;postgresql允许但需防mvcc可见性问题;sql server宜用instead of触发器;所有数据库都应避免读-改-写模式以防并发丢失。

触发器里不能用 SELECT ... FOR UPDATE 或事务嵌套
MySQL 的 BEFORE INSERT 或 AFTER INSERT 触发器中,如果尝试在触发器内对**同一张表**做 SELECT ... FOR UPDATE,会直接报错 ERROR 1442 (HY000): Can't update table 'xxx' in stored function/trigger because it is already used by statement which invoked this stored function/trigger。这是 MySQL 的硬性限制,不是权限或隔离级别问题。
解决思路是:避开对主表的加锁读取,改用聚合函数直接计算,或把汇总逻辑移到应用层/定时任务——但若坚持用触发器,必须确保只读取「当前操作行」相关数据,不查整表。
-
AFTER INSERT中,用NEW获取刚插入的值,累加到主表字段(如UPDATE summary_table SET total = total + NEW.amount WHERE id = NEW.order_id) -
AFTER DELETE中,用OLD做反向减法 -
AFTER UPDATE中,需同时处理OLD.amount和NEW.amount的差值:SET delta = NEW.amount - OLD.amount,再更新汇总字段
PostgreSQL 的触发器支持更灵活的查询,但要注意 MVCC 可见性
PostgreSQL 允许在 BEFORE 或 AFTER 触发器中执行任意 SELECT,包括对本表的聚合查询。但容易踩的坑是:触发器执行时,当前事务尚未提交,其他并发事务看不到本次修改,而本触发器看到的仍是旧快照——这会导致 SUM() 结果滞后于实际最新状态。
例如,两个并发 INSERT 同时触发,各自算出的 SUM(amount) 都不含对方的数据,最终汇总值比真实值少一条记录。
- 安全做法是:只依赖
NEW/OLD字段做增量更新,避免全表扫描 - 若必须用聚合(比如重算),应放在
AFTER触发器,并接受短暂不一致;或改用DEFERRABLE约束配合事务级汇总 - 注意
pg_trigger_depth()防止递归触发(比如汇总更新又触发自身)
SQL Server 的 INSTEAD OF 触发器更适合汇总场景
SQL Server 的 INSTEAD OF 触发器可以拦截原始 DML,先更新汇总表,再执行原操作,天然规避了“读自己未提交数据”的问题。相比 AFTER,它更可控。
典型写法是:在 INSTEAD OF INSERT 中,先 UPDATE summary SET total += (SELECT SUM(i.amount) FROM inserted i),再 INSERT INTO detail SELECT * FROM inserted。
- 必须显式写出原 DML 语句,否则数据不会真正插入
-
inserted和deleted伪表能批量处理多行,性能优于逐行触发 - 注意
@@ROWCOUNT在触发器内反映的是伪表行数,不是最终影响行数
所有数据库都绕不开的并发更新丢失问题
即使语法合法、逻辑正确,多个触发器并发执行 UPDATE summary SET total = total + X 仍可能丢失更新——因为读取 total 和写回是两个步骤,中间有竞态窗口。
根本解法不是加锁(触发器里难控制),而是用原子操作:
- MySQL:用
UPDATE summary SET total = total + ? WHERE id = ?,让引擎自己保证原子性 - PostgreSQL:同样用
+=(即total = total + ?),它底层是原子的 - SQL Server:推荐
UPDATE ... SET total += ?,比先SELECT再UPDATE安全
别在触发器里写 SELECT total FROM ... 再算新值——那是教科书级的竞态源头。










