处理事务数据库表中的竞争条件

时间:2014-08-13 23:38:23

标签: database race-condition

让我先说明情景。假设您有一个商业应用程序的数据库,它跟踪的一个内容是库存。系统说你有5个螺丝库存。假设您需要全部5.系统创建-5的库存交易记录。在您提交该事务之后,因为您知道您之前有5个并且您拔出了5个,如果您总结该螺钉的所有库存交易记录,则总数应该为0.当两个人试图在此处执行此操作时会出现问题。同时。假设一个人想要4而另一个想要2.两个客户端应用程序事先检查数量并且他们都被告知5.在完全相同的时间,一个创建一个事务为-4而另一个为-2。总库存数量的结果为-1,这是不可能的,因为系统不应允许负库存。

如果您没有服务器应用程序来帮助您,您会如何解决这个问题?我提到,因为协调库存交易的服务器是我如何解决它,但现在我们的产品没有服务器应用程序。我们只有客户端应用程序直接与Firebird数据库通信。我试图通过客户端应用程序和数据库来弄清楚如何做到这一点。可能有帮助的一点是Firebird有一个叫做Generator的东西,它基本上是一个原子的唯一数字生成器所以你可以保证,如果你让Firebird增加生成器并给你下一个数字它不会给其他任何人相同的数字。

我的想法是试图使用生成器创建临时记录锁定的路线。我以为我可以让他们同时检查一下"锁定" Item表上的字段。如果它为null,则没有人有锁。如果它是非null,则它被锁定,因此您需要继续检查,直到它没有被锁定。如果没有锁定,则向生成器询问uniq编号,并将其存储在要锁定的项目的锁定字段中。您提交该事务然后返回并检查是否确实是Item表的锁定字段包含您放在那里的数字。如果确实如此,那么你已成功锁定,如果它没有,那么这意味着某人同时锁定它并且你输掉了比赛。完成后,您将锁定为空,然后等待的客户端将看到null,将其自行锁定并重复。

我相信这本身就有竞争条件。 Trxn1(事务1)检查锁定并找到null。 Trxn2检查锁定并找到null。 Trxn1从生成器获取新的锁定号。 Trxn2从发电机获得新锁。如果锁定仍为空,Trxn1用我的锁说更新项目记录。 Trxn1提交trxn然后启动一个新的Trxn1并证明锁包含他的锁定ID,它确实知道它有权进行库存交易并且它开始这样做。在Trxn1检查它是否获得锁定之后,如果锁定为空,Trxn2会提交其存储锁定的更新语句。如果Trxn2在Trxn1提交锁之前执行了他的更新语句,那么Trxn2仍然会将该值视为null并且将发生更新。如果在Trxn1提交锁定并且已经验证它之后发生Trxn2的锁定提交,我们就会遇到问题。 Trxn1正在对Item事务表进行更改。 Trxn2得到了他的锁定,因为当它执行它时它的事务世界中的锁是空的,并且当它提交Trxn2的更新语句将覆盖Trxn1的锁定,因为更新语句中的空检查发生在两者都提交之前,不是在提交时。所以现在他们都认为他们有锁定,我们最终会得到负库存。

有人能想出一种方法来解决这种服务器应用程序与某种排队系统(FIFO)的缺点吗?我希望是否可以通过客户端完成所有这些工作"与数据库交谈"协调这一点,但从技术上讲,这可能是不可能的。对不起如果这有点罗嗦:D

解决方案编辑: jtahlborn似乎有正确的想法。我不知道Firebird确实有行级锁定。简单的选择语句(没有连接,分组等)可以使用锁定"附加到语句末尾并且语句返回的任何行将被锁定,直到提交或回滚事务为止。没有其他人可以获得该行的锁定,也无法对其进行更改。因为我不想在将行插入Item事务表时锁定整个ITEM表,所以我将创建一个仅用于具有一列(ItemID字段)的锁定的表。因为第二个事务在尝试执行它自己的锁时会出错,所以我从来没有真正修改锁定表本身的任何内容并不重要。未能获得锁定可以获得我需要的所有信息。我将把触发器放在ITEM表的插入/删除上,这样对于每个Item记录,这也是ITEMLOCK表中的记录。这是我将要使用的过程。

  1. 启动数据库事务
  2. 尝试使用要更改的项目的ItemID获取ITEMLOCK行的锁定
  3. 如果您无法获得锁定,请继续尝试,直到记录解锁为止
  4. 一旦锁定,请证明该项目的数量足以涵盖您的
  5. 想要拿走,因为他们可能有旧的数据,这可能不是 案件,它将在这里退出并向用户发送消息
  6. 如果存在足够数量,请在库存交易表中插入库存交易记录
  7. 提交事务,然后发布锁定
  8. 注意:Matthieu M提到了FOR UPDATE子句。文档中提到了WITH LOCK子句。据我所知,当您使用一个语句锁定多行时,可以使用它。我不是百分百肯定的,但似乎用WITH LOCK这样做会尝试一个全有或全无的方法,FOR UPDATE会一次一个地锁定每一个。我不确定如果它锁定你要求的前100个记录会发生什么,但是在第101个记录中它无法获得锁定。它会释放你获得的100个锁吗?我需要一次锁定多个项目,但我对FOR UPDATE感到不舒服,因为我觉得我不能真正理解其中的区别。我也可能想知道哪个项目已经被锁定用于用户消息传递目的(将超时因此trxns不会永远等待锁定)所以我将使用WITH LOCK锁定一个。

    注意2:我想指出任何在自己的代码中使用它的人都要小心。在等待释放锁时,我将有一个非常简单的循环(它是否已经发布了?现在怎么样?现在?)。如果我有大量用户可能试图同时锁定同一行,则可能存在死锁情况。假设你的客户很慢。那个客户端可能总是最终得到一个简短的结束,因为每次锁定释放时,其他一些客户端就会比慢速客户端更快地抓住它。如果这种情况一再发生,这本质上就是一种僵局。如果我担心这一点,我需要一种方法来弄清楚谁排在第一位。就我而言,数据库事务应该是短暂的,我们从来没有超过50个用户(不是云系统),并且他们都不太可能同时使用系统的这一部分来尝试修改完全相同的物品的库存数量。

2 个答案:

答案 0 :(得分:1)

最简单的解决方案是锁定一些主要行(如主"项目")并将其用作分布式锁定机制。 (假设您的数据库支持行级锁,就像大多数现代数据库一样)。

答案 1 :(得分:0)

我建议阅读有关CAP定理的内容,以及它如何解释您所描述的场景。编辑:阅读更详细,我的评论可能是有限的使用,因为你似乎已经知道这一点,并试图解决Firebird中的问题。