在高并发写入时使用触发器防止死锁错误 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 是如此之窄,但是随着更大的集合/更多的并发事务/更长的事务/更昂贵的写入/增加的触发器周期(!)等等。变得更有可能。
触发器本身不是这种情况的原因(除非它引入乱序写入!),它只会增加实际发生死锁的可能性。
解决方法是在同一事务中以一致的排序顺序 插入行。最重要的是在同一个命令中。然后下一个事务将排队等待,直到第一个事务完成(COMMIT
或 ROLLBACK
)并释放它的锁。 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
会覆盖它。相关:
我有一个 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 是如此之窄,但是随着更大的集合/更多的并发事务/更长的事务/更昂贵的写入/增加的触发器周期(!)等等。变得更有可能。
触发器本身不是这种情况的原因(除非它引入乱序写入!),它只会增加实际发生死锁的可能性。
解决方法是在同一事务中以一致的排序顺序 插入行。最重要的是在同一个命令中。然后下一个事务将排队等待,直到第一个事务完成(COMMIT
或 ROLLBACK
)并释放它的锁。 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
会覆盖它。相关: