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

Posted

技术标签:

【中文标题】在高并发写入表上使用触发器防止死​​锁错误【英文标题】:Prevent Deadlock Errors with Trigger on high concurrent write table 【发布时间】:2021-10-23 11:33:31 【问题描述】:

我有一张每分钟插入约 1000 次以上的表。它上面有一个触发器来更新另一个表上的列。

 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 不断向其中插入数据,但它不依赖其他表,并且在添加数据后不会发生更新。

【问题讨论】:

一字不差的错误信息、您的 Postgres 版本和表 clothes 的定义(CREATE TABLE 语句)将使这个问题更有意义。此外,可能最重要的是:在同一 INSERT INTO sales ... 命令(或同一事务)中插入了多少行,以及以什么排序顺序 那么你有答案了吗? 【参考方案1】:

陷入困境,死锁的典型根本原因是并发事务之间写入(锁定)行的顺序不一致。

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

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.

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

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

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

防止死锁的最佳方法通常是通过以下方式避免死锁 确保所有使用数据库的应用程序都获得锁定 多个对象以一致的顺序。

见:

How to simulate deadlock in PostgreSQL?

长时间运行的事务通常会增加问题。见:

Table Locking 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 会覆盖它。相关:

How to UPSERT multiple rows with individual values in one statement?

【讨论】:

以上是关于在高并发写入表上使用触发器防止死​​锁错误的主要内容,如果未能解决你的问题,请参考以下文章

求你了,别在高并发场景中使用悲观锁了!

Java开发两年:java写入txt换行

Mysql在高并发情况下,防止库存超卖而小于0的解决方案

MQ在高并发环境下,如果队列满了,如何防止消息丢失?

高并发下,HashMap会产生哪些问题?

锁定机制和数据并发管理(笔记)