Sql Postgres的并发性和可序列化性。我需要一个可序列化的隔离级别吗?
我有一个项目和作业表: 项目Sql Postgres的并发性和可序列化性。我需要一个可序列化的隔离级别吗?,sql,postgresql,transactions,Sql,Postgresql,Transactions,我有一个项目和作业表: 项目 id=PK job_id=Jobs FK 状态=进行中|完成 工作 id=PK 项目开始时是进行中的,但对其执行工作,然后交给工作人员进行更新。我有一个更新程序进程,在项目进入时用新状态更新项目。到目前为止,我所采用的方法是(伪代码): 这有意义吗?我希望它在并发环境中工作。我现在的问题是,对于一个作业,COMPLETE是多次到达的,而我只想对COMPLETE执行一次逻辑 如果将事务级别更改为SERIALIZABLE,则会出现如下所述的“错误:由于事务之间的
- id=PK
- job_id=Jobs FK
- 状态=进行中|完成
- id=PK
- 我需要序列化吗
- 我可以选择更新吗?在哪里
- 有人能给我解释一下发生了什么,为什么
编辑:我重新打开了这个问题,因为我对前面的答案不满意。有人能为我解释一下吗?具体地说,我需要一些关于伪代码的示例查询。您可以在
项目和作业上使用选择更新,并在单个事务中处理两个表中受影响的行。这应该足以强制执行整个操作的完整性,而不会产生SERIALIZABLE
或表锁的开销
我建议您创建一个函数,在items
表上进行插入或更新后调用该函数,传递该项的主键:
CREATE FUNCTION process_item(item integer) RETURNS void AS $$
DECLARE
item items%ROWTYPE;
job jobs%ROWTYPE;
BEGIN -- Implicitly starting a transaction
SELECT * INTO job FROM jobs
WHERE id = (SELECT job_id FROM items WHERE id = item)
FOR UPDATE; -- Lock the row for other users
FOR item IN SELECT * FROM items FOR UPDATE LOOP -- Rows locked
-- Work on items individually
UPDATE items
SET status = 'COMPLETED'
WHERE id = item.id;
END LOOP;
-- Do any work on the job itself
END; -- Implicitly close the transaction, releasing the locks
$$ LANGUAGE plpgsql;
如果某个其他进程已在该作业或其任何关联项上工作,则执行将停止,直到释放该其他锁为止。这与SERIALIZABLE
不同,后者将一直工作到失败,然后您必须在第二次尝试中重新执行所有处理。如果您希望作业能够同时运行,则SERIALIZABLE
和SELECT FOR UPDATE
都不会直接工作
如果使用SELECT FOR UPDATE
锁定行,则另一个进程将在执行SELECT FOR UPDATE
时简单地阻塞,直到第一个进程提交事务
如果执行SERIALIZABLE
,则两个进程可以同时运行(处理同一行),但在执行COMMIT
时,至少应该有一个进程失败,因为数据库将检测到冲突。此外,如果与数据库中同时进行的影响相关行的任何其他查询发生冲突,则可序列化的可能会失败。使用SERIALIZABLE
的真正原因正是为了防止其他作业同时进行数据库更新,而不是阻止同一作业执行两次
注意,有一些技巧可以让选择更新跳过锁定行。如果您这样做,那么您就可以拥有实际的并发性。看
我经常看到的另一种方法是将“status”列更改为第三个临时状态,在处理作业时使用该临时状态。通常情况下,会有“待定”、“进行中”、“完成”等状态。当您的流程搜索要做的工作时,它会找到一个“挂起”作业,立即将其移动到“进行中”并提交事务,然后继续工作,最后将其移动到“完成”。缺点是,如果进程在处理作业时死亡,它将无限期地留在“进行中”。谢谢,这是一个非常好的答案。因此,在连接上选择更新将锁定我需要的所有内容?除非您明确指出要在哪些表上应用锁,否则查询中的所有表都将锁定所有受影响的行。
CREATE FUNCTION process_item(item integer) RETURNS void AS $$
DECLARE
item items%ROWTYPE;
job jobs%ROWTYPE;
BEGIN -- Implicitly starting a transaction
SELECT * INTO job FROM jobs
WHERE id = (SELECT job_id FROM items WHERE id = item)
FOR UPDATE; -- Lock the row for other users
FOR item IN SELECT * FROM items FOR UPDATE LOOP -- Rows locked
-- Work on items individually
UPDATE items
SET status = 'COMPLETED'
WHERE id = item.id;
END LOOP;
-- Do any work on the job itself
END; -- Implicitly close the transaction, releasing the locks
$$ LANGUAGE plpgsql;