触发器中禁止对主表执行select ... for update或update主键行,否则极易引发死锁;应改用原子update语句(如set total = total + value)或聚合查询(如sum)避免加锁。

触发器里不能用 SELECT ... FOR UPDATE 去锁主表
直接在触发器中对主表做 SELECT ... FOR UPDATE 或 UPDATE 主键行,极大概率引发死锁——因为触发器本身已处于事务上下文中,而主表更新又会再次申请行锁。MySQL 5.7+ 和 PostgreSQL 都明确禁止在触发器内显式加锁主表行。
实操建议:
- 改用聚合查询计算子表总和,不加锁:例如
SELECT SUM(amount) FROM order_items WHERE order_id = NEW.order_id - 避免在触发器里调用存储过程或函数,除非该函数只读且无副作用
- PostgreSQL 中若用
AFTER INSERT OR UPDATE触发器,必须用PER ROW模式,且更新主表时确保 WHERE 条件精确命中单行(如WHERE id = NEW.order_id)
MySQL 的 BEFORE INSERT/UPDATE 触发器无法读取刚插入的子记录
在 BEFORE INSERT 触发器中,NEW.order_id 还没写入主表,子表也尚未插入;此时若想预填主表的 total_amount,只能靠应用层传值,或改用 AFTER 触发器 + 显式 UPDATE。
常见错误现象:Subquery returns more than 1 row 或 Unknown column 'NEW.order_id' in field list,多因误在 BEFORE 中引用了未提交的关联数据。
实操建议:
- 主表汇总字段统一用
AFTER INSERT, AFTER UPDATE, AFTER DELETE三类触发器维护 - 子表触发器中,用
COALESCE(SUM(amount), 0)避免SUM返回NULL导致主表字段被设为NULL - MySQL 8.0+ 可考虑用物化视图替代(但需手动刷新),或迁移到支持声明式引用完整性的数据库(如 PostgreSQL)
PostgreSQL 中触发器函数必须 RETURNS TRIGGER
写完函数体后忘记声明返回类型,或返回了 NULL,会导致触发器静默失效——语句执行成功,但主表字段没更新,且无报错提示。
实操建议:
- 函数定义必须以
RETURNS TRIGGER AS $$ ... $$ LANGUAGE plpgsql;结尾 - 函数末尾必须有
RETURN NEW;(BEFORE)或RETURN NULL;(AFTER) - 更新主表前先查是否存在对应记录:
IF NOT EXISTS (SELECT 1 FROM orders WHERE id = NEW.order_id) THEN RETURN NULL; END IF;,防止子表脏数据引发异常
并发更新时 total 字段可能丢失更新
两个事务同时更新同一订单的子项,各自读取旧 total、各自加增量、各自写回,结果只有一方生效——这是典型的“读-改-写”竞态,触发器本身不提供原子性保障。
性能与兼容性影响:加 SELECT ... FOR UPDATE 能解决,但会降低并发度;用 UPDATE orders SET total = total + NEW.amount WHERE id = NEW.order_id 原子更新更稳妥。
实操建议:
- 优先使用
UPDATE ... SET field = field + value形式,而非先SELECT再UPDATE - 避免在触发器中处理跨多行的复杂逻辑(如分摊费用),这类应移出到应用层或定时任务
- 上线前务必用
sysbench或自建脚本压测高并发子表插入场景,观察主表total是否出现负数或跳变
LOAD DATA INFILE 或 COPY)可能绕过行级触发器,导致汇总字段彻底失准——这种场景下,必须额外加校验脚本或改用物化视图+刷新机制。










