触发器中禁止直接执行update,应仅登记待更新记录到队列表;队列表需精简字段、避免约束开销,record_id须建组合索引,出队用select ... for update skip locked,定期归档完成记录。

触发器里直接写 UPDATE 会卡死主事务
大规模关联更新走触发器同步执行,等于把耗时操作塞进业务事务里。一旦 UPDATE 涉及百万级数据或跨表 JOIN,事务会长时间持有行锁甚至表锁,导致上游 INSERT/UPDATE 阻塞、连接池打满、超时频发。
真正可行的路径是:触发器只做「登记」,不执行实质更新。用它把待处理的主键或批次 ID 写入一张轻量级队列表(如 update_queue),后续由独立作业消费。
- 触发器中禁止出现
UPDATE ... FROM ... JOIN或子查询含聚合/多表关联 - 队列表字段尽量精简:
id(自增)、target_table(字符串)、record_id(被影响记录主键)、status('pending'/'processing'/'done')、created_at - 插入队列表必须用
INSERT INTO update_queue (target_table, record_id) VALUES ('orders', NEW.id),避免 SELECT 或函数调用
如何让队列表不成为新瓶颈
高频写入下,update_queue 自身可能成为热点。常见错误是加唯一约束或在 record_id 上建普通索引后全表扫描查 pending 记录——这会让消费者变慢,积压加剧。
关键设计点在于分离「入队」和「出队」路径:
- 入队用
INSERT,目标字段仅含必要信息,禁用触发器、外键、FULLTEXT 等开销项 - 出队用
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+ / PostgreSQL)或带LIMIT的低隔离度查询 + 应用层乐观锁更新 status -
record_id字段必须建索引,但若存在重复提交(如重试),建议组合索引(target_table, record_id, status)加快 pending 查询 - 定期归档已完成记录(如
DELETE FROM update_queue WHERE status = 'done' AND created_at ),避免表膨胀
异步消费者怎么安全执行关联更新
消费者不是简单循环跑 UPDATE,它得应对幂等、失败回滚、批量控制三类问题。典型错误是每次只取 1 条、逐条执行——吞吐低且易超时;或一次取 10 万条全量 JOIN 更新——内存爆、事务长、锁表。
推荐分批 + 显式事务 + 结果校验的组合:
- 每次消费取
SELECT id, record_id FROM update_queue WHERE status = 'pending' ORDER BY id LIMIT 1000 FOR UPDATE SKIP LOCKED - 用这批
record_id构造临时表或 CTE,在目标表上执行UPDATE t SET t.status = 'processed' FROM target_table t INNER JOIN temp_ids i ON t.id = i.record_id - 更新后检查
ROW_COUNT()是否匹配预期数量,不一致则标记该批次为'failed'并告警,不跳过 - 成功后统一
UPDATE update_queue SET status = 'done' WHERE id IN (...),避免逐条更新队列表
PostgreSQL 和 MySQL 在这事上的关键差异
同样逻辑,在两个数据库落地时绕不开底层机制差异。忽略这些,容易在迁移或混合环境出错。
- MySQL 触发器不能调用存储过程(除非是
DEFINER权限足够且开启log_bin_trust_function_creators),而 PostgreSQL 支持在触发器函数里调用任意 SQL 函数 - MySQL 的
SKIP LOCKED仅在READ COMMITTED隔离级别生效;PostgreSQL 默认就支持,且FOR UPDATE在可重复读下也有效 - MySQL 批量 JOIN 更新语法是
UPDATE t1 INNER JOIN t2 ON ... SET t1.x = t2.y;PostgreSQL 必须用UPDATE t1 SET x = t2.y FROM t2 WHERE t1.id = t2.t1_id,少一个FROM就语法报错 - MySQL 的
INSERT IGNORE可防重复入队;PostgreSQL 得靠INSERT ... ON CONFLICT DO NOTHING,且冲突字段必须有唯一索引支撑
实际最难的不是写对那几行 SQL,而是确认每张中间表的索引是否真被查询计划用了,以及消费者进程挂掉时队列状态能否自愈。别省略 EXPLAIN ANALYZE 和模拟 kill -9 测试。










