我有一个项目和工作表:
项
工作
项目以IN_PROGRESS开头,但是对它们执行工作,并交给工作人员进行更新。我有一个更新程序进程,当它们进入时会更新项目,并具有新状态。到目前为止我一直在做的方法是(伪代码):
def work(item: Item) = {
insideTransaction {
updateItemWithNewStatus(item)
jobs, items = getParentJobAndAllItems(item)
newJobStatus = computeParentJobStatus(jobs, items)
// do some stuff depending on newJobStatus
}
}
这有意义吗?我希望这在并发环境中工作。我现在遇到的问题是,COMPLETE多次到达作业,当我只想在COMPLETE上执行一次逻辑时。
如果我将事务级别更改为SERIALIZABLE,我会得到"错误:由于事务之间的读/写依赖关系而无法序列化访问#34;如上所述的错误。
所以我的问题是:
编辑:我重新打开了这个问题,因为我对以前的答案解释不满意。有人能为我解释一下吗?具体来说,我想要一些针对该伪代码的示例查询。
答案 0 :(得分:4)
您可以在SELECT FOR UPDATE
和items
上使用jobs
,并在单个事务中处理两个表中受影响的行。这应该足以强制执行整个操作的完整性,而不会产生SERIALIZABLE
或表锁的开销。
我建议你创建一个在items
表上插入或更新后调用的函数,传递项目的PK:
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
不同,{{1}}在失败之前会起作用,然后你必须在第二次尝试时重新执行所有处理。
答案 1 :(得分:2)
如果您希望作业能够同时运行,则SERIALIZABLE
和SELECT FOR UPDATE
都不会直接运作。
如果使用SELECT FOR UPDATE
锁定行,则另一个进程只会在执行SELECT FOR UPDATE
时阻塞,直到第一个进程提交事务为止。
如果你执行SERIALIZABLE
,两个进程可以同时运行(处理同一行)但是至少有一个进程应该在它执行COMMIT
时失败,因为数据库将检测到冲突。如果SERIALIZABLE
与影响相关行的同时在数据库中发生的任何其他查询冲突,则SERIALIZABLE
也可能失败。使用SELECT FOR UPDATE
的真正原因恰恰在于您试图防止其他作业进行的并发数据库更新,而不是阻止同一作业执行两次。
请注意,有一些技巧可以使.equals()
跳过锁定的行。如果你这样做,那么你就可以拥有实际的并发性。请参阅{{3}}。
我经常看到的另一种方法是改变你的状态"列具有在处理作业时使用的第3临时状态。通常情况下,人们可以拥有像“待定”,“过度”,“完成”这样的状态。当您的流程搜索要执行的工作时,会找到“待处理”工作。工作,立即将其移至' IN_PROGRESS'并提交交易,然后继续工作,最后将其移至“完成”。缺点是,如果处理过程在处理作业时死亡,它将留在' IN_PROGRESS'下去。