在高并发写入时使用触发器防止死​​锁错误 table

Prevent Deadlock Errors with Trigger on high concurrent write table

我有一个 table,每分钟大约插入 1000 次以上。它上面有一个触发器来更新另一个 table.

上的列
 CREATE or replace FUNCTION clothing_price_update() RETURNS trigger AS $clothing_price_update$
    BEGIN
       INSERT INTO
        clothes(clothing_id, last_price, sale_date)
        VALUES(NEW.clothing_id, new.price, new."timestamp")
    ON CONFLICT (clothing_id) DO UPDATE set last_price = NEW.price, sale_date = NEW."timestamp";
        RETURN NEW;
    END;
$clothing_price_update$ LANGUAGE plpgsql;


CREATE TRIGGER clothing_price_update_trigger BEFORE INSERT OR UPDATE ON sales
    FOR EACH ROW EXECUTE PROCEDURE clothing_price_update();

但是,我随机收到死锁错误。这看起来非常简单,并且没有其他触发器在起作用。我错过了什么吗?

sales 不断向其中插入数据,但它不依赖于其他 table,并且添加数据后不会发生任何更新。

走出去,死锁的典型根本原因是写入(锁定)行的顺序在并发事务中不一致。

想象一下两个完全并发的事务:

T1:


INSERT INTO sales(clothing_id, price, timestamp) VALUES
  (1, 11, '2000-1-1')
, (2, 22, '2000-2-1');

T2:

INSERT INTO sales(clothing_id, price, timestamp) VALUES
  (2, 23, '2000-2-1')
, (1, 12, '2000-1-1');
T1 locks the row with `clothing_id = 1` in `sales` and `clothes`.

      T2 locks the row with `clothing_id = 2` in `sales` and `clothes`.

T1 waits for T2 to release locks for `clothing_id = 2`.

      T2 waits for T1 to release locks for `clothing_id = 1`.

 Deadlock.

通常情况下,死锁仍然极不可能发生,因为时间 window 是如此之窄,但是随着更大的集合/更多的并发事务/更长的事务/更昂贵的写入/增加的触发器周期(!)等等。变得更有可能。

触发器本身不是这种情况的原因(除非它引入乱序写入!),它只会增加实际发生死锁的可能性。

解决方法是在同一事务中以一致的排序顺序 插入行。最重要的是在同一个命令中。然后下一个事务将排队等待,直到第一个事务完成(COMMITROLLBACK)并释放它的锁。 The manual:

The best defense against deadlocks is generally to avoid them by being certain that all applications using a database acquire locks on multiple objects in a consistent order.

参见:

  • How to simulate deadlock in PostgreSQL?

长-运行 交易通常会增加问题。参见:

除了,你使用:

ON CONFLICT (clothing_id) DO UPDATE set last_price = NEW.price ... 

您可能想在此处使用 EXCLUDED 而不是 NEW

ON CONFLICT (clothing_id) DO UPDATE set last_price = EXCLUDED.price ... 

细微差别:这样,可能的触发器 ON INSERT 的效果会被保留,而再次粘贴 NEW 会覆盖它。相关: