Postgres 中跨表的乐观并发控制

Optimistic concurrency control across tables in Postgres

我正在寻找一种方法来管理 Postgres 中多个 table 的乐观并发控制。我还试图将业务逻辑 排除在数据库之外 。我有一个 table 设置是这样的:

CREATE TABLE master
(
    id SERIAL PRIMARY KEY NOT NULL,
    status VARCHAR NOT NULL,
    some_value INT NOT NULL,
    row_version INT NOT NULL DEFAULT(1)
)

CREATE TABLE detail
(
    id SERIAL PRIMARY KEY NOT NULL,
    master_id INT NOT NULL REFERENCES master ON DELETE CASCADE ON UPDATE CASCADE,
    some_data VARCHAR NOT NULL
)
每当更新行时,

master.row_version 会自动由触发器递增。

客户端应用程序执行以下操作:

  1. master table 中读取一条记录。
  2. 根据记录的值计算一些业务逻辑,这可能包括涉及用户交互的几分钟延迟。
  3. 根据步骤 2 中的逻辑将记录插入 detail table。

如果 master.row_version 的值自第 1 步读取记录后发生变化,我希望第 3 步被拒绝。乐观并发控制在我看来是正确的答案(唯一的答案?),但是我不确定如何像这样跨两个 table 管理它。

我正在考虑在 master table 中的相关记录上使用 row-level lock 的 Postgres 函数可能是可行的方法。但我不确定这是否是我的 best/only 选项,或者它看起来像什么(我对 Postgres 语法有点陌生)。

鉴于客户端应用程序是用 C# 编写的,我正在使用 Npgsql。不知道里面有没有什么可以帮到我的?如果可能的话,我想避免使用函数,但我正在努力寻找一种方法来直接使用 SQL,并且匿名代码块(至少在 Npgsql 中)不支持 [=我需要 40=] 次操作。

如果您想使用乐观并发控制,则锁定已失效,请参阅主题 the Wikipedia article

OCC assumes that multiple transactions can frequently complete without interfering with each other. While running, transactions use data resources without acquiring locks on those resources.

您可以使用更复杂的 INSERT 语句。 如果</code>是原来的<code>row_version</code>和<code>master_idsome_data要插入detail,运行

WITH m(id) AS
     (SELECT CASE WHEN master.row_version = 
                  THEN 
                  ELSE NULL
             END
      FROM master
      WHERE master.id = )
INSERT INTO detail (master_id, some_data)
   SELECT m.id,  FROM m

如果 row_version 已更改,这将尝试将 NULL 作为 detail.id 插入,这将导致
ERROR: null value in column "id" violates not-null constraint
您可以将其转化为更有意义的错误消息。

从那以后我得出的结论是行锁可以用在 "typical" 悲观并发控制方法中,但是当与行版本结合使用时可以产生 "hybrid" 一些方法有意义的好处。

毫不奇怪,悲观、乐观或"hybrid"并发控制的选择取决于应用程序的需要。

悲观并发控制

典型的悲观并发控制方法可能如下所示。

  1. 开始数据库事务。
  2. 读取(并锁定)来自 master table 的记录。
  3. 执行业务逻辑。
  4. 插入一条记录到 detail table.
  5. 提交数据库事务。

如果第 3 步的业务逻辑是 long-运行,这种方法可能是不可取的,因为它会导致 long-运行 事务(通常不利),以及 long-运行 锁定 master 中的记录,否则可能会导致并发问题。

乐观并发控制

仅使用乐观并发控制的方法可能看起来更像这样。

  1. master table.
  2. 中读取记录(包括行版本)
  3. 执行业务逻辑。
  4. 开始数据库事务。
  5. master table(乐观并发控制检查)中记录的增量行版本。
  6. 插入一条记录到 detail table.
  7. 提交数据库事务。

在这种情况下,数据库事务保持较短的时间,任何(隐式)行锁也是如此。 但是mastertable中记录的row version增量可能有点误导并发操作。想象一下这种情况下的几个并发操作,它们将开始在乐观并发检查上失败,因为行版本已经增加,即使记录上有意义的属性没有改变。

混合并发控制

"hybrid" 方法同时使用悲观锁定和(某种)乐观锁定,就像这样。

  1. master table.
  2. 中读取记录(包括行版本)
  3. 执行业务逻辑。
  4. 开始数据库事务。
  5. 根据其 ID 行版本从 master table 重新读取记录(某种乐观并发控制检查)AND 锁定行。
  6. 插入一条记录到 detail table.
  7. 提交数据库事务。

如果第4步获取记录失败,应该是乐观并发控制检查失败。该记录自第 1 步以来已更改,因此业务逻辑不再有效。

和典型的悲观并发控制场景一样,这里涉及到一个事务和一个显式的行锁,但是事务+锁的持续时间不再包括执行业务逻辑所需要的时间。

和乐观并发控制场景一样,记录需要版本。但不同之处在于版本没有更新,这意味着依赖于该行版本的其他操作不会受到影响。

混合方法示例

混合方法可能有利的示例:

一个博客有 post table 和 comment table。仅当 post.comments_locked 标志为 false 时,才能将评论添加到 post。添加评论的过程可以使用混合方法,确保用户可以并发添加评论而不会出现任何并发异常。

博客的所有者可以编辑他们的 post,在这种情况下,可以采用传统的乐观并发控制方法。博客的所有者可以有一个漫长的运行 编辑过程,不会受到用户添加评论的影响。当 post 更新到数据库时,版本将增加,这意味着任何正在进行的评论添加操作都将失败,但可以通过重新获取 post 从数据库中记录并重试该过程。