数据库将有一个表,类似于
Product(id, amount)
。下订单需要更新该产品的记录。
实现的样子大致如下。首先,应用程序将从数据库中读取产品数量:
SELECT id, amount
FROM Product
WHERE id = {some_id}
FOR UPDATE;
应用程序代码将检查是否有所请求的金额。然后它会更新数据库中的该值:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
请注意:
- 所有这些应该在一个数据库事务中完成。
- 第一个查询使用
FOR UPDATE
子句防止其他并发事务在我们的事务运行时修改此产品。将其视为在DB级别而不是JVM级别上同步。
- 如果另一个用户尝试购买相同的产品,则该用户的事务(称为T2)将尝试执行相同的操作,第一步是使用锁定查询记录。这将阻止事务T2,直到记录被解除阻止,这将在当前持有锁定的事务提交或回滚时发生。此时,T2将继续进行,并且如果T1更改了产品,则它将看到最新的产品价值。
此方法使用悲观锁,即我们为要更新的记录锁定整个事务时间。
还有另一种方法-乐观。
乐观锁需要在Product表中有一个版本列。然后,第一个查询不会锁定DB中的记录,但我们会得到记录的版本:
SELECT id, amount, version
FROM Product
WHERE id = {some_id}
签到应用程序的工作方式与悲观情况下相同。但是最终的更新不同:
UPDATE Product
SET amount = {new_amount}
WHERE id = {some_id}
and version = {version_we_read_initially}
更新操作总是返回已更新记录的数量。如果返回值为0,则表示在我们的事务运行时记录已更改。这意味着我们最初看到的数量(在第一个查询中)可能已更改。为了继续,我们需要从第1步重新尝试整个操作,即再次读取产品的数量和版本,检查是否有所需数量,并尝试再次更新数量。
当然,我们应该设置一些重试次数限制。对于繁忙的、受欢迎的产品,这可能会成为问题。