在高并发写入表上使用触发器防止死锁错误
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.
通常情况下,死锁仍然极不可能,因为时间窗口是如此狭窄,但随着更大的集合/更多的并发事务/更长的事务/更昂贵的写入/为触发器(!)增加周期等,它变得更有可能。
在这种情况下,触发器本身并不是原因(除非它引入了乱序写入!),它只会增加实际发生死锁的可能性。
解决方法是在同一事务中以一致的排序顺序插入行。最重要的是在同一命令中。然后下一个事务将排队等待,直到第一个事务完成(COMMIT
或ROLLBACK
)并释放其锁。 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
会覆盖它。相关:
【讨论】:
以上是关于在高并发写入表上使用触发器防止死锁错误的主要内容,如果未能解决你的问题,请参考以下文章