

# 乐观锁（使用版本号）
<a name="BestPractices_OptimisticLocking"></a>

乐观锁是一种在写入时检测冲突而不是防止冲突的策略。每个项目都包含一个版本属性，其值会随着每次更新而增加。更新项目时，您需要包含一个[条件表达式](Expressions.ConditionExpressions.md)，用于检查版本号是否与应用程序上次读取的值匹配。如果在这个期间其他进程修改了该项目，则条件失败并且 DynamoDB 返回 `ConditionalCheckFailedException`。

## 使用乐观锁的情况
<a name="BestPractices_OptimisticLocking_WhenToUse"></a>

乐观锁非常适合用于以下情况：
+ 多个用户或进程可能会更新同一个项目，但冲突并不频繁。
+ 对于应用程序来说，重试失败写入的成本较低。
+ 您需要避免管理分布式锁的开销和复杂性。

常见的示例包括电子商务库存更新、协作编辑平台和金融交易记录。

## 权衡
<a name="BestPractices_OptimisticLocking_Tradeoffs"></a>

**高争用率下的重试开销**  
在高并发环境中，发生冲突的可能性会增加，进而可能导致更高的重试次数和写入成本。

**实施复杂性**  
向项目添加版本控制和处理有条件检查，会增加应用程序逻辑的复杂性。适用于 Java 的 AWS SDK v2 增强版客户端通过 [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE) 注解提供内置支持，这可以自动为您管理版本号。

## 模式设计
<a name="BestPractices_OptimisticLocking_PatternDesign"></a>

每个项目中包含一个版本属性。以下提供了一个简单的架构设计：
+ 分区键：每个项目的唯一标识符（例如 `ItemId`）。
+ 属性：
  + `ItemId` – 项目的唯一标识符。
  + `Version` – 表示项目版本号的整数。
  + `QuantityLeft` – 项目的剩余库存。

首次创建项目时，`Version` 属性设置为 1。每次进行更新时，版本号增加 1。


| ItemID（分区键） | 版本 | QuantityLeft | 
| --- | --- | --- | 
| Bananas | 1 | 10 | 
| Apples | 1 | 5 | 
| Oranges | 1 | 7 | 

## 实现
<a name="BestPractices_OptimisticLocking_Implementation"></a>

要实施乐观锁，请按照以下步骤操作：

1. 读取项目的当前版本。

   ```
   def get_item(item_id):
       response = table.get_item(Key={'ItemID': item_id})
       return response['Item']
   
   item = get_item('Bananas')
   current_version = item['Version']
   ```

1. 使用检查版本号的条件表达式更新项目。

   ```
   def update_item(item_id, qty_bought, current_version):
       try:
           response = table.update_item(
               Key={'ItemID': item_id},
               UpdateExpression="SET QuantityLeft = QuantityLeft - :qty, Version = :new_v",
               ConditionExpression="Version = :expected_v",
               ExpressionAttributeValues={
                   ':qty': qty_bought,
                   ':new_v': current_version + 1,
                   ':expected_v': current_version
               },
               ReturnValues="UPDATED_NEW"
           )
           return response
       except ClientError as e:
           if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
               print("Version conflict: another process updated this item.")
           raise
   ```

1. 使用新的读取进行重试来处理冲突。

   每次重试都需要进行额外的读取操作，因此请限制重试的总次数。

   ```
   def update_with_retry(item_id, qty_bought, max_retries=3):
       for attempt in range(max_retries):
           item = get_item(item_id)
           try:
               return update_item(item_id, qty_bought, item['Version'])
           except ClientError as e:
               if e.response['Error']['Code'] != 'ConditionalCheckFailedException':
                   raise
               print(f"Retry {attempt + 1}/{max_retries}")
       raise Exception("Update failed after maximum retries.")
   ```

对于 Java 应用程序，适用于 Java 的 AWS SDK v2 增强版客户端通过 [https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE](https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/ddb-en-client-extensions.html#ddb-en-client-extensions-VRE) 注解提供内置乐观锁支持，这可以自动为您管理版本号。

有关条件表达式的更多信息，请参阅 [DynamoDB 条件表达式 CLI 示例](Expressions.ConditionExpressions.md)。