2012-01-26 139 views
5

有时postgresql引发错误死锁。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(); 

它为什么会发生?

谢谢!

回答

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) 

So-死锁。

一种解决方案是立即之前采取对内容行独占锁插入注释。即

SELECT 1 FROM content WHERE content.id = 935967 FOR UPDATE 
INSERT INTO comment(.....) 

另一种解决方案是简单地完全避免这种“缓存数”的格局,除非你能证明它是必要的性能。如果是这样,考虑将缓存计数保持在除内容表之外的其他地方 - 例如柜台专用桌子。每次添加评论时,这也会减少内容表的更新流量。或者,也许只需重新选择计数并在应用程序中使用memcached即可。不管你存储这个缓存计数的哪个位置将成为一个瓶颈,它都必须被安全地更新。

+0

谢谢!做得好! :) – lestat

+0

我写了一些蟒蛇测试检测死锁,选择更新似乎没有帮助:( – lestat

+0

你正在做'select for update'并在同一事务中插入注释?它实际上是阻止其他进程尝试'选择更新'还是他们都要通过插入? – araqnid