我有一个使用 APIGateway、Lambda 和 DynamoDB 以及 NodeJS 和 Typescript 的无服务器应用程序。我遇到过一些情况,多个客户端可能会尝试同时修改某些资源。这些竞争条件通常涉及查询记录、决定如何修改记录,然后运行更新。因此,我通过条件表达式应用了机会锁定,并使用 Date.now() 作为版本。因此,我查询记录,决定如何修改它,如果条件检查失败,我可以简单地再次查询记录并做出可能不同的决定。机会锁定是一个非常自然的解决方案。
但是,有一种赛车情况,我需要同时更新两条记录。我查询这两条记录,决定如何修改它们,然后运行更新。但是,如果第二个客户端在此期间更新了其中任何一个,则第一个客户端应再次查询这两个记录,并可能做出不同的决定。
我使用具有两个更新操作的事务解决了这个问题。每个都包含自己单独的条件表达式,因此如果其中一个失败,则两个都会以全有或全无的方式失败。
这可能是一个不错的解决方案,但后来我阅读了一些 DynamoDB 文档,它说事务在以下情况下可能会失败:
当 TransactWriteItems 请求与 TransactWriteItems 请求中的一项或多项正在进行的 TransactWriteItems 操作发生冲突时。在这种情况下,请求会失败并出现 TransactionCanceledException。
根据我在本文其余部分的理解,如果两个事务涉及相同的记录,则其中一个事务将失败并出现异常。我知道这是为了解决竞争条件,失败的客户端可以捕获异常并重试。
我的问题是对事务实施机会锁定是否完全多余,捕获 TransactionCanceledException 是否足以处理我的竞争条件。或者说冗余有什么价值吗?毕竟,如果我查询我的两条记录,然后其他事务在我开始事务之前的几毫秒内修改它们,该怎么办?
不,即使有事务,你仍然需要实现乐观锁定。
如果没有乐观锁,它很容易崩溃,就像这样:
Client A reads item
Client B reads item
Client A updates item
Client B updates item (without seeing what client A done)
Client B corrupts the item