Postgres中跨表的乐观并发控制

Sni*_*tor 3 postgresql npgsql optimistic-concurrency

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

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
)
Run Code Online (Sandbox Code Playgroud)

master.row_version 每当更新行时,触发器都会自动递增。

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

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

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

我在想 Postgres 中的一个函数在表中的相关记录上有一个行级锁master可能是要走的路。但我不确定这是否是我最好的/唯一的选择,或者它会是什么样子(我对 Postgres 语法有点陌生)。

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

Sni*_*tor 10

我得出的结论是,行锁可以用于“典型的”悲观并发控制方法,但与行版本结合使用时可以产生具有一些有意义的好处的“混合”方法。

毫不奇怪,悲观、乐观或“混合”并发控制的选择取决于应用程序的需求。

悲观并发控制

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

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

如果步骤 3 中的业务逻辑长时间运行,则此方法可能是不可取的,因为它会导致长时间运行的事务(通常是不利的),以及记录上长时间运行的锁定,否则可能会出现并发master问题。

乐观并发控制

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

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

在这种情况下,数据库事务的保留时间较短,任何(隐式)行锁也是如此。但是,表中记录的行版本增量master可能会对并发操作产生一定的误导。想象一下这种情况下的多个并发操作,它们将开始在乐观并发检查上失败,因为行版本已增加,即使记录上有意义的属性尚未更改。

混合并发控制

“混合”方法同时使用悲观锁定和(某种程度上)乐观锁定,如下所示。

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

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

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

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

混合方法的示例

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

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

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


Lau*_*lbe 5

如果您想使用乐观并发控制,则锁定已关闭,请参阅有关该主题的维基百科文章

OCC 假设多个事务可以频繁完成而不会相互干扰。在运行时,事务使用数据资源而不获取这些资源的锁。

您可以使用更复杂的INSERT语句。如果$1是原始的row_versionand$2$3aremaster_id并且some_data要插入detail,运行

WITH m(id) AS
     (SELECT CASE WHEN master.row_version = $1
                  THEN $2
                  ELSE NULL
             END
      FROM master
      WHERE master.id = $2)
INSERT INTO detail (master_id, some_data)
   SELECT m.id, $3 FROM m
Run Code Online (Sandbox Code Playgroud)

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