1

我正在尝试建立一个类似维基百科的东西,多人可以编辑内容。有特权的人也可以恢复更改。我不希望恢复是有损的(这意味着真的放弃人们所做的编辑。它应该只隐藏它),所以这似乎需要一个像数据结构这样的 git 分支来存储编辑,并带有一个指向“当前”的指针。

我试过这个设计:

CREATE TABLE article (
    id serial PRIMARY KEY,
    content text  NOT NULL,
    author integer NOT NULL REFERENCES "user",
    path text NOT NULL,
    relationship ltree NOT NULL
);

CREATE TABLE current_article (
    article_id NOT NULL REFERENCES article
);

其中relationship记录是新文章还是现有文章的编辑:

id | content | path | author | relationship
---+---------+------+--------+-------------
1  | foo     | /a1  | 1      | 'root'
2  | bar     | /a1  | 2      | 'root.1'
3  | baz     | /a2  | 3      | 'root'

这里的意思是,作者 2 将文章/a1从 foo 更改为 bar,并且文章/a2是新的。

current_article记录哪篇文章是“当前”文章,通常它只是指向最新的文章。还原后,它可以指向一个较旧的:

article_id
----------
2
3

当一个编辑进来时,我像这样插入它:

INSERT INTO article (content, path, author) VALUES ('qux', '/a2', 4);

并且依靠插入前触发器来查找该路径的当前文章并填写关系,并依靠插入后触发器来更新当前文章指针。

你觉得这个设计怎么样?我在这个设计中遇到的问题是处理并发的困难。

在前插入触发器中,当它找到当前文章时,它可能已经被更改,而在后插入触发器中,它可能会错误地用已经指向不同的文章覆盖当前文章。

在这方面我有三个问题:

  1. 可序列化的隔离会解决问题吗?(我对 MVCC 的概念还很陌生,仍然试图绕开它)如果没有,我应该如何解决它?
  2. 有没有更好的设计不必处理并发?
  3. 如果我确实需要处理并发,我如何在不同的竞争条件下对我的设计进行单元测试(或者这样的单元测试甚至是必要的)?

谢谢你。

4

1 回答 1

2

并发发生在两个级别:应用程序和数据库。

在应用程序级别,多个用户可能有重叠的编辑会话。在某个时候,用户会保存他们的版本,然后下一个版本也会保存,但在您当前的设计中似乎没有办法确定最后一次编辑从哪个版本分支:此信息不存在INSERT提到的。

数据库级别的并发是一个不同的问题,它涉及同时运行的事务。

如果您尝试使用处理并发的数据库原语来解决应用程序并发,您将不​​得不保持打开的事务直到用户完成编辑,这意味着在任意长时间内,这在数据库设计中是行不通的。

首先你需要想出一个应用程序和设计策略来处理并发编辑,然后你需要想出一个数据库策略来处理并发事务,即当人们同时点击“保存”并且事务更新时数据并行运行。这些是完全不同的事情。


关于并发事务,避免麻烦的一种通用方法是在执行任何其他操作之前在写入事务的开头锁定文章,以便任何其他尝试执行相同操作的事务将被阻塞,直到并发更改被提交(或回滚)。这是序列化更新的最简单方法,但它假定有一些要锁定的东西具有足够的粒度,以便对其他文章的更新不会同时被阻止。

理想情况下,应该有一个article表,每行只有一行path(独立于修订,将存储在不同的表中)。然后锁定该行SELECT ... FOR UPDATE就足以保证执行分支或任何复杂更新的查询集可以正常工作,而不会受到对同一篇文章的并发更改的干扰。

另一种(更粗略的)方法是使用可序列化隔离级别并重试任何失败并SQLSTATE指示序列化失败的事务。

于 2017-09-30T14:09:54.460 回答