You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

高并发写入表使用触发器时如何避免死锁错误

死锁问题原因及解决方案

核心原因

你遇到的死锁是高并发下多事务无序获取clothes表行锁导致的循环等待,和触发器逻辑本身复杂度无关:
PostgreSQL的行锁会持有到事务提交为止,当两个及以上并发事务同时按不同顺序修改clothes表的不同行时,就会触发死锁:

  • 事务A先插入了clothing_id=1的sales记录,持有clothes表id=1的行锁,接下来要修改id=2的行
  • 事务B先插入了clothing_id=2的sales记录,持有clothes表id=2的行锁,接下来要修改id=1的行
    两个事务互相等待对方释放锁,就会触发死锁错误。

可行解决方案

  • 方案1:统一锁获取顺序(最优,从根源解决)
    如果你的业务存在单事务批量插入多条sales记录的场景,插入前先把所有待插入的sales数据按clothing_id做固定顺序(全升序或全降序)排序后再执行插入,保证所有事务都是按相同顺序获取clothes表的行锁,破坏死锁的循环等待条件。
  • 方案2:改行级触发器为语句级批量处理
    如果你使用的是PostgreSQL 10及以上版本,可以把行级触发器替换为语句级触发器,通过过渡表获取本次语句插入的所有数据,去重并按clothing_id排序后再批量UPSERT到clothes表,既大幅减少了锁竞争的频率,也能保证锁获取顺序一致。
    参考代码:
    CREATE OR REPLACE FUNCTION clothing_price_update_batch() RETURNS trigger AS $clothing_price_update_batch$
    BEGIN
      INSERT INTO clothes(clothing_id, last_price, sale_date)
      SELECT DISTINCT ON (clothing_id) clothing_id, price, "timestamp"
      FROM new_rows
      ORDER BY clothing_id, "timestamp" DESC -- 取同id最新的价格
      ON CONFLICT (clothing_id) DO UPDATE 
      SET last_price = EXCLUDED.last_price, sale_date = EXCLUDED.sale_date;
      RETURN NULL;
    END;
    $clothing_price_update_batch$ LANGUAGE plpgsql;
    
    CREATE TRIGGER clothing_price_update_batch_trigger AFTER INSERT ON sales
    REFERENCING NEW TABLE AS new_rows
    FOR EACH STATEMENT EXECUTE PROCEDURE clothing_price_update_batch();
    
  • 方案3:增加死锁重试逻辑
    如果都是单条插入的小事务,死锁概率极低的场景,可以在业务代码中捕获数据库死锁异常,对失败的插入操作做1-3次自动重试即可,PostgreSQL触发死锁时会自动回滚其中一个事务,重试后一般都能执行成功。

额外优化

因为你提到sales表数据插入完成后不会再执行更新操作,你可以把原有触发器的监听事件从BEFORE INSERT OR UPDATE改成AFTER INSERT,去掉不需要的UPDATE监听,减少不必要的触发器触发次数,进一步降低锁冲突概率。

内容的提问来源于stack exchange,提问作者AlicSt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 12:48:01