

# Amazon DynamoDB Transactions：工作原理
工作原理

借助 Amazon DynamoDB Transactions，您可以将多个操作分组在一起，并将它们作为单个“要么全有要么全无”的 `TransactWriteItems` 或 `TransactGetItems` 操作提交。以下各部分介绍有关在 DynamoDB 中使用事务操作的 API 操作、容量管理、最佳实践和其他详细信息。

**Topics**
+ [

## TransactWriteItems API
](#transaction-apis-txwriteitems)
+ [

## TransactGetItems API
](#transaction-apis-txgetitems)
+ [

## DynamoDB 事务的隔离级别
](#transaction-isolation)
+ [

## DynamoDB 中的事务冲突处理
](#transaction-conflict-handling)
+ [

## 在 DynamoDB Accelerator（DAX）中使用事务 API
](#transaction-apis-dax)
+ [

## 事务的容量管理
](#transaction-capacity-handling)
+ [

## 事务的最佳实践
](#transaction-best-practices)
+ [

## 将事务 API 与全局表结合使用
](#transaction-integration)
+ [

## DynamoDB 事务与 AWSLabs 事务客户端库
](#transaction-vs-library)

## TransactWriteItems API
TransactWriteItems

`TransactWriteItems` 是一个同步和幂等的写入操作，它可将多达 100 个写入操作分组在单个“要么全有要么全无”操作中。这些操作的目标是同一个 Amazon 账户和同一个区域内的一个或多个 DynamoDB 表中有多达 100 个不同的项目。事务中项目的合计大小不能超过 4 MB。这些操作以原子方式完成，以便所有操作都成功或都失败。

**注意**  
 `TransactWriteItems` 不同于 `BatchWriteItem` 操作，因为前者包含的所有操作必须成功完成，否则根本不会进行任何更改。借助 `BatchWriteItem` 操作，批处理中的某些操作可能获得成功，而其他操作失败。
 不能使用索引执行事务。

您不能将同一个事务中的多个操作指向同一个项目。例如，您不能在同一个事务中对相同项目既执行 `ConditionCheck` 又执行 `Update` 操作。

您可向事务添加下列类型的操作：
+ `Put` — 启动 `PutItem` 操作以创建一个新项目，或者将旧项目替换为新项目（有条件或未指定任何条件）。
+ `Update` — 启动 `UpdateItem` 操作以编辑现有项目的属性，或者将新项目添加到表中（如果它不存在）。使用此操作有条件或无条件添加、删除或更新现有项目的属性。
+ `Delete` — 启动 `DeleteItem` 操作，以删除表中由其主键标识的单个项目。
+ `ConditionCheck` — 检查项目是否存在，或者检查项目特定属性的条件。

某个事务在 DynamoDB 中完成之后，其更改开始传播到全局二级索引（GSI）、流和备份。这种传播是逐步进行的：来自同一事务的流记录可能显示为不同的时间，并且可以与其他事务的记录交错存放。流使用者不应假定事务的原子性或顺序保证。

要确保事务中修改的项目的原子快照，请使用 TransactGetItems 操作一起读取所有相关项目。此操作提供了一致的数据视图，可确保您看到已完成事务中的所有更改，否则不查看任何更改。

由于传播并不具有即时性，如果在传播过程中某个表从备份还原（[RestoreTableFromBackup](https://docs.amazonaws.cn/amazondynamodb/latest/APIReference/API_RestoreTableFromBackup.html)）或将其导出到某个时间点（[ExportTableToPointInTime](https://docs.amazonaws.cn/amazondynamodb/latest/APIReference/API_ExportTableToPointInTime.html)），则可能会包含在近期事务期间所做的部分而非全部更改。

### 幂等性
幂等性

当您执行 `TransactWriteItems` 调用时，可以选择包含客户端令牌，以确保请求是*幂等的*。通过使事务成为幂等的，如果相同操作由于连接超时或其他连接问题而多次提交，则可帮助防止出现应用程序错误。

如果原始 `TransactWriteItems` 调用成功，则使用相同客户端令牌的后续 `TransactWriteItems` 调用将成功返回，而不做任何更改。如果设置了 `ReturnConsumedCapacity` 参数，则初始 `TransactWriteItems` 调用将返回在进行更改时占用的写入容量单位数。使用相同客户端令牌的后续 `TransactWriteItems` 调用将返回在读取项目时占用的读取容量单位数。

**有关幂等性的要点**
+ 客户端令牌在使用它的请求完成后的 10 分钟内有效。10 分钟后，任何使用相同客户端令牌的请求都将被视为一个新请求。10 分钟后，您不应为同一请求使用重新相同的客户端令牌。
+ 如果您在 10 分钟的幂等性时段内使用相同的客户端令牌重复请求，但更改了某个其他请求参数，则 DynamoDB 将返回 `IdempotentParameterMismatch` 异常。

### 写入错误处理
错误处理

写入事务在以下情况下不会成功：
+ 其中一个条件表达式中的条件未得到满足时。
+ 因为同一个 `TransactWriteItems` 操作中的多个操作指向同一个项目而导致发生事务验证错误时。
+ `TransactWriteItems` 请求与 `TransactWriteItems` 请求中针对一个或多个项目的正在执行的 `TransactWriteItems` 操作冲突时。在这种情况下，请求将失败并显示 `TransactionCanceledException`。
+ 当完成事务所需的预置容量不足时。
+ 当项目大小变得过大（大于 400 KB），或者本地二级索引 (LSI) 变得过大，或者由于事务所做更改导致发生类似的验证错误时。
+ 出现用户错误（如数据格式无效）时。

 有关如何处理与 `TransactWriteItems` 操作的冲突的更多信息，请参阅 [DynamoDB 中的事务冲突处理](#transaction-conflict-handling)。

## TransactGetItems API
TransactGetItems

`TransactGetItems` 是一个同步读取操作，它可将多达 100 个 `Get` 操作分组在一起。这些操作的目标是同一个 Amazon 账户和区域内的一个或多个 DynamoDB 表中有多达 100 个不同的项目。事务中项目的合计大小不能超过 4 MB。

`Get` 以原子方式执行，以便所有操作都成功或都失败：
+ `Get` — 启动 `GetItem` 操作，以检索具有给定主键的项目的一组属性。如果找不到匹配项目，`Get` 将不返回任何数据。

### 读取错误处理
错误处理

读取事务在以下情况下不会成功：
+ `TransactGetItems` 请求与 `TransactWriteItems` 请求中针对一个或多个项目的正在执行的 `TransactGetItems` 操作冲突时。在这种情况下，请求将失败并显示 `TransactionCanceledException`。
+ 当完成事务所需的预置容量不足时。
+ 出现用户错误（如数据格式无效）时。

 有关如何处理与 `TransactGetItems` 操作的冲突的更多信息，请参阅 [DynamoDB 中的事务冲突处理](#transaction-conflict-handling)。

## DynamoDB 事务的隔离级别
隔离级别

事务操作的隔离级别（`TransactWriteItems` 或 `TransactGetItems`）和其他操作如下所示。

### 可序列化


*可序列化*隔离可确保多个并发操作的结果相同，就像当前操作在前一个操作完成之前不会开始。

以下操作类型之间具有可序列化隔离：
+ 在任何事务操作与任何标准写入操作（`PutItem`、`UpdateItem` 或 `DeleteItem`）之间。
+ 在任何事务操作与任何标准读取操作 (`GetItem`) 之间。
+ 在 `TransactWriteItems` 操作与 `TransactGetItems` 操作之间。

尽管在事务操作与 `BatchWriteItem` 操作中的每个单独标准写入之间具有可序列化隔离，但作为一个整体，在事务与 `BatchWriteItem` 操作之间没有可序列化隔离。

类似地，事务操作与 `GetItems` 操作中的单个 `BatchGetItem` 之间的隔离级别是可序列化的。但是事务和作为一个单元的 `BatchGetItem` 操作之间的隔离级别是*读取已提交*。

单个 `GetItem` 请求可以采用相对于 `TransactWriteItems` 请求的两种方式序列化，在 `TransactWriteItems` 请求 之前或之后。多个`GetItem`请求，针对并发`TransactWriteItems`请求可以按任何顺序运行，因此结果为*读取已提交*。

例如，如果对项目 A 和项目 B 的 `GetItem` 请求与修改项目 A 和项目 B 的 `TransactWriteItems` 请求同时运行，则有四种可能性：
+ 两个 `GetItem` 请求在 `TransactWriteItems` 请求之前运行。
+ 两个 `GetItem` 请求在 `TransactWriteItems` 请求之后运行。
+ 对项目 A 的 `GetItem` 请求在 `TransactWriteItems` 请求之前运行。对于项目 B，`GetItem` 在 `TransactWriteItems` 之后运行。
+ 对项目 B 的 `GetItem` 请求在 `TransactWriteItems` 请求之前运行。对于项目 A，`GetItem` 在 `TransactWriteItems` 之后运行。

如果对于多个 `GetItem` 请求偏好可序列化隔离级别，则应使用 `TransactGetItems`。

如果对属于传输中的同一事务写入请求的多个项目进行非事务性读取，则有可能能够读取其中一些项目的新状态以及其它项目的旧状态。仅当收到事务性写入的成功响应，指示事务已完成时，您才能读取属于事务写入请求的所有项目的新状态。

在事务成功完成并收到响应后，由于 DynamoDB 的最终一致性模型，后续的*最终一致性* 读取操作仍可能在短时间内返回旧状态。为了保证在事务处理后立即读取最新数据，应通过将 `ConsistentRead` 设置为 true 来使用[*强一致性*](HowItWorks.ReadConsistency.md#HowItWorks.ReadConsistency.Strongly) 读取。

### 读取已提交


*读取已提交*隔离可确保读取操作始终返回项目的已提交值，对于表现出事务写入未最终成功状态的项目，读取操作永远不会对该项目呈现视图。读取已提交隔离无法防止在读取操作后立即修改项目。

隔离级别在任何事务操作与涉及多个标准读取（`BatchGetItem`、`Query` 或 `Scan`）的任何读取操作之间为读取已提交。如果事务写入在 `BatchGetItem`、`Query` 或 `Scan` 操作中间更新了某个项目，则该读取操作接下来的部分将返回新的已提交值（对于 `ConsistentRead)`）或可能是之前的已提交值（最终一致性读取）。

### 操作摘要


简而言之，下表显示事务操作（`TransactWriteItems` 或 `TransactGetItems`）与其他操作之间的隔离级别。


| 操作 | 隔离级别 | 
| --- | --- | 
| `DeleteItem` | *可序列化* | 
| `PutItem` | *可序列化* | 
| `UpdateItem` | *可序列化* | 
| `GetItem` | *可序列化* | 
| `BatchGetItem` | *读取已提交*\$1 | 
| `BatchWriteItem` | *不可序列化*\$1 | 
| `Query` | *读取已提交* | 
| `Scan` | *读取已提交* | 
| 其他事务操作 | *可序列化* | 

标记星号 (\$1) 的级别作为一个整体适用于操作。但是，这些操作内的各个操作具有*可序列化*隔离级别。

## DynamoDB 中的事务冲突处理
事务冲突处理

事务内的项目上的并发项级请求期间可能会发生事务冲突。在以下情况下可能发生事务冲突：
+ 项目的 `PutItem`、`UpdateItem` 或 `DeleteItem` 请求与包含相同项目的正在进行的 `TransactWriteItems` 请求冲突。
+ `TransactWriteItems` 请求中的某个项目是另一个正在进行的 `TransactWriteItems` 请求的一部分。
+ `TransactGetItems` 请求中的某个项目是正在进行的 `TransactWriteItems`、`BatchWriteItem`、`PutItem`、`UpdateItem` 或 `DeleteItem` 请求的一部分。

**注意**  
当 `PutItem`、`UpdateItem` 或 `DeleteItem` 请求被拒绝时，请求会失败，并显示 `TransactionConflictException`。
如果 `TransactWriteItems` 或 `TransactGetItems` 任何项目级别请求被拒绝，则请求将失败并显示 `TransactionCanceledException`。如果该请求失败，Amazon SDK 不重试请求。  
如果您使用的是 适用于 Java 的 Amazon SDK，则该异常包含 [CancellationReasons](https://docs.amazonaws.cn/amazondynamodb/latest/APIReference/API_CancellationReason.html) 列表，该列表根据 `TransactItems` 请求参数中的项目列表排序。对于其他语言，列表的字符串表示形式包含在异常的错误消息中。
但是，如果正在执行的 `TransactWriteItems` 或 `TransactGetItems` 操作与并发 `GetItem` 请求冲突，则这两个操作都会成功。

对于每个失败的项目级别请求，[TransactionConflict CloudWatch metric](https://docs.amazonaws.cn/amazondynamodb/latest/developerguide/metrics-dimensions.html) 指标会递增。

## 在 DynamoDB Accelerator（DAX）中使用事务 API
DAX 中的事务

`TransactWriteItems` 和 `TransactGetItems` 在 DynamoDB Accelerator (DAX) 中都受支持，并且其隔离级别与 DynamoDB 中的相同。

`TransactWriteItems` 通过 DAX 写入。DAX 将一个 `TransactWriteItems` 调用传递到 DynamoDB，并返回响应。为了在写入后填充缓存，DAX 在后台对 `TransactWriteItems` 操作中的每个项目调用 `TransactGetItems`，这将使用额外的读取容量单位。（有关更多信息，请参阅 [事务的容量管理](#transaction-capacity-handling)。） 利用此功能，您可以保持应用程序逻辑的简单性，并将 DAX 同时用于事务操作和非事务操作。

`TransactGetItems` 调用将通过 DAX 进行传递，而不会在本地缓存项目。这与 DAX 中的强一致性读取 API 的行为相同。

## 事务的容量管理
容量管理

无需其他成本即可为 DynamoDB 表启用事务。您只需为作为事务一部分的读取或写入付费。DynamoDB 在事务中对于每个项目执行两次基础读写：一次是准备事务，一次是提交事务。这两个基础读/写操作显示在 Amazon CloudWatch 指标中。

当您为表预配置容量时，规划事务 API 需要的其他读取和写入。例如，假设您的应用程序每秒执行一个事务，并且每个事务在表中写入三个 500 字节的项目。每个项目需要两个写入容量单位 (WCU)：一个用于准备事务，一个用于提交事务。因此，您需要为表预置六个 WCU。

如果您在前面的示例中使用 DynamoDB Accelerator (DAX)，则还将为 `TransactWriteItems` 调用中的每个项目使用两个读取容量单位 (RCU)。因此，您需要为表预配置另外六个 RCU。

同样，如果您的应用程序每秒执行一个读取事务，并且每个事务读取表中三个 500 字节的项目，则您需要为表预置六个读取容量单位 (RCU)。读取每个项目需要两个 RCU：一个用于准备事务，一个用于提交事务。

此外，默认的 SDK 行为是在引发 `TransactionInProgressException` 异常时重试事务。规划这些重试所占用的其他读取容量单位 (RCU)。这同样适用于您使用 `ClientRequestToken` 在您自己的代码中重试事务。

## 事务的最佳实践
最佳实践

使用 DynamoDB 事务时，请考虑以下建议的实践。
+ 对表启用自动扩展功能，或者确保您已预置了足够的吞吐容量，以便为事务中的每个项目执行两个读取或写入操作。
+ 如果您未使用 Amazon 提供的 SDK，则在进行 `TransactWriteItems` 调用时加入 `ClientRequestToken` 属性，以确保请求具有幂等性。
+ 如果不必要，请勿将操作分组在一个事务中。例如，如果具有 10 个操作的单个事务可以分解为多个事务而不会妨碍应用程序正确性，则建议您拆分此事务。更简单的事务可提高吞吐量，且更可能成功。
+ 同时更新相同项目的多个事务可能导致冲突而取消事务。我们建议您遵循 DynamoDB 数据建模的最佳实践，以最大限度地减少此类冲突。
+ 如果一组属性经常跨多个项目作为单个事务的一部分进行更新，则考虑将这些属性分组到一个项目，以减小事务的范围。
+ 避免使用事务来成批注入数据。对于成批写入，使用 `BatchWriteItem` 则更好。

## 将事务 API 与全局表结合使用


事务操作仅在调用写入 API 的 Amazon 区域内提供原子性、一致性、隔离性和持久性（ACID）保证。全局表中不支持跨区域的事务。例如，假设您在美国东部（俄亥俄州）和美国西部（俄勒冈州）区域有一个带副本的全局表，并且您在美国东部（弗吉尼亚州北部）区域执行 `TransactWriteItems` 操作。您可能会在复制更改时观察到美国西部（俄勒冈州）区域内已部分完成的事务。更改仅在源区域中提交后才复制到其它区域。

## DynamoDB 事务与 AWSLabs 事务客户端库
事务与客户端库

DynamoDB 事务为 [AWSLabs](https://github.com/awslabs) 事务客户端库提供了更经济实惠、强大且高性能的替代方式。我们建议您更新应用程序，以使用原生服务器端事务 API。