【问题标题】:Best way of managing database concurrency?管理数据库并发的最佳方式?
【发布时间】:2018-03-22 15:06:33
【问题描述】:

我在处理并发时遇到了一个问题。

在下面的示例中,两个用户 A 和 B 编辑同一张发票并对其进行不同的更改。如果他们两个同时单击保存,我希望其中一个成功,另一个失败。否则,生成的发票将是一张不受欢迎的“合并发票”。

这是在 PostgreSQL 中测试的示例(但我认为这个问题应该与数据库无关):

create table invoice (
  id int primary key not null,
  created date
);

create table invoice_line (
  invoice_id int, 
  line numeric(6), 
  amount numeric(10,2),
  constraint fk_invoice foreign key (invoice_id) references invoice(id)
  );

insert into invoice(id, created) values (123, '2018-03-17');
insert into invoice_line (invoice_id, line, amount) values (123, 1, 24);
insert into invoice_line (invoice_id, line, amount) values (123, 2, 26);

所以发票的初始行是:

invoice_id  line  amount
----------  ----  ------
       123     1      24
       123     2      26

现在,用户 A 编辑发票,删除第 2 行并点击保存

-- transaction begins

set transaction isolation level serializable;

select * from invoice where id = 123; -- #1 will it block the other thread?

delete invoice_line where invoice_id = 123 and line = 2;

commit; -- User A would expect the invoice to only include line 1.

同时用户 B 编辑发票并添加第 3 行,然后点击保存

-- transaction begins

set transaction isolation level serializable;

select * from invoice where id = 123; -- #2 will this wait the other thread?

insert into invoice_line (invoice_id, line, amount) values (123, 3, 45);

commit; -- User B would expect the invoice to include lines 1, 2, and 3.

不幸的是,两个事务都成功了,我得到了合并的行(损坏的状态):

invoice_id  line  amount
----------  ----  ------
       123     1      24
       123     3      45

既然这不是我想要的,我有什么选项来控制并发?

【问题讨论】:

  • 一次只能处理一个。提交更改时,它应该检查要更改的状态是否未根据用户选择他们正在修改的数据时的状态进行调整。
  • 您必须在存储过程中处理此问题,使用if else 或最好的方法将它们包装在开始和结束中,无论您选择何种事务隔离级别,这两个事务都会成功。跨度>

标签: sql database database-concurrency


【解决方案1】:

这不是数据库并发问题。数据库的 ACID 属性是关于事务的完成,同时保持数据库的完整性。在您描述的情况下,事务是正确的,并且数据库正在正确处理它们。

您想要的是一种锁定机制,本质上是一种信号量,它保证在任何时候只有一个用户可以对数据进行写访问。您可能能够依赖数据库锁定机制,在锁定失败时捕获。

但是,我建议使用其他两种方法中的一种。如果您对仅在应用程序逻辑中的更改感到满意,那么将锁定机制放在那里。有一个用户可以“锁定”表或记录的地方;然后不要让其他人碰它。

你可以更进一步。您可以要求用户获得表的“所有权”以进行更改。然后,您可以实现一个失败的触发器,除非用户是进行更改的人。

而且,您可能会想到其他解决方案。我真正想指出的是,您的用例超出了 RDBMS 默认执行的操作(因为它们会让两个事务都成功完成)。因此,对于任何数据库(我熟悉的),您都需要额外的逻辑。

【讨论】:

  • 也许这个用例非常适合乐观锁定? (即在发票表中添加一个额外的“版本”列)
【解决方案2】:

作为一般规则,发票行项目在过帐后不应编辑或删除。如果客户需要冲销费用,执行此操作的典型方法是添加记入金额的新交易,可能使用包含要冲销的行项目 ID 的交叉引用字段。这种方法的优点是(1)您可以修改客户的余额,而无需返回并重新预订任何先前的报表周期,以及(2)您不会遇到像这样难以解决的并发问题。

如果尚未过帐发票,您仍然不允许编辑行项目。相反,您将取消之前的发票并创建一个包含所有新行项目的新发票。这再次避开了手头的并发问题。

【讨论】:

  • 没提,但用例是针对未过帐发票的。我想我可以删除编辑按钮并提供删除+创建。不理想,但是——正如你所说——这样我们就可以避开这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 2011-04-18
  • 1970-01-01
  • 2016-01-13
  • 2011-01-17
  • 1970-01-01
  • 2011-06-08
相关资源
最近更新 更多