PostgreSQL死锁

7
有时候postgresql会出现死锁错误。
在表的触发器中设置了FOR UPDATE。
表注释:

http://pastebin.com/L1a8dbn4

日志(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();

为什么会发生这种情况?
谢谢!
1个回答

12

这是两个具有相同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。无论您在哪里存储此缓存计数,都必须安全更新,这是不可避免的瓶颈。


我编写了一些Python测试来检测死锁,但使用select for update似乎没有帮助 :( - lestat
你是在同一个事务中执行select for update和插入评论吗?它实际上会阻塞另一个尝试执行select for update的进程吗,还是它们都能顺利进行插入操作? - araqnid
是的,在脚本中不会执行更新操作,因为更新操作需要触发器“increase_comment_counter”: SELECT INTO comments_count_var comments_count FROM content WHERE content.id = NEW.content_id FOR UPDATE; - lestat
我在插入后触发,但是我如何重写带锁的触发器?谢谢! - lestat
@lestat 你的选项是:a)在插入之前手动选择行(就像我在代码片段中建议的那样),b)编写一个额外的 before insert 触发器来自动执行锁定。 - araqnid
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接