锁定PostgreSQL表中的所有行
我试图在PG表中创建一种任务队列,类似于此,但有点复杂 1) 有些任务与某个锁定PostgreSQL表中的所有行,postgresql,locking,Postgresql,Locking,我试图在PG表中创建一种任务队列,类似于此,但有点复杂 1) 有些任务与某个实体id关联,当它们的实体id不同时,可以并行执行这些任务。所以他们有一张桌子: create table entity_tasks ( entity_id bigint, task text, inserted_at timestamp default now() ); 2) 有些任务必须以独占方式执行,即与所有其他任务按顺序执行。对于此类任务,还有一个表: create table block_ever
实体id
关联,当它们的实体id
不同时,可以并行执行这些任务。所以他们有一张桌子:
create table entity_tasks (
entity_id bigint,
task text,
inserted_at timestamp default now()
);
2) 有些任务必须以独占方式执行,即与所有其他任务按顺序执行。对于此类任务,还有一个表:
create table block_everything_tasks (
task TEXT,
inserted_at TIMESTAMP DEFAULT NOW()
);
从block_everything_tasks
执行任务应阻止从实体_tasks
和从block_everything_tasks
执行所有任务
在一些原型设计之后,我还添加了一个表
create table entities_for_tasks (
entity_id bigint primary key
);
每个实体获取和执行任务的工作方式如下:
begin;
select entity_id into entity_to_lock
from entities_for_tasks
for update skip locked
limit 1;
select * from entity_tasks
where entity_id = entity_to_lock
order by inserted_at
limit 1;
-- execute them and delete from the `entity_tasks`
commit;
到目前为止还不错,但是当我试图实现从块\u everything\u tasks
获取任务时,会变得很尴尬。我在这里看到了一些解决方案,但我不喜欢其中任何一个
1) 我可以显式地将整个实体锁定到\u lock
表,如下所示
begin;
lock table entity_to_lock;
select * from block_everything_tasks
order by inserted_at
limit 1;
-- execute them and delete from the `entity_tasks`
commit;
begin;
with lock as (
select * from entity_to_lock for update
)
select * from block_everything_tasks
order by inserted_at
for update skip locked
limit 1;
-- execute them and delete from the `entity_tasks`
commit;
但这将阻止将行添加到任务到实体\u到\u锁
,并可能阻止将任务添加到其中一个队列
2) 或者我可以试着做类似的事情
begin;
lock table entity_to_lock;
select * from block_everything_tasks
order by inserted_at
limit 1;
-- execute them and delete from the `entity_tasks`
commit;
begin;
with lock as (
select * from entity_to_lock for update
)
select * from block_everything_tasks
order by inserted_at
for update skip locked
limit 1;
-- execute them and delete from the `entity_tasks`
commit;
这看起来是个不错的解决方案,我不会阻止提交者,entity\u to\u lock
也不会太大,但我不会使用entity\u to\u lock
中的行,而且它们没有被锁定,所以它就是不起作用
所以我的问题是
- 是否有方法将选项(1)中的
表 这样插入仍然是可能的,实体锁定到\u lock
,是否将被锁定select*from entity\u to\u lock 哪里对于更新
- 或者有没有办法锁定选项(2)中的所有行而不实际使用这些行
- 还是我应该在这里想出别的办法
SELECT FOR UPDATE
锁定所有现有行,以防更改,但这不会同时影响INSERT
ed记录,因此无论当前正在运行什么任务,它们仍然会被拾取和处理
将任务的实体\u
表与实体\u任务
保持同步也可能会出现问题,具体取决于您填充它的方式和使用的内容;这种模式在SERIALIZABLE
以下的任何位置都容易出现竞争条件
退一步说,您确实需要解决两个截然不同的问题:创建和分配任务,以及协调任务的执行。第一个问题由基本排队机制完美处理,但试图通过重载该机制来解决第二个问题似乎是所有这些冲突的根源 因此,不要管队列,想想协调任务执行还需要什么:
x
运行”block\u everything\u tasks
的任务需要独占锁定(1),而来自entity\u tasks
的任务可以彼此共享锁定(1),但需要独占锁定(2)
实现这一点最显式的方法是通过,它允许您“锁定”具有某些应用程序特定含义的任意整数
假设没有实体的ID0
,让我们将其用于顶级“任务正在运行”锁。然后,在成功从队列中提取任务后,每个独占任务将运行:
SELECT pg_advisory_xact_lock(0);
SELECT pg_advisory_xact_lock_shared(0);
SELECT pg_advisory_xact_lock(<entity_id of selected task>);
…每个实体任务将运行:
SELECT pg_advisory_xact_lock(0);
SELECT pg_advisory_xact_lock_shared(0);
SELECT pg_advisory_xact_lock(<entity_id of selected task>);
…然后,对于独占任务:
LOCK currently_processing;
…对于每个实体的任务:
INSERT INTO currently_processing VALUES (<entity_id of selected task>);
<run the task>
DELETE FROM currently_processing WHERE entity_id = <entity_id of selected task>;
插入当前_处理值();
从当前_处理中删除实体_id=;
INSERT
s将尝试获取表上的共享锁(被独占任务阻止),并且主键上的唯一索引将导致相同ID的并发INSERT
s被阻止,直到冲突事务提交或回滚。是的,这是pg_advical_xact_lock_shared!我在寻找全局/每个实体具有某种等效rw锁的方法时忽略了它。谢谢