繁体   English   中英

Postgres并发性和可序列化。 我需要SERIALIZABLE隔离级别吗?

[英]Postgres concurrency and serializability. Do I need a SERIALIZABLE isolation level?

我有一个项目和工作表:

项目

  • id = PK
  • job_id =乔布斯FK
  • status = IN_PROGRESS | 完成

工作

  • id = PK

项目以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,我会收到“错误:由于事务之间的读/写依赖性而无法序列化访问”错误,如上所述。

所以我的问题是:

  • 我需要SERIALIZABLE吗?
  • 我可以逃脱SELECT FOR UPDATE,在哪里?
  • 有人可以向我解释发生了什么,为什么?

编辑:我重新打开了这个问题,因为我对以前的答案解释不满意。 有人能为我解释一下吗? 具体来说,我想要一些针对该伪代码的示例查询。

您可以对itemsjobs使用SELECT FOR UPDATE ,并在单个事务中处理两个表中受影响的行。 这应该足以强制执行整个操作的完整性,而不会产生SERIALIZABLE或表锁的开销。

我建议你创建一个在items表上插入或更新后调用的函数,传递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不同, SERIALIZABLE一直有效,直到失败,然后你必须在第二次尝试中重新执行所有处理。

如果您希望作业能够同时运行,则SERIALIZABLESELECT FOR UPDATE都不会直接运行。

如果使用SELECT FOR UPDATE锁定行,则另一个进程将在执行SELECT FOR UPDATE时直接阻塞,直到第一个进程提交事务为止。

如果您执行SERIALIZABLE ,则两个进程可以并发运行(处理同一行),但是在执行COMMIT时至少应该有一个进程失败,因为数据库将检测到冲突。 如果SERIALIZABLE与影响相关行的同时在数据库中发生的任何其他查询冲突,则SERIALIZABLE也可能失败。 使用SERIALIZABLE的真正原因恰恰在于您尝试防止其他作业进行并发数据库更新,而不是阻止同一作业执行两次。

注意有一些技巧可以使SELECT FOR UPDATE跳过锁定的行。 如果你这样做,那么你就可以拥有实际的并发性。 请参阅Postgresql中的选择未锁定行

我经常看到的另一种方法是将“状态”列更改为具有第3个临时状态,该状态在处理作业时使用。 通常会有'PENDING','IN_PROGRESS','COMPLETE'等状态。 当您的流程搜索要执行的工作时,它会找到“PENDING”作业,立即将其移至“IN_PROGRESS”并提交事务,然后继续工作并最终将其移至“COMPLETE”。 缺点是如果进程在处理作业时死亡,它将无限期地保留在“IN_PROGRESS”中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM