【问题标题】:Postgres concurrency and serializability. Do I need a SERIALIZABLE isolation level?Postgres 并发性和可序列化性。我需要 SERIALIZABLE 隔离级别吗?
【发布时间】:2017-02-15 06:51:04
【问题描述】:

我有一个 Items 和 Jobs 表:

项目

  • id = PK
  • job_id = Jobs FK
  • 状态 = 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 吗?在哪里?
  • 谁能向我解释发生了什么,为什么?

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

【问题讨论】:

    标签: sql postgresql transactions


    【解决方案1】:

    您可以在itemsjobs 上使用SELECT FOR UPDATE,并在单个事务中处理两个表中受影响的行。这应该足以强制整个操作的完整性,而不会产生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 不同,后者将一直工作到失败,然后您必须在第二次尝试中重新执行所有处理。

    【讨论】:

    • 谢谢,这是一个很棒的答案。那么连接上的 SELECT FOR UPDATE 会锁定我需要的一切吗?
    • 除非您明确指出要在哪些表上应用锁定,否则查询中的所有表都会锁定所有受影响的行。
    【解决方案2】:

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

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

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

    注意有一些技巧可以让SELECT FOR UPDATE 跳过锁定的行。如果你这样做,那么你可以有实际的并发性。见Select unlocked row in Postgresql

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-21
      • 1970-01-01
      • 2011-06-28
      • 2023-03-04
      • 1970-01-01
      • 2022-09-23
      • 2020-07-16
      • 2012-11-02
      相关资源
      最近更新 更多