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 每当更新行时,触发器都会自动递增。
客户端应用程序执行以下操作:
master表中读取记录。detail根据步骤 2 中的逻辑向表中插入一条记录。如果master.row_version自从在第 1 步读取记录后的值发生了变化,我希望第 3 步被拒绝。在我看来,乐观并发控制是正确的答案(唯一的答案?),但我不确定如何管理它两张这样的桌子。
我在想 Postgres 中的一个函数在表中的相关记录上有一个行级锁master可能是要走的路。但我不确定这是否是我最好的/唯一的选择,或者它会是什么样子(我对 Postgres 语法有点陌生)。
鉴于客户端应用程序是用 C# 编写的,我正在使用 Npgsql。不知道里面有没有什么可以帮到我的?如果可能的话,我想避免使用一个函数,但我正在努力寻找一种方法来使用直接的 SQL 来做到这一点,并且匿名代码块(至少在 Npgsql 中)不支持我的 I/O 操作需要。
Sni*_*tor 10
我得出的结论是,行锁可以用于“典型的”悲观并发控制方法,但与行版本结合使用时可以产生具有一些有意义的好处的“混合”方法。
毫不奇怪,悲观、乐观或“混合”并发控制的选择取决于应用程序的需求。
典型的悲观并发控制方法可能如下所示。
master。detail向表中插入一条记录。如果步骤 3 中的业务逻辑长时间运行,则此方法可能是不可取的,因为它会导致长时间运行的事务(通常是不利的),以及记录上长时间运行的锁定,否则可能会出现并发master问题。
仅使用乐观并发控制的方法可能看起来更像这样。
master。master(乐观并发控制检查)。detail向表中插入一条记录。在这种情况下,数据库事务的保留时间较短,任何(隐式)行锁也是如此。但是,表中记录的行版本增量master可能会对并发操作产生一定的误导。想象一下这种情况下的多个并发操作,它们将开始在乐观并发检查上失败,因为行版本已增加,即使记录上有意义的属性尚未更改。
“混合”方法同时使用悲观锁定和(某种程度上)乐观锁定,如下所示。
master。master根据记录的 ID和行版本(乐观并发控制检查)从表中重新读取记录并锁定该行。detail向表中插入一条记录。如果步骤4未能获取记录,则应视为乐观并发控制检查失败。该记录自步骤 1 以来已更改,因此业务逻辑不再有效。
与典型的悲观并发控制场景一样,这涉及到事务和显式行锁,但事务+锁的持续时间不再包括执行业务逻辑所需的时间。
和乐观并发控制场景一样,记录也需要版本。但不同之处在于版本未更新,这意味着依赖于该行版本的其他操作不会受到影响。
混合方法可能有利的示例:
一个博客有一个post表和comment一个表。post.comments_locked仅当标记为false时才可以将评论添加到帖子中 添加评论的过程可以使用混合方法,确保用户可以同时添加评论而不会出现任何并发异常。
博客的所有者可以编辑他们的博客post,在这种情况下可以采用传统的乐观并发控制方法。博客的所有者可以有一个长期运行的编辑过程,该过程不会受到用户添加评论的影响。当更新到数据库时,版本将增加,这意味着任何正在进行的注释添加操作都将失败,但可以使用数据库胜出的方法轻松重试,从数据库中post重新获取记录并重试post过程
如果您想使用乐观并发控制,则锁定已关闭,请参阅有关该主题的维基百科文章:
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
您可以将其转换为更有意义的错误消息。
| 归档时间: |
|
| 查看次数: |
6712 次 |
| 最近记录: |