数据库竞态条件

13

我听说很多应用程序开发人员在处理数据库时遇到了一些竞态条件的问题。一个典型的例子可能是这样的:

  • 用户1选择一个字段,比如numStock,它的值是3
  • 用户2也选中numStock,此时它仍然是3
  • 用户1减少numStock(在应用程序中),并将其在数据库中设置为2。
  • 用户2也减少numStock(在应用程序中),并将其在数据库中设置为2。

在这个例子中,numStock字段应该变成1,但由于用户之间的竞争,它被设置为2。

当然可以使用锁,但我想到了另一种处理方式 - 将所有行细节作为WHERE条件传递。让我解释一下...

在上面的例子中,SQL代码可能如下所示:

//select

SELECT itemID, numStock FROM items WHERE itemID = 45

//更新

UPDATE items SET numStock = 2 WHERE itemID = 45

我解决竞争的想法:

//选择

SELECT itemID, numStock FROM items WHERE itemID = 45

//更新

UPDATE items SET numStock = 2 WHERE itemID = 45 AND numStock = 3
因此,该查询检查数据是否自从它选择数据后发生了更改。因此,我的问题是:(1)这种方法[总是]有效吗? (2)与数据库锁定机制(例如,MySQL事务)相比,这是更好的选择吗?谢谢您的时间。

针对您的情况,您可以使用增量而不是将其设置为特定值。这样,如果两个独立的进程都想要增加numStock,它们就不会相互竞争。SET numStock = numStock + 1 - raw-bin hood
在您的方案中,必须设置一些重试机制以便在未更新任何内容时进行重试。在这种情况下,如果传递的itemID不正确,则重试机制将不断重试,除非指定了最大重试次数。使用事务绝对是一个更好的想法。 - skondgekar
3个回答

13

这种策略被称为“乐观锁定”,适用于IT技术领域。它的原理是在处理过程中,假设操作会成功,直到最后再检查是否成功。

当然,您需要找到一种重试事务的方法。如果失败的可能性非常高,这种方式可能变得低效。但在大多数情况下,它都能够正常运行。


2
当只有一个数据库连接时,是否可能发生数据库竞争条件? - arrowd
2
一些数据库包括运行异步进程的选项,使用这些功能,即使只有一个连接,也可以获得竞争条件。当然,根据您的角度,这些进程可能被视为单独的连接。 - Jens Schauder

2

如何在一条语句中同时进行查询和更新操作:

UPDATE counters SET value = (@cur_value := value) + 1 WHERE name_counter = 'XXX';

然后

SELECT @cur_value;

这个策略能解决竞态条件吗?

2
每次与数据库的交互都是一个事务 - 当您只写一个语句时,它是隐式的,当您使用BEGIN/COMMIT/ROLLBACK时,它是显式的。
然后,您使用一个“事务隔离级别”,它定义了哪些现象可能发生。典型的现象包括脏读、不可重复读和幻读。典型的隔离级别包括READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。
让我们看看您具体的例子(时间向下流动):
T1                T2
-----------------------------
x := r[numStock]
                  y := r[numStock]
w[numStock] = x-1
                  w[numStock] := y-1

因此,T2的写入是在陈旧的数据上进行的。这是一种“遗失更新”(lost update)现象。一些数据库,例如Postgres,可以防止遗失更新。当事务尝试在REPEATABLE_READ隔离级别或更高级别中提交时,会抛出异常:

ERROR: could not serialize access due to concurrent update

然而,据我所知,MySQL中的InnoDB引擎没有检查遗失更新(source)。

上述事务隔离级别指定了要预防的问题。它们并不说明如何实现。有乐观并发控制(快照隔离)和悲观并发控制(锁)。

我很快也会发布一篇关于这些主题的文章 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接