在我们使用Postgres工作的许多事情中,我们使用它作为某种远程请求的缓存。我们的模式是:Postgres中两个DELETE查询如何死锁?
CREATE TABLE IF NOT EXISTS cache (
key VARCHAR(256) PRIMARY KEY,
value TEXT NOT NULL,
ttl TIMESTAMP DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS idx_cache_ttl ON cache(ttl);
此表没有触发器或外键。更新通常是:
INSERT INTO cache (key, value, ttl)
VALUES ('Ethan is testing8393645', '"hi6286166"', sec2ttl(300))
ON CONFLICT (key) DO UPDATE
SET value = '"hi6286166"', ttl = sec2ttl(300);
(凡sec2ttl
被定义为:)
CREATE OR REPLACE FUNCTION sec2ttl(seconds FLOAT)
RETURNS TIMESTAMP AS $$
BEGIN
IF seconds IS NULL THEN
RETURN NULL;
END IF;
RETURN now() + (seconds || ' SECOND')::INTERVAL;
END;
$$ LANGUAGE plpgsql;
查询缓存在一个事务中做过这样的:
BEGIN;
DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;
SELECT value FROM cache WHERE key = 'Ethan is testing6460437';
COMMIT;
有几件事情不喜欢这个设计 - DELETE
发生在缓存“读取”,(编辑:ASC是默认值,谢谢wargre!)以及我们使用Postgres作为缓存的事实。但所有这一切本来是可以接受的,除了我们已经开始让生产死锁,这往往是这样的:cache.ttl
上的索引不是升序,这使得它有用ss,
ERROR: deadlock detected
DETAIL: Process 12750 waits for ShareLock on transaction 632693475; blocked by process 10080.
Process 10080 waits for ShareLock on transaction 632693479; blocked by process 12750.
HINT: See server log for query details.
CONTEXT: while deleting tuple (426,1) in relation "cache"
[SQL: 'DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;']
更彻底地调查日志表明,交易双方均执行此操作DELETE
。
至于我可以告诉大家:
- 我的交易是在
READ COMMITTED
隔离模式。 - ShareLock被一个事务抓取,表明它想要改变另一个事务已经发生变化(即锁定)的行。
- 根据
EXPLAIN
查询的输出结果,ShareLocks应该被物理顺序的DELETE
事务抓取。 - 死锁表明两个查询以不同的顺序锁定行。
如果这一切都是正确的,那么某种同时发生的事务已经改变了行的物理顺序。我发现UPDATE
可以将一行移动到更早或更晚的物理位置,但在我的应用程序中,UPDATE
总是从DELETE
s中删除行(因为它们总是延长行的TTL)。如果这些行以前是按照物理顺序排列的,并且您删除了一个,那么您仍然保持物理顺序。同样为DELETE
。我们没有执行任何VACUUM
或您可能期望对行重新排序的任何其他操作。
基于Avoiding PostgreSQL deadlocks when performing bulk update and delete operations,我试图DELETE
查询更改为:
DELETE FROM cache c
USING (
SELECT key
FROM cache
WHERE ttl IS NOT NULL AND now() > ttl
ORDER BY ttl ASC
FOR UPDATE
) del
WHERE del.key = c.key;
不过,我仍然能够获得本地死锁。那么一般来说,两个DELETE
查询怎么会死锁呢?是因为他们以未定义的顺序锁定,如果是这样,我如何执行特定的订单?
如何在自动提交和无事务的连接中管理缓存? (顺便说一下,索引是正确的,它是由你的dlete使用的) – wargre
我想即使我没有交易,Postgres围绕每一个查询创建一个交易,对吧? (顺便说一句,谢谢!我看到ASC是索引的默认值。) – Ethan
“SELECT ... FOR UPDATE”的意义在于为所有锁定采集实施全局一致的顺序。如果两个“ttl”值一致,那么您的行排序是未定义的,并且如果更新了“ttl”值,那么在明确定义的情况下,排序可能会在并发事务之间有所不同。改为使用'ORDER BY key'。 –