有时postgresql会引发错误死锁。
在设置FOR FOR UPDATE的表的触发器中。
表评论:
日志(INSERT句子被剪切):
2012-01-26 17:21:06 MSK ERROR: deadlock detected
2012-01-26 17:21:06 MSK DETAIL: Process 2754 waits for ExclusiveLock on tuple (40224,15) of relation 735493 of database 734745; blocked by process 2053.
Process 2053 waits for ShareLock on transaction 25162240; blocked by process 2754.
Process 2754: INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (1756235868, 935967, 11378142, 'text1') RETURNING comment.id;
Process 2053: INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (4071267066, 935967, 11372945, 'text2') RETURNING comment.id;
2012-01-26 17:21:06 MSK HINT: See server log for query details.
2012-01-26 17:21:06 MSK CONTEXT: SQL statement "SELECT comments_count FROM content WHERE content.id = NEW.content_id FOR UPDATE"
PL/pgSQL function "increase_comment_counter" line 5 at SQL statement
2012-01-26 17:21:06 MSK STATEMENT: INSERT INTO comment (user_id, content_id, reply_id, text) VALUES (1756235868, 935967, 11378142, 'text1') RETURNING comment.id;
触发表评论:
CREATE OR REPLACE FUNCTION increase_comment_counter() RETURNS TRIGGER AS $$
DECLARE
comments_count_var INTEGER;
BEGIN
SELECT INTO comments_count_var comments_count FROM content WHERE content.id = NEW.content_id FOR UPDATE;
UPDATE content SET comments_count = comments_count_var + 1, last_comment_dt = now() WHERE content.id = NEW.content_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER increase_comment_counter_trigger AFTER INSERT ON comment FOR EACH ROW EXECUTE PROCEDURE increase_comment_counter();
为什么会发生?
谢谢!
答案 0 :(得分:10)
这两个注释是使用相同的content_id插入的。仅插入注释将在内容行上取出SHARE锁定,以便在第一个事务完成之前停止另一个事务删除该行。
然而,触发器继续将锁升级为EXCLUSIVE,这可以通过执行相同过程的并发事务来阻止。考虑以下事件序列:
Txn 2754 Txn 2053
Insert Comment
Insert Comment
Lock Content#935967 SHARE
(performed by fkey)
Lock Content#935967 SHARE
(performed by fkey)
Trigger
Lock Content#935967 EXCLUSIVE
(blocks on 2053's share lock)
Trigger
Lock Content#935967 EXCLUSIVE
(blocks on 2754's share lock)
死锁。
一种解决方案是立即在插入评论之前对内容行进行独占锁定。即。
SELECT 1 FROM content WHERE content.id = 935967 FOR UPDATE
INSERT INTO comment(.....)
另一种解决方案是完全避免这种“缓存计数”模式,除非您可以证明它对性能是必要的。如果是这样,请考虑将缓存计数保留在内容表以外的其他位置 - 例如一个专门的柜台。每次添加评论时,这也将减少内容表的更新流量。或者只是重新选择计数并在应用程序中使用memcached。无论如何存储这个缓存计数将成为一个阻塞点,它必须得到安全更新。