Eth*_*han 5 postgresql deadlock transactions
在我们使用Postgres工作的许多事情中,我们将其用作某些类型的远程请求的缓存.我们的架构是:
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);
Run Code Online (Sandbox Code Playgroud)
此表没有触发器或外键.更新通常是:
INSERT INTO cache (key, value, ttl)
VALUES ('Ethan is testing8393645', '"hi6286166"', sec2ttl(300))
ON CONFLICT (key) DO UPDATE
SET value = '"hi6286166"', ttl = sec2ttl(300);
Run Code Online (Sandbox Code Playgroud)
(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;
Run Code Online (Sandbox Code Playgroud)
查询缓存是在这样的事务中完成的:
BEGIN;
DELETE FROM cache WHERE ttl IS NOT NULL AND now() > ttl;
SELECT value FROM cache WHERE key = 'Ethan is testing6460437';
COMMIT;
Run Code Online (Sandbox Code Playgroud)
关于这个设计有一些不喜欢的东西 - DELETE在缓存"读取"中发生,索引(编辑:ASC是默认值,感谢wargre!)加上事实我们将Postgres用作缓存.但是所有这些都是可以接受的,除了我们已经开始在生产中遇到死锁,这往往看起来像这样:cache.ttl不是提升,这使得它变得无用,
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;']
Run Code Online (Sandbox Code Playgroud)
更彻底地调查日志表明两个事务都在执行此DELETE操作.
据我所知:
READ COMMITTED隔离模式.EXPLAIN查询的输出,ShareLocks应该被DELETE物理顺序的两个事务抓取.如果一切正确,那么某种同时的事务已经改变了行的物理顺序.我看到一个UPDATE可以将行移动到更早或更晚的物理位置,但在我的应用程序中,UPDATEs总是从s中删除行DELETE(因为它们总是扩展行的TTL).如果这些行之前是物理顺序,并且您删除了一行,那么您仍然可以保留物理顺序.同样的DELETE.我们没有做任何VACUUM您可能希望重新排序行的操作.
基于在执行批量更新和删除操作时避免PostgreSQL死锁,我尝试将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;
Run Code Online (Sandbox Code Playgroud)
但是,我仍然可以在本地获得死锁.那么一般来说,两个DELETE查询怎么会死锁?是因为他们锁定了未定义的订单,如果是这样,我该如何强制执行特定订单?
您应该忽略过期的缓存条目,这样您就不会依赖频繁的删除操作来实现缓存过期:
SELECT value
FROM cache
WHERE
key = 'Ethan is testing6460437'
and (ttl is null or ttl<now());
Run Code Online (Sandbox Code Playgroud)
还有另一个作业定期选择键来删除,跳过已经锁定的键,这必须强制删除行的明确定义的顺序,或者更好的是,跳过已经锁定的更新行:
with delete_keys as (
select key from cache
where
ttl is not null
and now()>ttl
for update skip locked
)
delete from cache
where key in (select key from delete_keys);
Run Code Online (Sandbox Code Playgroud)
如果您无法定期安排此操作,则应该每运行 1000 次选择查询就随机运行一次此清理操作,如下所示:
create or replace function delete_expired_cache()
returns void
language sql
as $$
with delete_keys as (
select key from cache
where
ttl is not null
and now()>ttl
for update skip locked
)
delete from cache
where key in (select key from delete_keys);
$$;
SELECT value
FROM cache
WHERE
key = 'Ethan is testing6460437'
and (ttl is null or ttl<now());
select delete_expired_cache() where random()<0.001;
Run Code Online (Sandbox Code Playgroud)
您应该避免写入,因为它们很昂贵。不要那么频繁地删除缓存。
另外,您应该使用timestamp with time zonetype (或timestamptz简称)而不是 simple timestamp- 特别是如果您不知道为什么 - atimestamp并不是大多数人认为的那样 - 归咎于 SQL 标准。
| 归档时间: |
|
| 查看次数: |
1103 次 |
| 最近记录: |