8

我听说许多应用程序开发人员在数据库处理中的竞争条件方面遇到了一些麻烦。一个典型的例子是这样的:

  • 用户 1 选择一个字段,例如 numStock,它是 3
  • 用户 2 也选择了 numStock,仍然是 3
  • 用户 1 递减 numStock(在应用程序中),并在数据库中将其设置为 2。
  • 用户 2 还递减 numStock(在应用程序中),并在数据库中将其设置为 2。

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

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

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

//选择

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 事务)相比,这是一个更好的选择吗?

谢谢你的时间。

4

3 回答 3

9

这种策略有效,被称为“乐观锁定”。那是因为您在进行处理时假设它会成功,并且只有在最后才实际检查它是否成功。

当然,您需要一种重试事务的方法。如果失败的可能性非常高,它可能会变得低效。但在大多数情况下,它工作得很好。

于 2012-03-24T08:56:53.977 回答
1

如何在单个语句中进行选择和更新:

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

进而

SELECT @cur_value;

这种策略是否可以解决竞争条件?

于 2018-08-27T15:18:44.203 回答
0

与数据库的每次交互都是一个事务——当你只写一个语句时是隐式的,或者当你使用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 的写入是在陈旧数据上。这是一个丢失的更新。一些数据库,例如 Postgres,可以防止丢失更新。当尝试在REPEATABLE_READ隔离级别或更高级别提交事务时,会抛出异常:

错误:由于并发更新,无法序列化访问

但是,我听说 MySQL 中的 InnoDB 引擎没有检查丢失更新(来源)。

提到的事务隔离级别指定了您要防止的问题。他们没有就如何实现做出任何声明。有乐观并发控制(快照隔离)和悲观并发控制(锁)。

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

于 2021-10-10T17:24:21.910 回答