

# 使用表、项目、查询、扫描和索引
<a name="WorkingWithDynamo"></a>

此部分提供了有关在 Amazon DynamoDB 中处理表、项目、查询及更多内容的详细信息。

**Topics**
+ [使用 DynamoDB 中的表和数据](WorkingWithTables.md)
+ [全局表 - 多活、多区域复制](GlobalTables.md)
+ [使用 DynamoDB 中的项目和属性](WorkingWithItems.md)
+ [在 DynamoDB 中使用二级索引改进数据访问](SecondaryIndexes.md)
+ [使用 DynamoDB 事务管理复杂工作流](transactions.md)
+ [将更改数据捕获与 Amazon DynamoDB 结合使用](streamsmain.md)

# 使用 DynamoDB 中的表和数据
<a name="WorkingWithTables"></a>

本部分介绍如何使用 AWS Command Line Interface (AWS CLI) 和 AWS SDK 在 Amazon DynamoDB 中创建、更新和删除表。

**注意**  
您还可以使用 AWS 管理控制台 执行这些任务。有关更多信息，请参阅 [使用控制台](AccessingDynamoDB.md#ConsoleDynamoDB)。

此部分还提供了有关吞吐容量、DynamoDB Auto Scaling 的使用方式或手动设置预配置吞吐量的更多信息。

**Topics**
+ [针对 DynamoDB 表的基本操作](WorkingWithTables.Basics.md)
+ [在 DynamoDB 中选择表类时的注意事项](WorkingWithTables.tableclasses.md)
+ [在 DynamoDB 中向资源添加标记和标签](Tagging.md)

# 针对 DynamoDB 表的基本操作
<a name="WorkingWithTables.Basics"></a>

类似于其他数据库系统，Amazon DynamoDB 将数据存储在表中。您可以使用一些基本操作来管理表。

**Topics**
+ [创建表](#WorkingWithTables.Basics.CreateTable)
+ [描述表](#WorkingWithTables.Basics.DescribeTable)
+ [更新表](#WorkingWithTables.Basics.UpdateTable)
+ [删除表](#WorkingWithTables.Basics.DeleteTable)
+ [使用删除保护](#WorkingWithTables.Basics.DeletionProtection)
+ [列出表名](#WorkingWithTables.Basics.ListTables)
+ [描述预调配的吞吐量配额](#WorkingWithTables.Basics.DescribeLimits)

## 创建表
<a name="WorkingWithTables.Basics.CreateTable"></a>

使用 `CreateTable` 操作在 Amazon DynamoDB 中创建表。要创建表，您必须提供以下信息：
+ **表名 **() 此名称必须遵循 DynamoDB 命名规则，并且对当前 AWS 账户和区域必须唯一。例如，您可以创建 `People` 表中的美国东部（弗吉尼亚州北部）和另一个 `People` 表在欧洲地区（爱尔兰）。但是，这两个表彼此完全不同。有关更多信息，请参阅 [Amazon DynamoDB 中支持的数据类型和命名规则](HowItWorks.NamingRulesDataTypes.md)。
+ **主键。**主键可包含一个属性（分区键）或两个属性（分区键和排序键）。您需要提供每个属性的属性名称、数据类型和角色：`HASH`（针对分区键）和 `RANGE`（针对排序键）。有关更多信息，请参阅 [主键](HowItWorks.CoreComponents.md#HowItWorks.CoreComponents.PrimaryKey)。
+ **吞吐量设置（对于预置表）。**如果使用预置模式，则必须指定表的初始读取和写入吞吐量设置。您可以稍后修改这些设置，或启用 DynamoDB Auto Scaling 以管理设置。有关更多信息，请参阅[DynamoDB 预置容量模式](provisioned-capacity-mode.md)和[使用 DynamoDB Auto Scaling 自动管理吞吐能力](AutoScaling.md)。

### 示例 1：创建按需表
<a name="create-payperrequest-example"></a>

使用按需模式创建同一个表 `Music`。

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --billing-mode=PAY_PER_REQUEST
```

`CreateTable` 操作返回表的元数据，如下所示。

```
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/Music",
        "AttributeDefinitions": [
            {
                "AttributeName": "Artist",
                "AttributeType": "S"
            },
            {
                "AttributeName": "SongTitle",
                "AttributeType": "S"
            }
        ],
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "WriteCapacityUnits": 0,
            "ReadCapacityUnits": 0
        },
        "TableSizeBytes": 0,
        "TableName": "Music",
        "BillingModeSummary": {
            "BillingMode": "PAY_PER_REQUEST"
        },
        "TableStatus": "CREATING",
        "TableId": "12345678-0123-4567-a123-abcdefghijkl",
        "KeySchema": [
            {
                "KeyType": "HASH",
                "AttributeName": "Artist"
            },
            {
                "KeyType": "RANGE",
                "AttributeName": "SongTitle"
            }
        ],
        "ItemCount": 0,
        "CreationDateTime": 1542397468.348
    }
}
```

**重要**  
 当对按需表调用 `DescribeTable` 时，读取容量单位和写入容量单位设置为 0。

### 示例 2：创建预置表
<a name="create-provisioned-example"></a>

以下 AWS CLI 示例说明了如何创建表 (`Music`)。主键包含 `Artist`（分区键）和 `SongTitle`（排序键），它们均具有 `String` 数据类型。此表的最大吞吐量为 10 个读取容量单位和 5 个写入容量单位。

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5
```

`CreateTable` 操作返回表的元数据，如下所示。

```
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/Music",
        "AttributeDefinitions": [
            {
                "AttributeName": "Artist",
                "AttributeType": "S"
            },
            {
                "AttributeName": "SongTitle",
                "AttributeType": "S"
            }
        ],
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "WriteCapacityUnits": 5,
            "ReadCapacityUnits": 10
        },
        "TableSizeBytes": 0,
        "TableName": "Music",
        "TableStatus": "CREATING",
        "TableId": "12345678-0123-4567-a123-abcdefghijkl",
        "KeySchema": [
            {
                "KeyType": "HASH",
                "AttributeName": "Artist"
            },
            {
                "KeyType": "RANGE",
                "AttributeName": "SongTitle"
            }
        ],
        "ItemCount": 0,
        "CreationDateTime": 1542397215.37
    }
}
```

这些区域有：`TableStatus` 元素指示表的当前状态 (`CREATING`)。创建表可能需要一段时间，具体取决于您为 `ReadCapacityUnits` 和 `WriteCapacityUnits` 指定的值。二者的值越大，DynamoDB 需要为表分配的资源就越多。

### 示例 3：使用“DynamoDB 标准 – 不频繁访问”表类别创建表
<a name="create-infrequent-access-example"></a>

要使用“DynamoDB 标准-不经常访问”表类别创建相同的 `Music` 表。

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --table-class STANDARD_INFREQUENT_ACCESS
```

`CreateTable` 操作返回表的元数据，如下所示。

```
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/Music",
        "AttributeDefinitions": [
            {
                "AttributeName": "Artist",
                "AttributeType": "S"
            },
            {
                "AttributeName": "SongTitle",
                "AttributeType": "S"
            }
        ],
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0,
            "WriteCapacityUnits": 5,
            "ReadCapacityUnits": 10
        },
        "TableClassSummary": {
            "LastUpdateDateTime": 1542397215.37,
            "TableClass": "STANDARD_INFREQUENT_ACCESS"
        },
        "TableSizeBytes": 0,
        "TableName": "Music",
        "TableStatus": "CREATING",
        "TableId": "12345678-0123-4567-a123-abcdefghijkl",
        "KeySchema": [
            {
                "KeyType": "HASH",
                "AttributeName": "Artist"
            },
            {
                "KeyType": "RANGE",
                "AttributeName": "SongTitle"
            }
        ],
        "ItemCount": 0,
        "CreationDateTime": 1542397215.37
    }
}
```

## 描述表
<a name="WorkingWithTables.Basics.DescribeTable"></a>

要查看有关表的详细信息，请使用 `DescribeTable` 操作。您必须提供表名称。`DescribeTable` 的输出格式与 `CreateTable` 相同。它包括表创建时的时间戳、表的键架构、预配置吞吐量设置、表的估计大小以及存在的所有二级索引。

**重要**  
 当对按需表调用 `DescribeTable` 时，读取容量单位和写入容量单位设置为 0。

**Example**  

```
aws dynamodb describe-table --table-name Music
```

当 `TableStatus` 从 `CREATING` 更改为 `ACTIVE` 后，表即可供使用。

**注意**  
如果发出 `CreateTable` 请求后立即请求 `DescribeTable`，DynamoDB 可能会返回一个错误 (`ResourceNotFoundException`)。这是因为 `DescribeTable` 使用最终一致查询，并且表的元数据在此时可能不可用。请等待几秒钟，再尝试 `DescribeTable` 请求。  
出于记账目的，您的 DynamoDB 存储成本包含 100 字节的每项目开销。（有关更多信息，请转到 [DynamoDB 定价](https://aws.amazon.com/dynamodb/pricing/)。） 每项目的此额外 100 字节不会用于容量单位计算或用于 `DescribeTable` 操作。

## 更新表
<a name="WorkingWithTables.Basics.UpdateTable"></a>

利用 `UpdateTable` 操作，您可以执行下列操作之一：
+ 修改表的预置吞吐量设置（对于预置模式表）。
+ 更改表的读/写容量模式。
+ 在表上操作全局二级索引（请参阅 [在 DynamoDB 中使用全局二级索引](GSI.md)）。
+ 在表上启用或禁用 DynamoDB Streams（请参阅 [将更改数据捕获用于 DynamoDB Streams](Streams.md)）。

**Example**  
下面的 AWS CLI 示例说明如何修改表的预置吞吐量设置。  

```
aws dynamodb update-table --table-name Music \
    --provisioned-throughput ReadCapacityUnits=20,WriteCapacityUnits=10
```

**注意**  
在发出 `UpdateTable` 请求时，表的状态从 `AVAILABLE` 变为 `UPDATING`。该表仍然完全可用，而它是 `UPDATING`。完成此过程后，表状态将从 `UPDATING` 到 `AVAILABLE`。

**Example**  
下面的 AWS CLI 示例说明如何将表的读/写容量模式修改为按需模式。  

```
aws dynamodb update-table --table-name Music \
    --billing-mode PAY_PER_REQUEST
```

## 删除表
<a name="WorkingWithTables.Basics.DeleteTable"></a>

您可以通过 `DeleteTable` 操作移除未使用的表格。表的删除操作是不可恢复的。要使用 AWS 管理控制台删除表，请参阅[第 6 步：（可选）删除 DynamoDB 表以清理资源](getting-started-step-6.md)。

**Example**  
下面的 AWS CLI 示例说明如何删除表。  

```
aws dynamodb delete-table --table-name Music
```

在您发出 `DeleteTable` 请求时，表的状态从 `ACTIVE` 变为 `DELETING`。删除表可能需要一段时间，具体取决于它使用的资源（例如表中存储的数据以及表上的任何流或索引）。

在 `DeleteTable` 操作结束时，该表在 DynamoDB 中不再存在。

## 使用删除保护
<a name="WorkingWithTables.Basics.DeletionProtection"></a>

您可以使用删除保护属性保护表免遭意外删除。为表启用此属性有助于确保在管理员执行常规表管理操作期间不会意外删除表。这将有助于防止您的常规业务运营受到干扰。

 表所有者或授权管理员控制每个表的删除保护属性。默认情况下，每个表的删除保护属性为关闭状态。这包括全局副本和从备份中恢复的表。禁用表的删除保护后，任何获得 Identity and Access Management (IAM) 策略授权的用户都可以删除该表。当表启用了删除保护时，任何人都无法将其删除。

要更改此设置，请转到表的**其他设置**，导航到**删除保护**面板，然后选择**启用删除保护**。

DynamoDB 控制台、API、CLI/SDK 和 CloudFormation 都支持删除保护属性。`CreateTable` API 在创建表时支持删除保护属性，并且 `UpdateTable` API 支持更改现有表的删除保护属性。

**注意**  
如果删除了某个 AWS 账户，则该账户的所有数据（包括表）仍会在 90 天内被删除。
如果 DynamoDB 无法访问用于加密表的客户托管密钥，DynamoDB 仍将存档此表。存档包括备份表和删除原始表。

## 列出表名
<a name="WorkingWithTables.Basics.ListTables"></a>

`ListTables` 操作返回当前 AWS 账户和区域的 DynamoDB 表的名称。

**Example**  
下面的 AWS CLI 示例演示如何列出 DynamoDB 表名称。  

```
aws dynamodb list-tables
```

## 描述预调配的吞吐量配额
<a name="WorkingWithTables.Basics.DescribeLimits"></a>

`DescribeLimits` 操作会返回当前 AWS 账户和区域的当前读入容量配额。

**Example**  
下面的 AWS CLI 示例说明了如何描述当前的预置吞吐量配额。  

```
aws dynamodb describe-limits
```
输出显示当前 AWS 账户和区域的读入容量单位配额。

有关这些配额以及如何请求增加配额的更多信息，请参阅 [吞吐量默认限额](ServiceQuotas.md#default-limits-throughput)。

# 在 DynamoDB 中选择表类时的注意事项
<a name="WorkingWithTables.tableclasses"></a>

DynamoDB 提供两个表类别，旨在帮助您优化成本。“DynamoDB 标准”表类别是默认设置，建议用于绝大多数工作负载。“DynamoDB 标准-不经常访问 (DynamoDB Standard-IA)”表类别针对存储占据主要成本的表进行优化。例如，存储不经常访问数据的表（例如应用程序日志、旧的社交媒体帖子、电子商务订单历史记录以及过去的游戏成就）就适合使用 Standard-IA 表类别。

每个 DynamoDB 表都与一个表类别关联。与该表关联的所有二级索引都使用相同的表类。您可以在创建表时设置表类别（原定设置为“DynamoDB 标准”），然后使用 AWS 管理控制台、AWS CLI 或 AWS SDK 更新现有表的表类别。DynamoDB 还支持使用面向单区域表（非全局表）的 AWS CloudFormation 管理表类别。每个表类别都为数据存储以及读取和写入请求提供不同的定价。在为您的表选择表类别时，请注意以下几点：
+ DynamoDB 标准表类别提供的吞吐量成本低于 DynamoDB Standard-IA，对于吞吐量占据主要成本的表来说，是最具成本效益的选择。
+ DynamoDB Stand-IA 表类别提供的存储成本低于 DynamoDB Standard，对于存储成本占据主要成本的表来说，是最具成本效益的选择。当存储超过使用 DynamoDB 标准表类别的表吞吐量（读取和写入）成本的 50% 时，DynamoDB Standard-IA 表类别可以帮助您降低总体表成本。
+ DynamoDB 标准-IA 表提供与 DynamoDB 标准表相同的性能、耐久性和可用性。
+ 在 DynamoDB 标准和 DynamoDB 标准-IA 表类之间切换不需要更改应用程序代码。无论表使用哪种表类，您都可以使用相同的 DynamoDB API 和服务端点。
+ DynamoDB 标准-IA 表与所有现有 DynamoDB 功能兼容，例如自动扩缩、按需模式、生存时间（TTL）、按需备份、时间点恢复（PITR）和全局二级索引。

对于表而言，最具成本效益的表类别取决于表的预期存储和吞吐量使用模式。您可以通过 AWS 成本和使用情况报告以及 AWS Cost Explorer 查看表的历史存储和吞吐量成本以及使用情况。使用此历史数据为表确定最具成本效益的表类别。要了解有关使用 AWS 成本和使用情况报告以及 AWS Cost Explorer 的更多信息，请参阅 [AWS 计费和成本管理文档](https://docs.aws.amazon.com/account-billing/index.html)。请参阅 [Amazon DynamoDB 定价](https://aws.amazon.com/dynamodb/pricing/on-demand/)了解有关表类别定价的详细信息。

**注意**  
表类别更新是一个后台进程。在表类别更新期间，您仍然可以正常访问表。更新表类别的时间取决于表流量、存储大小和其他相关变量。在 30 天的跟踪时间内，不允许对表进行两次以上的表类别更新。

# 在 DynamoDB 中向资源添加标记和标签
<a name="Tagging"></a>

您可 Amazon DynamoDB 用*标签*。标签可让您按各种方法对资源进行分类，例如，按用途、所有者、环境或其他标准。标签可帮助您：
+ 根据您分配到资源的标签来快速识别资源。
+ 按标签查看 AWS 账单细分。
**注意**  
与添加了标签的表相关的任意本地二级索引 (LSI) 和全局二级索引 (GSI) 会自动使用相同的标签。目前，无法为 DynamoDB Streams 使用情况添加标签。

支持添加标签 AWS 服务，如 Amazon EC2、Amazon S3、DynamoDB 等。有效的标签让您可对具有特定标签的服务创建报告，从而提供成本分析。

要开始使用标签，请执行以下操作：

1. 了解 [DynamoDB 中的标签限制](#TaggingRestrictions)。

1. 使用 [在 DynamoDB 中为资源添加标签](Tagging.Operations.md) 创建标签。

1. 使用 [使用 DynamoDB 标记创建成本分配报告](#CostAllocationReports) 跟踪各个活动标签的 AWS 成本。

最后，最佳实践是遵循最佳标签策略。有关信息，请参阅 [AWS 标记策略](https://d0.awsstatic.com/aws-answers/AWS_Tagging_Strategies.pdf)。

## DynamoDB 中的标签限制
<a name="TaggingRestrictions"></a>

 每个标签都由密钥和值组成，这两个参数都由您指定。以下限制适用：
+  每个 DynamoDB 表的同一个键只能有一个标签。如果您尝试添加现有标签（相同键），现有标签值会更新为新值。
+  标签键和值区分大小写。
+  最大键长度为 128 个 Unicode 字符。
+ 最大值长度为 256 个 Unicode 字符。
+  允许的字符包括字母、空格和数字，以及以下特殊字符：`+ - = . _ : /`
+  每个资源的最大标签数是 50。
+ 表中所有标签支持的最大大小为 10 KB。
+ AWS 分配的标签名称和值将自动被分配 `aws:` 前缀，您无法分配该前缀。AWS 分配的标签名称不计入标签限制 50 或最大大小限制 10 K。用户分配的标签名称在成本分配报告中具有 `user:` 前缀。
+  您不能回溯标签的应用日期。

# 在 DynamoDB 中为资源添加标签
<a name="Tagging.Operations"></a>

您可以使用 Amazon DynamoDB 控制台或 AWS Command Line Interface (AWS CLI) 添加、列出、编辑或删除标签。然后，您可以激活这些用户定义的标签，以便在 AWS 账单与成本管理 控制台上显示这些标签以进行成本分配跟踪。有关更多信息，请参阅 [使用 DynamoDB 标记创建成本分配报告](Tagging.md#CostAllocationReports)。

 对于批量编辑，您还可以使用 AWS 管理控制台 中的标签编辑器。有关更多信息，请参阅[使用标签编辑器](https://docs.aws.amazon.com/awsconsolehelpdocs/latest/gsg/tag-editor.html)。

 要改用 DynamoDB API，请参阅 [Amazon DynamoDB API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)的以下操作：
+ [TagResource](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TagResource.html)
+ [UntagResource](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UntagResource.html)
+ [ListTagsOfResource](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTagsOfResource.html)

**Topics**
+ [设置按标签筛选的权限](#Tagging.Operations.permissions)
+ [将标签添加到新的或现有的表 (AWS 管理控制台)](#Tagging.Operations.using-console)
+ [将标签添加到新的或现有的表 (AWS CLI)](#Tagging.Operations.using-cli)

## 设置按标签筛选的权限
<a name="Tagging.Operations.permissions"></a>

要在 DynamoDB 控制台中使用标签筛选表列表，请确保用户的策略包括对以下操作的访问权限：
+ `tag:GetTagKeys`
+ `tag:GetTagValues`

您可以通过按照以下步骤，向您的用户附加新的 IAM policy 来访问这些操作。

1. 以管理员用户身份登录 [IAM 控制台](https://console.aws.amazon.com/iam/)。

1. 在左侧导航菜单中，选择“策略”。

1. 选择“创建策略”。

1. 将以下策略粘贴到 JSON 编辑器中。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "tag:GetTagKeys",
                   "tag:GetTagValues"
               ],
               "Resource": "*"
           }
       ]
   }
   ```

------

1. 完成向导并为策略指定名称（例如，`TagKeysAndValuesReadAccess`）。

1. 在左侧导航菜单中，选择“用户”。

1. 从该列表中选择您通常用于访问 DynamoDB 控制台的用户。

1. 选择“添加权限”。

1. 选择“直接附加现有策略”。

1. 从列表中选择您之前创建的策略。

1. 完成向导。

## 将标签添加到新的或现有的表 (AWS 管理控制台)
<a name="Tagging.Operations.using-console"></a>

您可以使用 DynamoDB 控制台在创建新表时在表中添加标签，或添加、编辑或删除现有表的标签。

**在创建时标记资源（控制台）**

1. 登录 AWS 管理控制台，打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 在导航窗格中，选择 **Tables (表)**，然后选择 **Create table (创建表)**。

1. 在 **Create DynamoDB table (创建 DynamoDB 表)** 页面上，提供名称和主键。在 **Tags (标签)** 部分，选择 **Add new tag (添加新标签)**，然后输入要使用的标签。

   有关标签结构的信息，请参阅 [DynamoDB 中的标签限制](Tagging.md#TaggingRestrictions)。

   有关创建表的更多信息，请参阅 [针对 DynamoDB 表的基本操作](WorkingWithTables.Basics.md)。

**标记现有资源（控制台）**

打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 在导航窗格中，选择**表**。

1. 选择列表中的表，然后选择 **Additional settings (其他设置)** 选项卡。在页面底部的 **Tags(标签)** 部分中，可以添加、编辑或删除标签。

## 将标签添加到新的或现有的表 (AWS CLI)
<a name="Tagging.Operations.using-cli"></a>

以下示例说明了如何在创建表和索引时使用 AWS CLI 指定标签以及标记现有的资源。

**在创建时标记资源 (AWS CLI)**
+ 以下示例创建一个新的 `Movies` 表，并添加具有 `Owner` 值的 `blueTeam` 标签：

  ```
  aws dynamodb create-table \
      --table-name Movies \
      --attribute-definitions AttributeName=Title,AttributeType=S \
      --key-schema AttributeName=Title,KeyType=HASH \
      --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
      --tags Key=Owner,Value=blueTeam
  ```

**标记现有的资源 (AWS CLI)**
+ 以下示例为 `Owner` 表添加具有 `blueTeam` 值的 `Movies` 标签：

  ```
  aws dynamodb tag-resource \
      --resource-arn arn:aws:dynamodb:us-east-1:123456789012:table/Movies \
      --tags Key=Owner,Value=blueTeam
  ```

**列出表的所有标签 (AWS CLI)**
+ 以下示例列出与 `Movies` 表关联的所有标签：

  ```
  aws dynamodb list-tags-of-resource \
      --resource-arn arn:aws:dynamodb:us-east-1:123456789012:table/Movies
  ```

## 使用 DynamoDB 标记创建成本分配报告
<a name="CostAllocationReports"></a>

AWS 使用标签组织成本分配报告上的资源成本。AWS 提供了两种类型的成本分配标签：
+ AWS 生成的标签 AWS 为您定义、创建和应用此标签。
+ 用户定义的标签。您定义、创建和应用这些标签。

您必须先分别激活两种类型的标签，然后这些标签才能显示在 Cost Explorer 中或成本分配报告上。

 要激活 AWS 生成的标签：

1.  登录 AWS 管理控制台，打开 Billing and Cost Management 控制台：[https://console.aws.amazon.com/billing/home\$1/](https://console.aws.amazon.com/billing/home#/.)。

1.  在导航窗格中，选择 **Cost Allocation Tags (成本分配标签)**。

1.  在 **AWS 生成的成本分配标签**下，选择**激活**。

 激活用户定义的标签：

1.  登录 AWS 管理控制台，打开 Billing and Cost Management 控制台：[https://console.aws.amazon.com/billing/home\$1/](https://console.aws.amazon.com/billing/home#/.)。

1.  在导航窗格中，选择 **Cost Allocation Tags (成本分配标签)**。

1.  在 **User-Defined Cost Allocation Tags (用户生成的成本分配标签)** 下，选择 **Activate (激活)**。

 创建并激活标签后，AWS 生成成本分配报告，其中按活动标签分组了使用情况和成本。成本分配报告包括您每个账单周期的所有 AWS 成本。该报告包括标记资源和未标记资源，因此您可以清晰地排列资源费用。

**注意**  
 目前，从 DynamoDB 传出的所有数据不会在成本分配报告上按标签细分。

 有关更多信息，请参阅[使用成本分配标签](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-alloc-tags.html)。

# 全局表 - 多活、多区域复制
<a name="GlobalTables"></a>

*Amazon DynamoDB 全局表*是一项完全托管式、多区域和多活数据库功能，它为全局扩展的应用程序提供易于使用的数据复制以及快速的本地读写性能。

全局表可以跨 AWS 区域以及（可选）跨 AWS 账户自动复制 DynamoDB 表数据，而无需您构建和维护自己的复制解决方案。全局表非常适合需要通过多区域部署来实现业务连续性和高可用性的应用程序。任何全局表副本都可以处理读写操作。如果应用程序处理在某个区域中断，应用程序可以通过将流量转移到其它区域，来实现恢复点目标（RPO）较低或为零的高韧性。在 DynamoDB 可用的所有区域中均可使用全局表。

## 一致性模式
<a name="GlobalTables.consistency-modes"></a>

创建全局表时，可以配置其一致性模式。全局表支持两种一致性模式：多区域最终一致性（MREC）和多区域强一致性（MRSC）。

如果您在创建全局表时未指定一致性模式，则全局表默认为多区域最终一致性（MREC）。全局表不能包含配置了不同一致性模式的副本。创建全局表后，无法更改其一致性模式。

## 账户配置
<a name="GlobalTables.account-configurations"></a>

DynamoDB 现在支持两种全局表模型，每种模型都设计用于不同的架构模式：
+ **同账户全局表**：在单个 AWS 账户中创建和管理所有副本。
+ **多账户全局表**：副本跨多个 AWS 账户部署，并加入到共享复制组中。

同账户和多账户模型都支持多区域写入、异步复制、以最后写入者为准冲突解决方案，并采用相同的计费模型。但是，这两种模型在账户、权限、加密和表治理的管理方式上有所不同。

为 MRSC 配置的全局表仅支持同账户配置。

您可以使用 AWS 管理控制台来配置全局表。全局表使用现有的 DynamoDB API 来读取表中的数据和将数据写入表中，因此无需对应用程序进行任何更改。您只需为您预置或使用的资源付费，而无需支付任何前期费用，也没有任何承诺。


| ** 属性** | **同账户全局表** | **多账户全局表** | 
| --- | --- | --- | 
| 主要使用案例 | 为单个 AWS 账户内的应用程序实现多区域韧性 | 为由不同团队、不同业务部门或不同账户（具有严格的跨账户安全边界）拥有的应用程序实现多区域、多账户复制 | 
| 账户模型 | 在一个 AWS 账户中创建和管理所有副本 | 在同一个部署中跨多个 AWS 账户创建副本 | 
| 资源所有权 | 一个账户拥有表和所有副本 | 每个账户都拥有其本地副本；复制组跨多个账户 | 
| 支持的版本 | 全局表版本 2019.11.21（当前版本）和版本 2017.11.29（旧版） | 全局表版本 2019.11.21（当前版本） | 
| 控制面板操作 | 通过表拥有者账户创建、修改和删除副本 | 分布式控制面板操作：账户加入或退出复制组 | 
| 数据面板操作 | 每个区域的标准 DynamoDB 端点 | 各账户/区域的数据面板访问；通过复制组进行路由 | 
| 安全边界 | 单个 IAM 和 KMS 边界 | 各账户有不同的 IAM、KMS、计费、CloudTrail 和治理方式 | 
| 适用于 | 对表拥有集中所有权的组织 | 具有联合团队、治理边界或多账户设置的组织 | 

**Topics**
+ [一致性模式](#GlobalTables.consistency-modes)
+ [账户配置](#GlobalTables.account-configurations)
+ [全局表核心概念](globaltables-CoreConcepts.md)
+ [DynamoDB 同账户全局表](globaltables-SameAccount.md)
+ [DynamoDB 多账户全局表](globaltables-MultiAccount.md)
+ [了解 Amazon DynamoDB 全局表计费](global-tables-billing.md)
+ [DynamoDB 全局表版本](V2globaltables_versions.md)
+ [全局表的最佳实践](globaltables-bestpractices.md)

# 全局表核心概念
<a name="globaltables-CoreConcepts"></a>

以下各节介绍 Amazon DynamoDB 中全局表的概念和行为

## 概念
<a name="globaltables-CoreConcepts.KeyConcepts"></a>

*全局表*是一项 DynamoDB 功能，可跨 AWS 区域复制表数据。

*副本表*（或副本）是一个 DynamoDB 表，它作为全局表的一部分发挥作用。一个全局表由跨不同 AWS 区域的两个或更多副本表组成。对于每个 AWS 区域，每个全局表只能有一个副本。全局表中的所有副本共享相同的表名称、主键架构和项目数据。

当应用程序将数据写入一个区域中的副本时，DynamoDB 会自动将写入内容复制到全局表中的所有其它副本。有关如何开始使用全局表的更多信息，请参阅[教程：创建全局表](V2globaltables.tutorial.md)或[教程：创建多账户全局表](V2globaltables_MA.tutorial.md)。

## 版本
<a name="globaltables-CoreConcepts.Versions"></a>

DynamoDB 全局表有两个版本：[全局表版本 2019.11.21（当前版）](GlobalTables.md)和[全局表版本 2017.11.29（旧版）](globaltables.V1.md)。您应尽可能使用全局表版本 2019.11.21（当前版本）。文档这一部分的信息适用于版本 2019.11.21（当前版）。有关更多信息，请参阅[确定全局表的版本](V2globaltables_versions.md#globaltables.DetermineVersion)。

## 可用性
<a name="globaltables-CoreConcepts.availability"></a>

全局表可以更轻松地实施多区域高可用性架构，从而有助于提高业务连续性。如果单个 AWS 区域中的工作负载受损，则可以将应用程序流量转移到不同的区域，并对同一个全局表中的其它副本表执行读取和写入操作。

全局表中的每个副本表都提供与单区域 DynamoDB 表相同的持久性和可用性。全局表提供 99.999% 的可用性[服务水平协议（SLA）](https://aws.amazon.com//dynamodb/sla/)，相比之下，单区域表提供 99.99% 的可用性。

## 故障注入测试
<a name="fault-injection-testing"></a>

MREC 和 MRSC 全局表都与 [AWS 故障注入服务](https://docs.aws.amazon.com/resilience-hub/latest/userguide/testing.html)（AWS FIS）集成，这是一项完全托管式服务，用于运行受控的故障注入实验，以提高应用程序的弹性。使用 AWS FIS，您可以：
+ 创建用于定义特定故障场景的实验模板。
+ 通过模拟区域隔离（即暂停与选定副本之间的复制）来测试错误处理、恢复机制和一个 AWS 区域出现中断时的多区域流量转移行为，从而注入故障以验证应用程序的弹性。

例如，在一个在美国东部（弗吉尼亚州北部）、美国东部（俄亥俄州）和美国西部（俄勒冈州）有副本的全局表中，您可以在美国东部（俄亥俄州）运行一个实验以测试此处的区域隔离，而美国东部（弗吉尼亚州北部）和美国西部（俄勒冈州）继续正常运行。这种受控测试有助于确定并解决潜在的问题，以防这些问题影响到生产工作负载。

有关 AWS FIS 支持的操作的完整列表，请参阅《AWS FIS 用户指南》**中的[操作目标](https://docs.aws.amazon.com/fis/latest/userguide/action-sequence.html#action-targets)；有关如何暂停 DynamoDB 在区域之间复制，请参阅[跨区域连接](https://docs.aws.amazon.com/fis/latest/userguide/cross-region-scenario.html)。

有关 AWS FIS 中可用的 Amazon DynamoDB 全局表操作的信息，请参阅《AWS FIS 用户指南》**中的 [DynamoDB 全局表操作参考](https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html#dynamodb-actions-reference)。

要开始运行故障注入实验，请参阅《AWS FIS 用户指南》中的[计划您的 AWS FIS 实验](https://docs.aws.amazon.com/fis/latest/userguide/getting-started-planning.html)。

**注意**  
在 MRSC 中进行 AWS FIS 实验期间，允许最终一致性读取，但不支持更新表设置（例如更改计费模式或配置表吞吐量），这与 MREC 类似。请检查 CloudWatch 指标 [`FaultInjectionServiceInducedErrors`](metrics-dimensions.md#FaultInjectionServiceInducedErrors)，以了解有关错误代码的更多详细信息。

## 生存时间（TTL）
<a name="global-tables-ttl"></a>

配置为 MREC 的全局表支持配置[生存时间](TTL.md)（TTL）删除。全局表中所有副本的 TTL 设置会自动同步。当 TTL 从某个区域的副本中删除项目时，该删除操作会复制到全局表中的所有其它副本。TTL 不占用写入容量，因此您无需为发生删除的区域中的 TTL 删除付费。但是，对于全局表中具有副本的每个其它区域，您需要对复制的删除操作付费。

对于要向其复制删除操作的副本，TTL 删除复制会占用写入容量。如果写入吞吐量和 TTL 删除吞吐量的组合高于预置的写入容量，则配置为预置容量的副本可能会限制请求。

配置为多区域强一致性（MRSC）的全局表不支持配置生存时间（TTL）删除。

## 流
<a name="global-tables-streams"></a>

配置为多区域最终一致性（MREC）的全局表可通过从副本表上的 [DynamoDB 流](Streams.md)读取这些更改，并将该更改应用于所有其它副本表，从而复制更改。因此，默认情况下，对 MREC 全局表中的所有副本都启用了流，并且无法在这些副本上禁用流。MREC 复制过程可能会在短时间内将多项更改合并到单个复制写入操作中，从而导致每个副本的流包含的记录略有不同。MREC 副本上的 Streams 记录会维护对同一个项目的所有更改的顺序，但在各个副本之间，对不同项目进行更改的相对顺序可能会不同。

如果您要编写一个应用程序，用于处理全局表中在特定区域（而非其它区域）发生的更改的流记录，则可以为每个项目添加一个属性，以定义该项目的更改发生在哪个区域。您可以使用此属性来筛选流记录，以了解在其它区域中发生的更改，包括使用 Lambda 事件筛选条件仅针对特定区域中的更改调用 Lambda 函数。

配置为多区域强一致性（MRSC）的全局表不使用 DynamoDB Streams 进行复制，因此默认情况下不在 MRSC 副本上启用流。您可以在 MRSC 副本上启用流。MRSC 副本上的流记录对于每个副本都是相同的，包括流记录排序。

## 事务
<a name="global-tables-transactions"></a>

在配置为 MREC 的全局表上，DynamoDB 事务操作（[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 和 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html)）仅在调用该操作的区域内才是原子操作。事务性写入不会作为一个单元跨区域复制，这意味着在给定时间点，其它副本中的读取操作可能只会返回事务中的部分写入内容。

例如，如果您有一个全局表，该表在美国东部（俄亥俄州）和美国西部（俄勒冈州）区域中具有副本，并且在美国东部（俄亥俄州）区域中执行 `TransactWriteItems` 操作，则在复制更改时，可能会在美国西部（俄勒冈州）区域观察到部分完成的事务。更改仅在源区域中提交后才会复制到其它区域。

配置为多区域强一致性（MRSC）的全局表不支持事务操作，如果对 MRSC 副本调用这些操作，则会返回错误。

## 读写吞吐量
<a name="globaltables-CoreConcepts.Throughput"></a>

### 预置模式
<a name="gt_throughput.provisioned"></a>

复制会消耗写入容量。如果应用程序写入吞吐量和复制写入吞吐量的组合超过了预置的写入容量，则配置为预置容量的副本可能会限制请求。对于使用预置模式的全局表，读取和写入容量的自动扩缩设置将在副本之间同步。

可以通过在副本级别使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ProvisionedThroughputOverride.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ProvisionedThroughputOverride.html) 参数，为全局表中的每个副本独立配置读取容量设置。默认情况下，对预置读取容量的更改将应用于全局表中的所有副本。向全局表添加新副本时，除非明确指定了副本级别覆盖，否则将使用源表或副本的读取容量作为初始值。

### 按需模式
<a name="gt_throughput.on-demand"></a>

对于配置为按需模式的全局表，写入容量会在所有副本之间自动同步。DynamoDB 会根据流量自动调整容量，并且无需管理特定于副本的读取或写入容量设置。

## 监控全局表
<a name="monitoring-global-tables"></a>

配置为多区域最终一致性（MREC）的全局表会将 [`ReplicationLatency`](metrics-dimensions.md#ReplicationLatency) 指标发布到 CloudWatch。此指标跟踪从一个项目写入到副本表到该项目出现在全局表的另一个副本中之间经过的时间。`ReplicationLatency` 以毫秒表示，并针对全局表中的每个源区域和目标区域对发出。

典型的 `ReplicationLatency` 值取决于所选 AWS 区域之间的距离以及工作负载类型和吞吐量等其它变量。例如，与非洲（开普敦）（af-south-1）区域相比，美国西部（北加利福尼亚）（us-west-1）区域的源副本到美国西部（俄勒冈州）（us-west-2）区域的 `ReplicationLatency` 更低。

`ReplicationLatency` 值不断增加可能表明来自一个副本的更新没有及时传播到其它副本表。在这种情况下，可以临时将应用程序的读取和写入活动重定向到不同的 AWS 区域。

配置为多区域强一致性（MRSC）的全局表不发布 `ReplicationLatency` 指标。

## 管理全局表的注意事项
<a name="management-considerations"></a>

在创建新的全局表副本后的 24 小时内，您无法删除用于添加该新副本的表。

如果您禁用包含全局表副本的 AWS 区域，则在禁用该区域的 20 小时后，这些副本将永久转换为单区域表。

# DynamoDB 同账户全局表
<a name="globaltables-SameAccount"></a>

同账户全局表可以在单个 AWS 中，跨 AWS 区域自动复制 DynamoDB 表数据。同账户全局表为运行多区域应用程序提供了最简单的模型，因为所有副本共享相同的账户边界、所有权和权限模型。当您为表副本选择 AWS 区域时，全局表会自动处理所有复制。在 DynamoDB 可用的所有区域中均可使用全局表。

同账户全局表具有以下优点：
+ 跨您选择的 AWS 区域自动复制 DynamoDB 表数据，以便将数据放置在离用户更近的位置
+ 在区域隔离或降级期间实现更高的应用程序可用性
+ 使用内置冲突解决方法，让您能够专注于应用程序的业务逻辑
+ 创建同账户全局表时，您可以选择[多区域最终一致性（MREC）](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes.mrec)或[多区域强一致性（MRSC）](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes.mrsc)

**Topics**
+ [DynamoDB 全局表工作原理](V2globaltables_HowItWorks.md)
+ [教程：创建全局表](V2globaltables.tutorial.md)
+ [DynamoDB 全局表安全性](globaltables-security.md)

# DynamoDB 全局表工作原理
<a name="V2globaltables_HowItWorks"></a>

以下各节介绍 Amazon DynamoDB 中全局表的概念和行为。

## 概念
<a name="V2globaltables_HowItWorks.KeyConcepts"></a>

*全局表*是一项 DynamoDB 功能，可跨 AWS 区域复制表数据。

*副本表*（或副本）是一个 DynamoDB 表，它作为全局表的一部分发挥作用。一个全局表由跨不同 AWS 区域的两个或更多副本表组成。对于每个 AWS 区域，每个全局表只能有一个副本。全局表中的所有副本共享相同的表名称、主键架构和项目数据。

当应用程序将数据写入一个区域中的副本时，DynamoDB 会自动将写入内容复制到全局表中的所有其它副本。有关如何开始使用全局表的更多信息，请参阅 [教程：创建全局表](V2globaltables.tutorial.md)。

## 版本
<a name="V2globaltables_HowItWorks.versions"></a>

DynamoDB 全局表有两个版本可用：版本 2019.11.21（当前版）和[版本 2017.11.29（旧版）](globaltables.V1.md)。您应尽可能使用版本 2019.11.21（当前版）。文档这一部分的信息适用于版本 2019.11.21（当前版）。有关更多信息，请参阅 [确定全局表的版本](V2globaltables_versions.md#globaltables.DetermineVersion)。

## 可用性
<a name="V2globaltables_HowItWorks.availability"></a>

全局表可以更轻松地实施多区域高可用性架构，从而有助于提高业务连续性。如果单个 AWS 区域中的工作负载受损，则可以将应用程序流量转移到不同的区域，并对同一个全局表中的其它副本表执行读取和写入操作。

全局表中的每个副本表都提供与单区域 DynamoDB 表相同的持久性和可用性。全局表提供 99.999% 的可用性[服务水平协议（SLA）](https://aws.amazon.com//dynamodb/sla/)，相比之下，单区域表提供 99.99% 的可用性。

## 一致性模式
<a name="V2globaltables_HowItWorks.consistency-modes"></a>

创建全局表时，可以配置其一致性模式。全局表支持两种一致性模式：多区域最终一致性（MREC）和多区域强一致性（MRSC）。

如果您在创建全局表时未指定一致性模式，则全局表默认为多区域最终一致性（MREC）。全局表不能包含配置了不同一致性模式的副本。创建全局表后，无法更改其一致性模式。

### 多区域最终一致性（MREC）
<a name="V2globaltables_HowItWorks.consistency-modes.mrec"></a>

多区域最终一致性（MREC）是全局表的默认一致性模式。MREC 全局表副本中的项目更改通常会在一秒或更短的时间内异步复制到所有其它副本。万一 MREC 全局表中的副本变得孤立或受损，任何尚未复制到其它区域的数据将在该副本恢复正常时进行复制。

如果在多个区域中同时修改同一个项目，DynamoDB 将基于每个项目使用具有最新内部时间戳的修改来解决冲突，称为“最后写入者为准”冲突解决方法。一个项目最终将在所有副本中会聚至由最后一次写入创建的版本。

如果项目是在发生读取的区域中进行最后一次更新的，则 [Strongly consistent read operations](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html#DDB-GetItem-request-ConsistentRead) 会返回该项目的最新版本；但如果该项目是在其它区域中进行最后一次更新的，则可能会返回陈旧的数据。条件写入会根据区域中项目的版本来评估条件表达式。

您可以通过向现有 DynamoDB 表添加副本来创建 MREC 全局表。添加副本不会影响现有单区域 DynamoDB 表或全局表副本的性能。您可以向 MREC 全局表中添加副本以扩大复制数据的区域数量，或者如果不再需要副本，则可以从 MREC 全局表中移除副本。MREC 全局表可以在 DynamoDB 可用的任何区域中有一个副本，并且可以拥有与 [AWS partition](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/partitions.html) 中的区域数量一样多的副本。

### 多区域强一致性（MRSC）
<a name="V2globaltables_HowItWorks.consistency-modes.mrsc"></a>

创建全局表时，可以配置多区域强一致性（MRSC）模式。在写入操作返回成功响应之前，MRSC 全局表副本中的项目更改会同步复制到至少一个其它区域。对任何 MRSC 副本执行的强一致性读取操作始终返回项目的最新版本。条件写入始终根据项目的最新版本来评估条件表达式。

MRSC 全局表必须部署在恰好三个区域中。您可以将 MRSC 全局表配置为具有三个副本或具有两个副本和一个见证者。见证者是 MRSC 全局表的一个组成部分，它包含写入全局表副本的数据，并在支持 MRSC 的可用性架构的同时，为完整副本提供了一种可选的替代方案。您无法对见证者执行读取或写入操作。见证者位于与两个副本不同的区域中。创建 MRSC 全局表时，您可以在创建 MRSC 表时为副本和见证者部署选择区域。您可以从 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) API 的输出中，确定 MRSC 全局表是否配置了见证者以及在哪个区域中配置了见证者。见证者由 DynamoDB 拥有和管理，并且在配置了见证者的区域中，见证者不会出现在您的 AWS 账户中。

MRSC 全局表在以下区域集中可用：美国区域集 [美国东部（弗吉尼亚州北部）、美国东部（俄亥俄州）、美国西部（俄勒冈州）]、欧洲区域集 [欧洲地区（爱尔兰）、欧洲地区（伦敦）、欧洲地区（巴黎）、欧洲地区（法兰克福）] 和亚太地区区域集 [亚太地区（东京）、亚太地区（首尔）、亚太地区（大阪）]。MRSC 全局表不能跨区域集（例如，MRSC 全局表不能同时包含来自美国区域集和欧盟区域集的副本）。

您可以通过向不包含任何数据的现有 DynamoDB 表中添加一个副本和一个见证者或两个副本来创建 MRSC 全局表。在将现有单区域表转换为 MRSC 全局表时，必须确保该表为空。不支持将单区域表转换为包含现有项目的 MRSC 全局表。确保在转换过程中未向表中写入任何数据。您无法向现有 MRSC 全局表添加其它副本。您无法从 MRSC 全局表中删除单个副本表或见证者。您可以从 MRSC 全局表中删除两个副本，或者删除一个副本和一个见证者，同时将剩余的副本转换为单区域 DynamoDB 表。

当写入操作尝试修改已在另一个区域中处于正在修改状态的项目时，此操作将失败并引发 `ReplicatedWriteConflictException`。失败并引发了 `ReplicatedWriteConflictException` 的写入操作可以进行重试，如果该项目不再在另一个区域中处于正在修改状态，则写入将成功。

以下注意事项适用于 MRSC 全局表：
+ MRSC 全局表不支持生存时间（TTL）。
+ MRSC 全局表不支持本地二级索引（LSI）。
+ CloudWatch Contributor Insights 信息仅针对发生操作的区域进行报告。

## 选择一致性模式
<a name="V2globaltables_HowItWorks.choosing-consistency-mode"></a>

选择多区域一致性模式的关键标准是：应用程序是优先考虑低延迟写入和强一致性读取，还是优先考虑全局强一致性。

与 MRSC 全局表相比，MREC 全局表将具有更低的写入延迟和强一致性读取延迟。MREC 全局表具有与副本间的复制延迟相等的恢复点目标（RPO），通常为几秒，具体取决于副本所在的区域。

在以下情况下，应使用 MREC 模式：
+ 如果从强一致性读取操作返回的陈旧数据已在其它区域中更新，您的应用程序可以容忍这些数据。
+ 您优先考虑较低的写入延迟和强一致性读取延迟，而不是多区域读取一致性。
+ 您的多区域高可用性策略可以容忍 RPO 大于零。

与 MREC 全局表相比，MRSC 全局表将具有更高的写入延迟和强一致性读取延迟。MRSC 全局表支持恢复点目标（RPO）为零。

在以下情况下，应使用 MRSC 模式：
+ 您需要跨多个区域实现强一致性读取。
+ 您优先考虑全局读取一致性，而不是较低的写入延迟。
+ 您的多区域高可用性策略要求 RPO 为零。

## 监控全局表
<a name="monitoring-global-tables"></a>

配置为多区域最终一致性（MREC）的全局表会将 [`ReplicationLatency`](metrics-dimensions.md#ReplicationLatency) 指标发布到 CloudWatch。此指标跟踪从一个项目写入到副本表到该项目出现在全局表的另一个副本中之间经过的时间。`ReplicationLatency` 以毫秒表示，并针对全局表中的每个源区域和目标区域对发出。

典型的 `ReplicationLatency` 值取决于所选 AWS 区域之间的距离以及工作负载类型和吞吐量等其它变量。例如，与非洲（开普敦）（af-south-1）区域相比，美国西部（北加利福尼亚）（us-west-1）区域的源副本到美国西部（俄勒冈州）（us-west-2）区域的 `ReplicationLatency` 更低。

`ReplicationLatency` 值不断增加可能表明来自一个副本的更新没有及时传播到其它副本表。在这种情况下，可以临时将应用程序的读取和写入活动重定向到不同的 AWS 区域。

配置为多区域强一致性（MRSC）的全局表不发布 `ReplicationLatency` 指标。

## 故障注入测试
<a name="fault-injection-testing"></a>

MREC 和 MRSC 全局表都与 [AWS 故障注入服务](https://docs.aws.amazon.com/resilience-hub/latest/userguide/testing.html)（AWS FIS）集成，这是一项完全托管式服务，用于运行受控的故障注入实验，以提高应用程序的弹性。使用 AWS FIS，您可以：
+ 创建用于定义特定故障场景的实验模板。
+ 通过模拟区域隔离（即暂停与选定副本之间的复制）来测试错误处理、恢复机制和一个 AWS 区域出现中断时的多区域流量转移行为，从而注入故障以验证应用程序的弹性。

例如，在一个在美国东部（弗吉尼亚州北部）、美国东部（俄亥俄州）和美国西部（俄勒冈州）有副本的全局表中，您可以在美国东部（俄亥俄州）运行一个实验以测试此处的区域隔离，而美国东部（弗吉尼亚州北部）和美国西部（俄勒冈州）继续正常运行。这种受控测试有助于确定并解决潜在的问题，以防这些问题影响到生产工作负载。

有关 AWS FIS 支持的操作的完整列表，请参阅《AWS FIS 用户指南》**中的[操作目标](https://docs.aws.amazon.com/fis/latest/userguide/action-sequence.html#action-targets)，有关如何暂停 DynamoDB 在区域之间复制，请参阅[跨区域连接](https://docs.aws.amazon.com/fis/latest/userguide/cross-region-scenario.html)。

有关 AWS FIS 中可用的 Amazon DynamoDB 全局表操作的信息，请参阅《AWS FIS 用户指南》**中的 [DynamoDB 全局表操作参考](https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html#dynamodb-actions-reference)。

要开始运行故障注入实验，请参阅《AWS FIS 用户指南》中的[计划您的 AWS FIS 实验](https://docs.aws.amazon.com/fis/latest/userguide/getting-started-planning.html)。

**注意**  
在 MRSC 中进行 AWS FIS 实验期间，允许最终一致性读取，但不支持更新表设置（例如更改计费模式或配置表吞吐量），这与 MREC 类似。请检查 CloudWatch 指标 [`FaultInjectionServiceInducedErrors`](metrics-dimensions.md#FaultInjectionServiceInducedErrors)，以了解有关错误代码的更多详细信息。

## 生存时间（TTL）
<a name="global-tables-ttl"></a>

配置为 MREC 的全局表支持配置[生存时间](TTL.md)（TTL）删除。全局表中所有副本的 TTL 设置会自动同步。当 TTL 从某个区域的副本中删除项目时，该删除操作会复制到全局表中的所有其它副本。TTL 不占用写入容量，因此您无需为发生删除的区域中的 TTL 删除付费。但是，对于全局表中具有副本的每个其它区域，您需要对复制的删除操作付费。

对于要向其复制删除操作的副本，TTL 删除复制会占用写入容量。如果写入吞吐量和 TTL 删除吞吐量的组合高于预置的写入容量，则配置为预置容量的副本可能会限制请求。

配置为多区域强一致性（MRSC）的全局表不支持配置生存时间（TTL）删除。

## 流
<a name="global-tables-streams"></a>

配置为多区域最终一致性（MREC）的全局表可通过从副本表上的 [DynamoDB 流](Streams.md)读取这些更改，并将该更改应用于所有其它副本表，从而复制更改。因此，默认情况下，对 MREC 全局表中的所有副本都启用了流，并且无法在这些副本上禁用流。MREC 复制过程可能会在短时间内将多项更改合并到单个复制写入操作中，从而导致每个副本的流包含的记录略有不同。MREC 副本上的流记录始终按每个项目排序，但项目间的排序可能在副本之间有所不同。

配置为多区域强一致性（MRSC）的全局表不使用 DynamoDB Streams 进行复制，因此默认情况下不在 MRSC 副本上启用流。您可以在 MRSC 副本上启用流。MRSC 副本上的流记录对于每个副本都是相同的，包括流记录排序。

如果您要编写一个应用程序，用于处理全局表中在特定区域（而非其它区域）发生的更改的流记录，则可以为每个项目添加一个属性，以定义该项目的更改发生在哪个区域。您可以使用此属性来筛选流记录，以了解在其它区域中发生的更改，包括使用 Lambda 事件筛选条件仅针对特定区域中的更改调用 Lambda 函数。

## 事务
<a name="global-tables-transactions"></a>

在配置为 MREC 的全局表上，DynamoDB 事务操作（[https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) 和 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html)）仅在调用该操作的区域内才是原子操作。事务性写入不会作为一个单元跨区域复制，这意味着在给定时间点，其它副本中的读取操作可能只会返回事务中的部分写入内容。

例如，如果您有一个全局表，该表在美国东部（俄亥俄州）和美国西部（俄勒冈州）区域中具有副本，并且在美国东部（俄亥俄州）区域中执行 `TransactWriteItems` 操作，则在复制更改时，可能会在美国西部（俄勒冈州）区域观察到部分完成的事务。更改仅在源区域中提交后才会复制到其它区域。

配置为多区域强一致性（MRSC）的全局表不支持事务操作，如果对 MRSC 副本调用这些操作，则会返回错误。

## 读写吞吐量
<a name="V2globaltables_HowItWorks.Throughput"></a>

### 预置模式
<a name="gt_throughput.provisioned"></a>

复制会消耗写入容量。如果应用程序写入吞吐量和复制写入吞吐量的组合超过了预置的写入容量，则配置为预置容量的副本可能会限制请求。对于使用预置模式的全局表，读取和写入容量的自动扩缩设置将在副本之间同步。

可以通过在副本级别使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ProvisionedThroughputOverride.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ProvisionedThroughputOverride.html) 参数，为全局表中的每个副本独立配置读取容量设置。默认情况下，对预置读取容量的更改将应用于全局表中的所有副本。向全局表添加新副本时，除非明确指定了副本级别覆盖，否则将使用源表或副本的读取容量作为初始值。

### 按需模式
<a name="gt_throughput.on-demand"></a>

对于配置为按需模式的全局表，写入容量会在所有副本之间自动同步。DynamoDB 会根据流量自动调整容量，并且无需管理特定于副本的读取或写入容量设置。

## 设置同步
<a name="V2globaltables_HowItWorks.setting-synchronization"></a>

DynamoDB 全局表中的设置是控制表行为和复制的各个方面的配置参数。这些设置可通过 DynamoDB 控制面板 API 进行管理，并可以在创建或修改全局表时进行配置。全局表会自动在所有副本间同步某些设置以保持一致性，同时可以灵活地进行特定于区域的优化。了解哪些设置会同步以及它们的行为方式，有助于您有效地配置全局表。根据这些设置在副本间的同步方式，可分为三个主要类别。

以下设置始终在全局表中的副本之间同步：
+ 容量模式（预置容量或按需）
+ 表预置写入容量
+ 表写入自动扩缩
+ 关键架构的属性定义
+ 全局二级索引（GSI）定义
+ GSI 预置写入容量
+ GSI 写入自动扩缩
+ 服务器端加密（SSE）类型
+ MREC 模式下的流定义
+ 生存时间（TTL）
+ 热吞吐量
+ 按需最大写入吞吐量

以下设置在副本之间同步，但可以根据每个副本进行覆盖：
+ 表预置读取容量
+ 表读取自动扩缩
+ GSI 预置读取容量
+ GSI 读取自动扩缩
+ 表类
+ 按需最大读取吞吐量

**注意**  
如果在任何其它副本上修改了设置，则会更改可覆盖的设置值。例如，您有一个 MREC 全局表，其副本位于美国东部（弗吉尼亚州北部）和美国西部（俄勒冈州）。美国东部（弗吉尼亚州北部）副本的预置读取吞吐量设置为 200 个 RCU。美国西部（俄勒冈州）副本的预置读取吞吐量覆盖设置为 100 个 RCU。如果您将美国东部（弗吉尼亚州北部）副本上的预置读取吞吐量设置从 200 个 RCU 更新为 300 个 RCU，则新的预置读取吞吐量值也将应用于美国西部（俄勒冈州）的副本。这会将美国西部（俄勒冈州）副本的预置读取吞吐量设置从被覆盖的值 100 个 RCU 更改为新值 300 个 RCU。

以下设置从不会在副本之间同步：
+ 删除保护
+ 时间点故障恢复
+ 标签
+ 表 CloudWatch Contributor Insights 启用
+ GSI CloudWatch Contributor Insights 启用
+ Kinesis Data Streams 定义
+ 资源策略
+ MRSC 模式下的流定义

所有其它设置均不会在副本之间同步。

## DynamoDB Accelerator (DAX)
<a name="V2globaltables_HowItWorks.dax"></a>

对全局表副本的写入会绕过 DynamoDB Accelerator（DAX），而直接更新 DynamoDB。因此，由于写入操作未更新 DAX 缓存，DAX 缓存可能变得过时。为全局表副本配置的 DAX 缓存只有在缓存 TTL 到期时才会刷新。

## 管理全局表的注意事项
<a name="management-considerations"></a>

在创建新的全局表副本后的 24 小时内，您无法删除用于添加该新副本的表。

如果您禁用包含全局表副本的 AWS 区域，则在禁用该区域的 20 小时后，这些副本将永久转换为单区域表。

# 教程：创建全局表
<a name="V2globaltables.tutorial"></a>

本节提供分步说明，指导您创建已配置为首选一致性模式的 DynamoDB 全局表。根据应用程序的要求，选择多区域最终一致性（MREC）模式或多区域强一致性（MRSC）模式。

MREC 全局表跨 AWS 区域提供较低的写入延迟及最终一致性。MRSC 全局表提供跨区域的强一致性读取，写入延迟略高于 MREC。选择最能满足应用程序对数据一致性、延迟和可用性的需求的一致性模式。

**Topics**
+ [创建配置为 MREC 的全局表](#V2creategt_mrec)
+ [创建配置为 MRSC 的全局表](#create-gt-mrsc)

## 创建配置为 MREC 的全局表
<a name="V2creategt_mrec"></a>

本节介绍如何创建具有多区域最终一致性（MREC）模式的全局表。MREC 是全局表的默认一致性模式，可通过跨 AWS 区域的异步复制来提供低延迟写入。在一个区域中对某个项目所做的更改通常会在 1 秒内复制到所有其它区域。这使得 MREC 非常适合以下应用场景：优先考虑低写入延迟，且能够容忍在短时间内不同区域可能返回略有差异的数据版本。

您可以在 DynamoDB 可用的任何 AWS 区域中创建 MREC 全局表以及副本，并可随时添加或移除副本。以下示例显示了如何在多个区域中创建 MREC 全局表以及副本。

### 使用 DynamoDB 控制台创建 MREC 全局表
<a name="mrec-console"></a>

按照以下步骤，使用 AWS 管理控制台创建全局表。以下示例创建一个全局表，其副本表位于美国和欧洲。

1. 登录 AWS 管理控制台，并打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 对于本示例，从导航栏的区域选择器中选择**美国东部（俄亥俄州）**。

1. 在控制台左侧的导航窗格中，选择**表**。

1. 选择**创建表**。

1. 在**创建表**页面上：

   1. 对于**表名称**，输入 **Music**。

   1. 对于**分区键**，输入 **Artist**。

   1. 对于**排序键**，输入 **SongTitle**。

   1. 保留其它默认设置，然后选择**创建表**。

      此新表在新的全局表中用作第一个副本表。这是您稍后添加的其他副本表的原型。

1. 在表变为活动状态后：

   1. 从表列表中选择 **Music** 表。

   1. 选择**全局表**选项卡。

   1. 选择**创建副本**。

1. 从**可用的复制区域**下拉菜单中，选择**美国西部（俄勒冈州）us-west-2**。

   控制台将确保所选区域中不存在同名的表。如果有同名的表，则必须删除现有表，然后才能在该区域创建新的副本表。

1. 选择**创建副本**。这将在美国西部（俄勒冈州）us-west-2 区域启动表创建过程。

   **音乐**表（以及任何其他副本表）的**全局表**选项卡将显示该表已在多个区域中复制。

1. 通过重复前面的步骤来添加另一个区域，但选择**欧洲地区（法兰克福）eu-central-1** 作为区域。

1. 测试复制：

   1. 在美国东部（俄亥俄州）区域，确保您使用的是 AWS 管理控制台。

   1. 选择**浏览表项目**。

   1. 选择**创建项目**。

   1. 对于**艺术家**，输入 **item\$11**；而对于**歌名**，则输入 **Song Value 1**。

   1. 选择**创建项目**。

1. 通过切换到其它区域来验证复制：

   1. 从右上角的区域选择器中，选择**欧洲地区（法兰克福）**。

   1. 验证 **Music** 表中是否包含您创建的项目。

   1. 对**美国西部（俄勒冈州）**重复此验证过程。

### 使用 AWS CLI 或 Java 创建 MREC 全局表
<a name="mrec-cli-java"></a>

------
#### [ CLI ]

以下代码示例显示如何使用具有最终一致性的多区域复制（MREC）来管理 DynamoDB 全局表。
+ 使用多区域复制（MREC）创建表。
+ 从副本表中放置和获取项目。
+ 逐一移除副本。
+ 通过删除表来进行清除。

**AWS CLI 及 Bash 脚本**  
使用多区域复制来创建表。  

```
# Step 1: Create a new table (MusicTable) in US East (Ohio), with DynamoDB Streams enabled (NEW_AND_OLD_IMAGES)
aws dynamodb create-table \
    --table-name MusicTable \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
    --region us-east-2

# Step 2: Create an identical MusicTable table in US East (N. Virginia)
aws dynamodb update-table --table-name MusicTable --cli-input-json \
'{
  "ReplicaUpdates":
  [
    {
      "Create": {
        "RegionName": "us-east-1"
      }
    }
  ]
}' \
--region us-east-2

# Step 3: Create a table in Europe (Ireland)
aws dynamodb update-table --table-name MusicTable --cli-input-json \
'{
  "ReplicaUpdates":
  [
    {
      "Create": {
        "RegionName": "eu-west-1"
      }
    }
  ]
}' \
--region us-east-2
```
描述多区域表。  

```
# Step 4: View the list of replicas created using describe-table
aws dynamodb describe-table \
    --table-name MusicTable \
    --region us-east-2 \
    --query 'Table.{TableName:TableName,TableStatus:TableStatus,MultiRegionConsistency:MultiRegionConsistency,Replicas:Replicas[*].{Region:RegionName,Status:ReplicaStatus}}'
```
将项目放在副本表中。  

```
# Step 5: To verify that replication is working, add a new item to the Music table in US East (Ohio)
aws dynamodb put-item \
    --table-name MusicTable \
    --item '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region us-east-2
```
从副本表中获取项目。  

```
# Step 6: Wait for a few seconds, and then check to see whether the item has been 
# successfully replicated to US East (N. Virginia) and Europe (Ireland)
aws dynamodb get-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region us-east-1

aws dynamodb get-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region eu-west-1
```
移除副本。  

```
# Step 7: Delete the replica table in Europe (Ireland) Region
aws dynamodb update-table --table-name MusicTable --cli-input-json \
'{
  "ReplicaUpdates":
  [
    {
      "Delete": {
        "RegionName": "eu-west-1"
      }
    }
  ]
}' \
--region us-east-2

# Delete the replica table in US East (N. Virginia) Region
aws dynamodb update-table --table-name MusicTable --cli-input-json \
'{
  "ReplicaUpdates":
  [
    {
      "Delete": {
        "RegionName": "us-east-1"
      }
    }
  ]
}' \
--region us-east-2
```
通过删除表来进行清除。  

```
# Clean up: Delete the primary table
aws dynamodb delete-table --table-name MusicTable --region us-east-2

echo "Global table demonstration complete."
```
+ 有关 API 详细信息，请参阅《AWS CLI 命令参考》**中的以下主题。
  + [CreateTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/CreateTable)
  + [DeleteTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/DeleteTable)
  + [DescribeTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/DescribeTable)
  + [GetItem](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/GetItem)
  + [PutItem](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/PutItem)
  + [UpdateTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/UpdateTable)

------
#### [ Java ]

以下代码示例演示了如何跨多个区域创建和管理 DynamoDB 全局表以及副本。
+ 使用全局二级索引和 DynamoDB Streams 创建一个表。
+ 在不同区域中添加副本以创建全局表。
+ 从全局表中移除副本。
+ 添加测试项目以验证跨区域的复制。
+ 描述全局表配置和副本状态。

**适用于 Java 的 SDK 2.x**  
使用AWS SDK for Java 2.x 通过全局二级索引和 DynamoDB Streams 创建一个表。  

```
    public static CreateTableResponse createTableWithGSI(
        final DynamoDbClient dynamoDbClient, final String tableName, final String indexName) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (indexName == null || indexName.trim().isEmpty()) {
            throw new IllegalArgumentException("Index name cannot be null or empty");
        }

        try {
            LOGGER.info("Creating table: " + tableName + " with GSI: " + indexName);

            CreateTableRequest createTableRequest = CreateTableRequest.builder()
                .tableName(tableName)
                .attributeDefinitions(
                    AttributeDefinition.builder()
                        .attributeName("Artist")
                        .attributeType(ScalarAttributeType.S)
                        .build(),
                    AttributeDefinition.builder()
                        .attributeName("SongTitle")
                        .attributeType(ScalarAttributeType.S)
                        .build())
                .keySchema(
                    KeySchemaElement.builder()
                        .attributeName("Artist")
                        .keyType(KeyType.HASH)
                        .build(),
                    KeySchemaElement.builder()
                        .attributeName("SongTitle")
                        .keyType(KeyType.RANGE)
                        .build())
                .billingMode(BillingMode.PAY_PER_REQUEST)
                .globalSecondaryIndexes(GlobalSecondaryIndex.builder()
                    .indexName(indexName)
                    .keySchema(KeySchemaElement.builder()
                        .attributeName("SongTitle")
                        .keyType(KeyType.HASH)
                        .build())
                    .projection(
                        Projection.builder().projectionType(ProjectionType.ALL).build())
                    .build())
                .streamSpecification(StreamSpecification.builder()
                    .streamEnabled(true)
                    .streamViewType(StreamViewType.NEW_AND_OLD_IMAGES)
                    .build())
                .build();

            CreateTableResponse response = dynamoDbClient.createTable(createTableRequest);
            LOGGER.info("Table creation initiated. Status: "
                + response.tableDescription().tableStatus());

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to create table: " + tableName + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 等待表变为活动状态。  

```
    public static void waitForTableActive(final DynamoDbClient dynamoDbClient, final String tableName) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }

        try {
            LOGGER.info("Waiting for table to become active: " + tableName);

            try (DynamoDbWaiter waiter =
                DynamoDbWaiter.builder().client(dynamoDbClient).build()) {
                DescribeTableRequest request =
                    DescribeTableRequest.builder().tableName(tableName).build();

                waiter.waitUntilTableExists(request);
                LOGGER.info("Table is now active: " + tableName);
            }

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to wait for table to become active: " + tableName + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 添加副本以创建或扩展全局表。  

```
    public static UpdateTableResponse addReplica(
        final DynamoDbClient dynamoDbClient,
        final String tableName,
        final Region replicaRegion,
        final String indexName,
        final Long readCapacity) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (replicaRegion == null) {
            throw new IllegalArgumentException("Replica region cannot be null");
        }
        if (indexName == null || indexName.trim().isEmpty()) {
            throw new IllegalArgumentException("Index name cannot be null or empty");
        }
        if (readCapacity == null || readCapacity <= 0) {
            throw new IllegalArgumentException("Read capacity must be a positive number");
        }

        try {
            LOGGER.info("Adding replica in region: " + replicaRegion.id() + " for table: " + tableName);

            // Create a ReplicationGroupUpdate for adding a replica
            ReplicationGroupUpdate replicationGroupUpdate = ReplicationGroupUpdate.builder()
                .create(builder -> builder.regionName(replicaRegion.id())
                    .globalSecondaryIndexes(ReplicaGlobalSecondaryIndex.builder()
                        .indexName(indexName)
                        .provisionedThroughputOverride(ProvisionedThroughputOverride.builder()
                            .readCapacityUnits(readCapacity)
                            .build())
                        .build())
                    .build())
                .build();

            UpdateTableRequest updateTableRequest = UpdateTableRequest.builder()
                .tableName(tableName)
                .replicaUpdates(replicationGroupUpdate)
                .build();

            UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest);
            LOGGER.info("Replica addition initiated in region: " + replicaRegion.id());

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to add replica in region: " + replicaRegion.id() + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 从全局表中移除副本。  

```
    public static UpdateTableResponse removeReplica(
        final DynamoDbClient dynamoDbClient, final String tableName, final Region replicaRegion) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (replicaRegion == null) {
            throw new IllegalArgumentException("Replica region cannot be null");
        }

        try {
            LOGGER.info("Removing replica in region: " + replicaRegion.id() + " for table: " + tableName);

            // Create a ReplicationGroupUpdate for removing a replica
            ReplicationGroupUpdate replicationGroupUpdate = ReplicationGroupUpdate.builder()
                .delete(builder -> builder.regionName(replicaRegion.id()).build())
                .build();

            UpdateTableRequest updateTableRequest = UpdateTableRequest.builder()
                .tableName(tableName)
                .replicaUpdates(replicationGroupUpdate)
                .build();

            UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest);
            LOGGER.info("Replica removal initiated in region: " + replicaRegion.id());

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to remove replica in region: " + replicaRegion.id() + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 添加测试项目以验证复制。  

```
    public static PutItemResponse putTestItem(
        final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (artist == null || artist.trim().isEmpty()) {
            throw new IllegalArgumentException("Artist cannot be null or empty");
        }
        if (songTitle == null || songTitle.trim().isEmpty()) {
            throw new IllegalArgumentException("Song title cannot be null or empty");
        }

        try {
            LOGGER.info("Adding test item to table: " + tableName);

            Map<String, software.amazon.awssdk.services.dynamodb.model.AttributeValue> item = new HashMap<>();
            item.put(
                "Artist",
                software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder()
                    .s(artist)
                    .build());
            item.put(
                "SongTitle",
                software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder()
                    .s(songTitle)
                    .build());

            PutItemRequest putItemRequest =
                PutItemRequest.builder().tableName(tableName).item(item).build();

            PutItemResponse response = dynamoDbClient.putItem(putItemRequest);
            LOGGER.info("Test item added successfully");

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to add test item to table: " + tableName + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 描述全局表配置和副本。  

```
    public static DescribeTableResponse describeTable(final DynamoDbClient dynamoDbClient, final String tableName) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }

        try {
            LOGGER.info("Describing table: " + tableName);

            DescribeTableRequest request =
                DescribeTableRequest.builder().tableName(tableName).build();

            DescribeTableResponse response = dynamoDbClient.describeTable(request);

            LOGGER.info("Table status: " + response.table().tableStatus());
            if (response.table().replicas() != null
                && !response.table().replicas().isEmpty()) {
                LOGGER.info("Number of replicas: " + response.table().replicas().size());
                response.table()
                    .replicas()
                    .forEach(replica -> LOGGER.info(
                        "Replica region: " + replica.regionName() + ", Status: " + replica.replicaStatus()));
            }

            return response;

        } catch (ResourceNotFoundException e) {
            LOGGER.severe("Table not found: " + tableName + " - " + e.getMessage());
            throw e;
        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to describe table: " + tableName + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 进行全局表操作的完整示例。  

```
    public static void exampleUsage(final Region sourceRegion, final Region replicaRegion) {

        String tableName = "Music";
        String indexName = "SongTitleIndex";
        Long readCapacity = 15L;

        // Create DynamoDB client for the source region
        try (DynamoDbClient dynamoDbClient =
            DynamoDbClient.builder().region(sourceRegion).build()) {

            try {
                // Step 1: Create the initial table with GSI and streams
                LOGGER.info("Step 1: Creating table in source region: " + sourceRegion.id());
                createTableWithGSI(dynamoDbClient, tableName, indexName);

                // Step 2: Wait for table to become active
                LOGGER.info("Step 2: Waiting for table to become active");
                waitForTableActive(dynamoDbClient, tableName);

                // Step 3: Add replica in destination region
                LOGGER.info("Step 3: Adding replica in region: " + replicaRegion.id());
                addReplica(dynamoDbClient, tableName, replicaRegion, indexName, readCapacity);

                // Step 4: Wait a moment for replica creation to start
                Thread.sleep(5000);

                // Step 5: Describe table to view replica information
                LOGGER.info("Step 5: Describing table to view replicas");
                describeTable(dynamoDbClient, tableName);

                // Step 6: Add a test item to verify replication
                LOGGER.info("Step 6: Adding test item to verify replication");
                putTestItem(dynamoDbClient, tableName, "TestArtist", "TestSong");

                LOGGER.info("Global table setup completed successfully!");
                LOGGER.info("You can verify replication by checking the item in region: " + replicaRegion.id());

                // Step 7: Remove replica and clean up table
                LOGGER.info("Step 7: Removing replica from region: " + replicaRegion.id());
                removeReplica(dynamoDbClient, tableName, replicaRegion);
                DeleteTableResponse deleteTableResponse = dynamoDbClient.deleteTable(
                    DeleteTableRequest.builder().tableName(tableName).build());
                LOGGER.info("MREC global table demonstration completed successfully!");

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Thread was interrupted", e);
            } catch (DynamoDbException e) {
                LOGGER.severe("DynamoDB operation failed: " + e.getMessage());
                throw e;
            }
        }
    }
```
+ 有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的以下主题。
  + [CreateTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/CreateTable)
  + [DescribeTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/DescribeTable)
  + [PutItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/PutItem)
  + [UpdateTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateTable)

------

## 创建配置为 MRSC 的全局表
<a name="create-gt-mrsc"></a>

本节介绍如何创建多区域强一致性（MRSC）全局表。MRSC 全局表跨区域同步复制项目更改，确保对任何副本进行的强一致性读取操作始终返回项目的最新版本。在将单区域表转换为 MRSC 全局表时，必须确保该表为空。不支持将单区域表转换为包含现有项目的 MRSC 全局表。确保在转换过程中未向表中写入任何数据。

您可以将 MRSC 全局表配置为具有三个副本或两个副本和一个见证者。创建 MRSC 全局表时，您可以选择在其中部署副本和可选见证者的区域。以下示例创建一个 MRSC 全局表，该表在美国东部（弗吉尼亚州北部）和美国东部（俄亥俄州）区域具有副本，而在美国西部（俄勒冈州）区域具有见证者。

**注意**  
创建全局表之前，请验证所有目标区域的服务配额吞吐量限额是否一致，因为这是创建全局表所必需的。有关全局表吞吐量限制的更多信息，请参阅[全局表配额](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#gt-limits-throughput)。

### 使用 DynamoDB 控制台创建 MRSC 全局表
<a name="mrsc_console"></a>

按照以下步骤，使用 AWS 管理控制台创建 MRSC 全局表。

1. 登录 AWS 管理控制台，并打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 从导航栏的区域选择器中，选择一个[支持](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes)具有 MRSC 的全局表的区域，如 **us-east-2**。

1. 在导航窗格中，选择**表**。

1. 选择**创建表**。

1. 在**创建表**页面上：

   1. 对于**表名称**，输入 **Music**。

   1. 对于**分区键**，输入 **Artist**，并保留默认的**字符串**类型。

   1. 对于**排序键**，输入 **SongTitle**，并保留默认的**字符串**类型。

   1. 保留其它默认设置，然后选择**创建表**。

      此新表在新的全局表中用作第一个副本表。这是您稍后添加的其他副本表的原型。

1. 等待表变为活动状态，然后从表列表中选择它。

1. 选择**全局表**选项卡，然后选择**创建副本**。

1. 在**创建副本**页面上：

   1. 在**多区域一致性**下，选择**强一致性**。

   1. 对于**复制区域 1**，选择 **US East (N. Virginia) us-east-1**。

   1. 对于**复制区域 2**，选择 **US West (Oregon) us-west-2**。

   1. 对于美国西部（俄勒冈州）区域，选中**配置为见证者**。

   1. 选择**创建副本**。

1. 等待副本和见证者创建过程完成。当表准备就绪可供使用时，副本状态将显示为**活动**。

### 使用 AWS CLI 或 Java 创建 MRSC 全局表
<a name="mrsc-cli-java"></a>

在开始之前，请确保 IAM 主体拥有创建具有见证区域的 MRSC 全局表所需的权限。

以下示例 IAM 策略让您可以在美国东部（俄亥俄州）区域创建 DynamoDB 表（`MusicTable`），在美国东部（弗吉尼亚州北部）区域拥有副本，并在美国西部（俄勒冈州）区域拥有见证区域。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:CreateTable",
                "dynamodb:CreateTableReplica",
                "dynamodb:CreateGlobalTableWitness",
                "dynamodb:DescribeTable",
                "dynamodb:UpdateTable",
                "dynamodb:DeleteTable",
                "dynamodb:DeleteTableReplica",
                "dynamodb:DeleteGlobalTableWitness",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem",
                "dynamodb:PutItem",
                "dynamodb:GetItem",
                "dynamodb:DeleteItem",
                "dynamodb:BatchWriteItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:123456789012:table/MusicTable",
                "arn:aws:dynamodb:us-east-2:123456789012:table/MusicTable",
                "arn:aws:dynamodb:us-west-2:123456789012:table/MusicTable"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "iam:CreateServiceLinkedRole",
            "Resource": "arn:aws:iam::*:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication",
            "Condition": {
                "StringLike": {
                    "iam:AWSServiceName": "replication.dynamodb.amazonaws.com"
                }
            }
        }
    ]
}
```

------

以下代码示例显示如何创建和管理具有多区域强一致性（MRSC）的 DynamoDB 全局表。
+ 创建具有多区域强一致性的表。
+ 验证 MRSC 配置和副本状态。
+ 通过即时读取来测试各区域间的强一致性。
+ 使用 MRSC 保证执行条件写入。
+ 清理 MRSC 全局表资源。

------
#### [ Bash ]

**AWS CLI 及 Bash 脚本**  
创建具有多区域强一致性的表。  

```
# Step 1: Create a new table in us-east-2 (primary region for MRSC)
# Note: Table must be empty when enabling MRSC
aws dynamodb create-table \
    --table-name MusicTable \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST \
    --region us-east-2

# Wait for table to become active
aws dynamodb wait table-exists --table-name MusicTable --region us-east-2

# Step 2: Add replica and witness with Multi-Region Strong Consistency
# MRSC requires exactly three replicas in supported regions
aws dynamodb update-table \
    --table-name MusicTable \
    --replica-updates '[{"Create": {"RegionName": "us-east-1"}}]' \
    --global-table-witness-updates '[{"Create": {"RegionName": "us-west-2"}}]' \
    --multi-region-consistency STRONG \
    --region us-east-2
```
验证 MRSC 配置和副本状态。  

```
# Verify the global table configuration and MRSC setting
aws dynamodb describe-table \
    --table-name MusicTable \
    --region us-east-2 \
    --query 'Table.{TableName:TableName,TableStatus:TableStatus,MultiRegionConsistency:MultiRegionConsistency,Replicas:Replicas[*],GlobalTableWitnesses:GlobalTableWitnesses[*].{Region:RegionName,Status:ReplicaStatus}}'
```
通过跨区域即时读取来测试强一致性。  

```
# Write an item to the primary region
aws dynamodb put-item \
    --table-name MusicTable \
    --item '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"},"Album": {"S":"The Beatles 1967-1970"},"Year": {"N":"1968"}}' \
    --region us-east-2

# Read the item from replica region to verify strong consistency (cannot read or write to witness)
# No wait time needed - MRSC provides immediate consistency
echo "Reading from us-east-1 (immediate consistency):"
aws dynamodb get-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"}}' \
    --consistent-read \
    --region us-east-1
```
使用 MRSC 保证执行条件写入。  

```
# Perform a conditional update from a different region
# This demonstrates that conditions work consistently across all regions
aws dynamodb update-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"The Beatles"},"SongTitle": {"S":"Hey Jude"}}' \
    --update-expression "SET #rating = :rating" \
    --condition-expression "attribute_exists(Artist)" \
    --expression-attribute-names '{"#rating": "Rating"}' \
    --expression-attribute-values '{":rating": {"N":"5"}}' \
    --region us-east-1
```
清理 MRSC 全局表资源。  

```
# Remove replica tables (must be done before deleting the primary table)
aws dynamodb update-table \
    --table-name MusicTable \
    --replica-updates '[{"Delete": {"RegionName": "us-east-1"}}]' \
    --global-table-witness-updates '[{"Delete": {"RegionName": "us-west-2"}}]' \
    --region us-east-2

# Wait for replicas to be deleted
echo "Waiting for replicas to be deleted..."
sleep 30

# Delete the primary table
aws dynamodb delete-table \
    --table-name MusicTable \
    --region us-east-2
```
+ 有关 API 详细信息，请参阅《AWS CLI 命令参考》**中的以下主题。
  + [CreateTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/CreateTable)
  + [DeleteTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/DeleteTable)
  + [DescribeTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/DescribeTable)
  + [GetItem](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/GetItem)
  + [PutItem](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/PutItem)
  + [UpdateItem](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/UpdateItem)
  + [UpdateTable](https://docs.aws.amazon.com/goto/aws-cli/dynamodb-2012-08-10/UpdateTable)

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  
使用AWS SDK for Java 2.x 创建准备好进行 MRSC 转换的区域表。  

```
    public static CreateTableResponse createRegionalTable(final DynamoDbClient dynamoDbClient, final String tableName) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }

        try {
            LOGGER.info("Creating regional table: " + tableName + " (must be empty for MRSC)");

            CreateTableRequest createTableRequest = CreateTableRequest.builder()
                .tableName(tableName)
                .attributeDefinitions(
                    AttributeDefinition.builder()
                        .attributeName("Artist")
                        .attributeType(ScalarAttributeType.S)
                        .build(),
                    AttributeDefinition.builder()
                        .attributeName("SongTitle")
                        .attributeType(ScalarAttributeType.S)
                        .build())
                .keySchema(
                    KeySchemaElement.builder()
                        .attributeName("Artist")
                        .keyType(KeyType.HASH)
                        .build(),
                    KeySchemaElement.builder()
                        .attributeName("SongTitle")
                        .keyType(KeyType.RANGE)
                        .build())
                .billingMode(BillingMode.PAY_PER_REQUEST)
                .build();

            CreateTableResponse response = dynamoDbClient.createTable(createTableRequest);
            LOGGER.info("Regional table creation initiated. Status: "
                + response.tableDescription().tableStatus());

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to create regional table: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to create regional table: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 将区域表转换为具有副本和见证者的 MRSC。  

```
    public static UpdateTableResponse convertToMRSCWithWitness(
        final DynamoDbClient dynamoDbClient,
        final String tableName,
        final Region replicaRegion,
        final Region witnessRegion) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (replicaRegion == null) {
            throw new IllegalArgumentException("Replica region cannot be null");
        }
        if (witnessRegion == null) {
            throw new IllegalArgumentException("Witness region cannot be null");
        }

        try {
            LOGGER.info("Converting table to MRSC with replica in " + replicaRegion.id() + " and witness in "
                + witnessRegion.id());

            // Create replica update using ReplicationGroupUpdate
            ReplicationGroupUpdate replicaUpdate = ReplicationGroupUpdate.builder()
                .create(CreateReplicationGroupMemberAction.builder()
                    .regionName(replicaRegion.id())
                    .build())
                .build();

            // Create witness update
            GlobalTableWitnessGroupUpdate witnessUpdate = GlobalTableWitnessGroupUpdate.builder()
                .create(CreateGlobalTableWitnessGroupMemberAction.builder()
                    .regionName(witnessRegion.id())
                    .build())
                .build();

            UpdateTableRequest updateTableRequest = UpdateTableRequest.builder()
                .tableName(tableName)
                .replicaUpdates(List.of(replicaUpdate))
                .globalTableWitnessUpdates(List.of(witnessUpdate))
                .multiRegionConsistency(MultiRegionConsistency.STRONG)
                .build();

            UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest);
            LOGGER.info("MRSC conversion initiated. Status: "
                + response.tableDescription().tableStatus());
            LOGGER.info("UpdateTableResponse full object: " + response);
            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to convert table to MRSC: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to convert table to MRSC: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 描述 MRSC 全局表配置。  

```
    public static DescribeTableResponse describeMRSCTable(final DynamoDbClient dynamoDbClient, final String tableName) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }

        try {
            LOGGER.info("Describing MRSC global table: " + tableName);

            DescribeTableRequest request =
                DescribeTableRequest.builder().tableName(tableName).build();

            DescribeTableResponse response = dynamoDbClient.describeTable(request);

            LOGGER.info("Table status: " + response.table().tableStatus());
            LOGGER.info("Multi-region consistency: " + response.table().multiRegionConsistency());

            if (response.table().replicas() != null
                && !response.table().replicas().isEmpty()) {
                LOGGER.info("Number of replicas: " + response.table().replicas().size());
                response.table()
                    .replicas()
                    .forEach(replica -> LOGGER.info(
                        "Replica region: " + replica.regionName() + ", Status: " + replica.replicaStatus()));
            }

            if (response.table().globalTableWitnesses() != null
                && !response.table().globalTableWitnesses().isEmpty()) {
                LOGGER.info("Number of witnesses: "
                    + response.table().globalTableWitnesses().size());
                response.table()
                    .globalTableWitnesses()
                    .forEach(witness -> LOGGER.info(
                        "Witness region: " + witness.regionName() + ", Status: " + witness.witnessStatus()));
            }

            return response;

        } catch (ResourceNotFoundException e) {
            LOGGER.severe("Table not found: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Table not found: " + tableName)
                .cause(e)
                .build();
        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to describe table: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to describe table: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 添加测试项目以验证 MRSC 强一致性。  

```
    public static PutItemResponse putTestItem(
        final DynamoDbClient dynamoDbClient,
        final String tableName,
        final String artist,
        final String songTitle,
        final String album,
        final String year) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (artist == null || artist.trim().isEmpty()) {
            throw new IllegalArgumentException("Artist cannot be null or empty");
        }
        if (songTitle == null || songTitle.trim().isEmpty()) {
            throw new IllegalArgumentException("Song title cannot be null or empty");
        }

        try {
            LOGGER.info("Adding test item to MRSC global table: " + tableName);

            Map<String, AttributeValue> item = new HashMap<>();
            item.put("Artist", AttributeValue.builder().s(artist).build());
            item.put("SongTitle", AttributeValue.builder().s(songTitle).build());

            if (album != null && !album.trim().isEmpty()) {
                item.put("Album", AttributeValue.builder().s(album).build());
            }
            if (year != null && !year.trim().isEmpty()) {
                item.put("Year", AttributeValue.builder().n(year).build());
            }

            PutItemRequest putItemRequest =
                PutItemRequest.builder().tableName(tableName).item(item).build();

            PutItemResponse response = dynamoDbClient.putItem(putItemRequest);
            LOGGER.info("Test item added successfully with strong consistency");

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to add test item to table: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to add test item to table: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 通过一致性读取从 MRSC 副本中读取项目。  

```
    public static GetItemResponse getItemWithConsistentRead(
        final DynamoDbClient dynamoDbClient, final String tableName, final String artist, final String songTitle) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (artist == null || artist.trim().isEmpty()) {
            throw new IllegalArgumentException("Artist cannot be null or empty");
        }
        if (songTitle == null || songTitle.trim().isEmpty()) {
            throw new IllegalArgumentException("Song title cannot be null or empty");
        }

        try {
            LOGGER.info("Reading item from MRSC global table with consistent read: " + tableName);

            Map<String, AttributeValue> key = new HashMap<>();
            key.put("Artist", AttributeValue.builder().s(artist).build());
            key.put("SongTitle", AttributeValue.builder().s(songTitle).build());

            GetItemRequest getItemRequest = GetItemRequest.builder()
                .tableName(tableName)
                .key(key)
                .consistentRead(true)
                .build();

            GetItemResponse response = dynamoDbClient.getItem(getItemRequest);

            if (response.hasItem()) {
                LOGGER.info("Item found with strong consistency - no wait time needed");
            } else {
                LOGGER.info("Item not found");
            }

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to read item from table: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to read item from table: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 通过 MRSC 保证来执行条件更新。  

```
    public static UpdateItemResponse performConditionalUpdate(
        final DynamoDbClient dynamoDbClient,
        final String tableName,
        final String artist,
        final String songTitle,
        final String rating) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (artist == null || artist.trim().isEmpty()) {
            throw new IllegalArgumentException("Artist cannot be null or empty");
        }
        if (songTitle == null || songTitle.trim().isEmpty()) {
            throw new IllegalArgumentException("Song title cannot be null or empty");
        }
        if (rating == null || rating.trim().isEmpty()) {
            throw new IllegalArgumentException("Rating cannot be null or empty");
        }

        try {
            LOGGER.info("Performing conditional update on MRSC global table: " + tableName);

            Map<String, AttributeValue> key = new HashMap<>();
            key.put("Artist", AttributeValue.builder().s(artist).build());
            key.put("SongTitle", AttributeValue.builder().s(songTitle).build());

            Map<String, String> expressionAttributeNames = new HashMap<>();
            expressionAttributeNames.put("#rating", "Rating");

            Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
            expressionAttributeValues.put(
                ":rating", AttributeValue.builder().n(rating).build());

            UpdateItemRequest updateItemRequest = UpdateItemRequest.builder()
                .tableName(tableName)
                .key(key)
                .updateExpression("SET #rating = :rating")
                .conditionExpression("attribute_exists(Artist)")
                .expressionAttributeNames(expressionAttributeNames)
                .expressionAttributeValues(expressionAttributeValues)
                .build();

            UpdateItemResponse response = dynamoDbClient.updateItem(updateItemRequest);
            LOGGER.info("Conditional update successful - demonstrates strong consistency");

            return response;

        } catch (ConditionalCheckFailedException e) {
            LOGGER.warning("Conditional check failed: " + e.getMessage());
            throw e;
        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to perform conditional update: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to perform conditional update: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 等待 MRSC 副本和见证者变为活动状态。  

```
    public static void waitForMRSCReplicasActive(
        final DynamoDbClient dynamoDbClient, final String tableName, final int maxWaitTimeSeconds)
        throws InterruptedException {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (maxWaitTimeSeconds <= 0) {
            throw new IllegalArgumentException("Max wait time must be positive");
        }

        try {
            LOGGER.info("Waiting for MRSC replicas and witnesses to become active: " + tableName);

            final long startTime = System.currentTimeMillis();
            final long maxWaitTimeMillis = maxWaitTimeSeconds * 1000L;
            int backoffSeconds = 5; // Start with 5 second intervals
            final int maxBackoffSeconds = 30; // Cap at 30 seconds

            while (System.currentTimeMillis() - startTime < maxWaitTimeMillis) {
                DescribeTableResponse response = describeMRSCTable(dynamoDbClient, tableName);

                boolean allActive = true;
                StringBuilder statusReport = new StringBuilder();

                if (response.table().multiRegionConsistency() == null
                    || !MultiRegionConsistency.STRONG
                        .toString()
                        .equals(response.table().multiRegionConsistency().toString())) {
                    allActive = false;
                    statusReport
                        .append("MultiRegionConsistency: ")
                        .append(response.table().multiRegionConsistency())
                        .append(" ");
                }
                if (response.table().replicas() == null
                    || response.table().replicas().isEmpty()) {
                    allActive = false;
                    statusReport.append("No replicas found. ");
                }
                if (response.table().globalTableWitnesses() == null
                    || response.table().globalTableWitnesses().isEmpty()) {
                    allActive = false;
                    statusReport.append("No witnesses found. ");
                }

                // Check table status
                if (!"ACTIVE".equals(response.table().tableStatus().toString())) {
                    allActive = false;
                    statusReport
                        .append("Table: ")
                        .append(response.table().tableStatus())
                        .append(" ");
                }

                // Check replica status
                if (response.table().replicas() != null) {
                    for (var replica : response.table().replicas()) {
                        if (!"ACTIVE".equals(replica.replicaStatus().toString())) {
                            allActive = false;
                            statusReport
                                .append("Replica(")
                                .append(replica.regionName())
                                .append("): ")
                                .append(replica.replicaStatus())
                                .append(" ");
                        }
                    }
                }

                // Check witness status
                if (response.table().globalTableWitnesses() != null) {
                    for (var witness : response.table().globalTableWitnesses()) {
                        if (!"ACTIVE".equals(witness.witnessStatus().toString())) {
                            allActive = false;
                            statusReport
                                .append("Witness(")
                                .append(witness.regionName())
                                .append("): ")
                                .append(witness.witnessStatus())
                                .append(" ");
                        }
                    }
                }

                if (allActive) {
                    LOGGER.info("All MRSC replicas and witnesses are now active: " + tableName);
                    return;
                }

                LOGGER.info("Waiting for MRSC components to become active. Status: " + statusReport.toString());
                LOGGER.info("Next check in " + backoffSeconds + " seconds...");

                tempWait(backoffSeconds);

                // Exponential backoff with cap
                backoffSeconds = Math.min(backoffSeconds * 2, maxBackoffSeconds);
            }

            throw DynamoDbException.builder()
                .message("Timeout waiting for MRSC replicas to become active after " + maxWaitTimeSeconds + " seconds")
                .build();

        } catch (DynamoDbException | InterruptedException e) {
            LOGGER.severe("Failed to wait for MRSC replicas to become active: " + tableName + " - " + e.getMessage());
            throw e;
        }
    }
```
使用AWS SDK for Java 2.x 清理 MRSC 副本和见证者。  

```
    public static UpdateTableResponse cleanupMRSCReplicas(
        final DynamoDbClient dynamoDbClient,
        final String tableName,
        final Region replicaRegion,
        final Region witnessRegion) {

        if (dynamoDbClient == null) {
            throw new IllegalArgumentException("DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (replicaRegion == null) {
            throw new IllegalArgumentException("Replica region cannot be null");
        }
        if (witnessRegion == null) {
            throw new IllegalArgumentException("Witness region cannot be null");
        }

        try {
            LOGGER.info("Cleaning up MRSC replicas and witnesses for table: " + tableName);

            // Remove replica using ReplicationGroupUpdate
            ReplicationGroupUpdate replicaUpdate = ReplicationGroupUpdate.builder()
                .delete(DeleteReplicationGroupMemberAction.builder()
                    .regionName(replicaRegion.id())
                    .build())
                .build();

            // Remove witness
            GlobalTableWitnessGroupUpdate witnessUpdate = GlobalTableWitnessGroupUpdate.builder()
                .delete(DeleteGlobalTableWitnessGroupMemberAction.builder()
                    .regionName(witnessRegion.id())
                    .build())
                .build();

            UpdateTableRequest updateTableRequest = UpdateTableRequest.builder()
                .tableName(tableName)
                .replicaUpdates(List.of(replicaUpdate))
                .globalTableWitnessUpdates(List.of(witnessUpdate))
                .build();

            UpdateTableResponse response = dynamoDbClient.updateTable(updateTableRequest);
            LOGGER.info("MRSC cleanup initiated - removing replica and witness. Response: " + response);

            return response;

        } catch (DynamoDbException e) {
            LOGGER.severe("Failed to cleanup MRSC replicas: " + tableName + " - " + e.getMessage());
            throw DynamoDbException.builder()
                .message("Failed to cleanup MRSC replicas: " + tableName)
                .cause(e)
                .build();
        }
    }
```
使用AWS SDK for Java 2.x 完成 MRSC 工作流程演示。  

```
    public static void demonstrateCompleteMRSCWorkflow(
        final DynamoDbClient primaryClient,
        final DynamoDbClient replicaClient,
        final String tableName,
        final Region replicaRegion,
        final Region witnessRegion)
        throws InterruptedException {

        if (primaryClient == null) {
            throw new IllegalArgumentException("Primary DynamoDB client cannot be null");
        }
        if (replicaClient == null) {
            throw new IllegalArgumentException("Replica DynamoDB client cannot be null");
        }
        if (tableName == null || tableName.trim().isEmpty()) {
            throw new IllegalArgumentException("Table name cannot be null or empty");
        }
        if (replicaRegion == null) {
            throw new IllegalArgumentException("Replica region cannot be null");
        }
        if (witnessRegion == null) {
            throw new IllegalArgumentException("Witness region cannot be null");
        }

        try {
            LOGGER.info("=== Starting Complete MRSC Workflow Demonstration ===");

            // Step 1: Create an empty single-Region table
            LOGGER.info("Step 1: Creating empty single-Region table");
            createRegionalTable(primaryClient, tableName);

            // Use the existing GlobalTableOperations method for basic table waiting
            LOGGER.info("Intermediate step: Waiting for table [" + tableName + "] to become active before continuing");
            GlobalTableOperations.waitForTableActive(primaryClient, tableName);

            // Step 2: Convert to MRSC with replica and witness
            LOGGER.info("Step 2: Converting to MRSC with replica and witness");
            convertToMRSCWithWitness(primaryClient, tableName, replicaRegion, witnessRegion);

            // Wait for MRSC conversion to complete using MRSC-specific waiter
            LOGGER.info("Waiting for MRSC conversion to complete...");
            waitForMRSCReplicasActive(primaryClient, tableName);

            LOGGER.info("Intermediate step: Waiting for table [" + tableName + "] to become active before continuing");
            GlobalTableOperations.waitForTableActive(primaryClient, tableName);

            // Step 3: Verify MRSC configuration
            LOGGER.info("Step 3: Verifying MRSC configuration");
            describeMRSCTable(primaryClient, tableName);

            // Step 4: Test strong consistency with data operations
            LOGGER.info("Step 4: Testing strong consistency with data operations");

            // Add test item to primary region
            putTestItem(primaryClient, tableName, "The Beatles", "Hey Jude", "The Beatles 1967-1970", "1968");

            // Immediately read from replica region (no wait needed with MRSC)
            LOGGER.info("Reading from replica region immediately (strong consistency):");
            GetItemResponse getResponse =
                getItemWithConsistentRead(replicaClient, tableName, "The Beatles", "Hey Jude");

            if (getResponse.hasItem()) {
                LOGGER.info("✓ Strong consistency verified - item immediately available in replica region");
            } else {
                LOGGER.warning("✗ Item not found in replica region");
            }

            // Test conditional update from replica region
            LOGGER.info("Testing conditional update from replica region:");
            performConditionalUpdate(replicaClient, tableName, "The Beatles", "Hey Jude", "5");
            LOGGER.info("✓ Conditional update successful - demonstrates strong consistency");

            // Step 5: Cleanup
            LOGGER.info("Step 5: Cleaning up resources");
            cleanupMRSCReplicas(primaryClient, tableName, replicaRegion, witnessRegion);

            // Wait for cleanup to complete using basic table waiter
            LOGGER.info("Waiting for replica cleanup to complete...");
            GlobalTableOperations.waitForTableActive(primaryClient, tableName);

            // "Halt" until replica/witness cleanup is complete
            DescribeTableResponse cleanupVerification = describeMRSCTable(primaryClient, tableName);
            int backoffSeconds = 5; // Start with 5 second intervals
            while (cleanupVerification.table().multiRegionConsistency() != null) {
                LOGGER.info("Waiting additional time (" + backoffSeconds + " seconds) for MRSC cleanup to complete...");
                tempWait(backoffSeconds);

                // Exponential backoff with cap
                backoffSeconds = Math.min(backoffSeconds * 2, 30);
                cleanupVerification = describeMRSCTable(primaryClient, tableName);
            }

            // Delete the primary table
            deleteTable(primaryClient, tableName);

            LOGGER.info("=== MRSC Workflow Demonstration Complete ===");
            LOGGER.info("");
            LOGGER.info("Key benefits of Multi-Region Strong Consistency (MRSC):");
            LOGGER.info("- Immediate consistency across all regions (no eventual consistency delays)");
            LOGGER.info("- Simplified application logic (no need to handle eventual consistency)");
            LOGGER.info("- Support for conditional writes and transactions across regions");
            LOGGER.info("- Consistent read operations from any region without waiting");

        } catch (DynamoDbException | InterruptedException e) {
            LOGGER.severe("MRSC workflow failed: " + e.getMessage());
            throw e;
        }
    }
```
+ 有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的以下主题。
  + [CreateTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/CreateTable)
  + [DeleteTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/DeleteTable)
  + [DescribeTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/DescribeTable)
  + [GetItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/GetItem)
  + [PutItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/PutItem)
  + [UpdateItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateItem)
  + [UpdateTable](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateTable)

------

# DynamoDB 全局表安全性
<a name="globaltables-security"></a>

全局表副本是 DynamoDB 表，因此，您可以使用与控制单区域表的访问权限相同的方法，来控制副本的访问权限，包括使用 AWS Identity and Access Management（IAM）身份策略和基于资源的策略。

本主题介绍如何使用 IAM 权限和 AWS Key Management Service（AWS KMS）加密来保护 DynamoDB 全局表。您将了解用于实现跨区域复制和自动扩缩的服务相关角色（SLR），创建、更新和删除全局表所需的 IAM 权限，以及多区域最终一致性（MREC）与多区域强一致性（MRSC）表之间的区别。您还将学习用于安全地管理跨区域复制的 AWS KMS 加密密钥。

## 全局表的服务相关角色
<a name="globaltables-slr"></a>

DynamoDB 全局表依靠服务相关角色（SLR）来管理跨区域复制和自动扩缩功能。

您只需为每个 AWS 账户设置一次这些角色。创建后，同一个角色将用于您的账户中的所有全局表。有关服务相关角色的更多信息，请参见 *IAM 用户指南*中的[使用服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html)。

### 复制服务相关角色
<a name="globaltables-replication-slr"></a>

当您首次创建全局表时，Amazon DynamoDB 会自动为您创建一个 `AWSServiceRoleForDynamoDBReplication` 服务相关角色（SLR）。此角色为您管理跨区域复制。

在将基于资源的策略应用于副本时，请确保不要拒绝向 SLR 主体授予在 `AWSServiceRoleForDynamoDBReplicationPolicy` 中定义的任何权限，因为这会中断复制。如果您拒绝所需的 SLR 权限，则与受影响副本之间的复制将停止，副本表的状态将更改为 `REPLICATION_NOT_AUTHORIZED`。
+ 对于多区域最终一致性（MREC）全局表，如果副本保持在 `REPLICATION_NOT_AUTHORIZED` 状态的时间超过 20 小时，则该副本将不可逆地转换为单区域 DynamoDB 表。
+ 对于多区域强一致性（MRSC）全局表，拒绝所需权限将导致写入和强一致性读取操作的 `AccessDeniedException`。如果副本保持 `REPLICATION_NOT_AUTHORIZED` 状态的时间超过七天，则该副本将变为永久无法访问，并且写入和强一致性读取操作将继续失败并出现错误。某些管理操作（例如副本删除）将获得成功。

### 自动扩缩服务相关角色
<a name="globaltables-autoscaling-slr"></a>

在将全局表配置为调配容量模式时，必须为全局表配置自动扩缩。DynamoDB 自动扩缩功能使用 AWS Application Auto Scaling 服务来动态调整全局表副本上的预调配吞吐能力。Application Auto Scaling 服务创建名为 [https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html](https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html) 的服务相关角色（SLR）。当您首次为 DynamoDB 表配置自动扩缩时，将在您的 AWS 账户中自动创建此服务相关角色。此角色允许 Application Auto Scaling 管理预调配的表容量并创建 CloudWatch 警报。

 在将基于资源的策略应用于副本时，请确保不要拒绝向 Application Auto Scaling SLR 主体授予在 [https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSApplicationAutoscalingDynamoDBTablePolicy.html](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSApplicationAutoscalingDynamoDBTablePolicy.html) 中定义的任何权限，因为这将中断自动扩缩功能。

### 服务相关角色的 IAM 策略示例
<a name="globaltables-example-slr"></a>

符合以下条件的 IAM 策略不会影响对 DynamoDB 复制 SLR 和 AWS 自动扩缩 SLR 所需的权限。此条件可以添加到原本限制性较宽泛的其他策略中，以避免无意间中断复制或自动扩缩。

#### 从拒绝策略中排除所需的 SLR 权限
<a name="example-exclude-slr-policy"></a>

以下示例说明如何从拒绝语句中排除服务相关角色主体：

```
"Condition": {
    "StringNotEquals": {
        "aws:PrincipalArn": [
            "arn:aws::iam::111122223333:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication",
            "arn:aws::iam::111122223333:role/aws-service-role/dynamodb.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_DynamoDBTable"
        ]
    }
}
```

## 全局表如何使用 AWS IAM
<a name="globaltables-iam"></a>

以下各节介绍不同全局表操作所需的权限，并提供了策略示例，以帮助您为用户和应用程序配置适当的访问权限。

**注意**  
描述的所有权限都必须应用于受影响区域中的特定表资源 ARN。表资源 ARN 采用格式 `arn:aws:dynamodb:region:account-id:table/table-name`，您需要指定实际区域、账户 ID 和表名值。

**Topics**
+ [创建全局表和添加副本](#globaltables-creation-iam)
+ [更新全局表](#globaltables-update-iam)
+ [删除全局表和移除副本](#globaltables-delete-iam)

### 创建全局表和添加副本
<a name="globaltables-creation-iam"></a>

DynamoDB 全局表支持两种一致性模式：多区域最终一致性（MREC）和多区域强一致性（MRSC）。MREC 全局表可以在任意数量的区域中有多个副本，并提供最终的一致性。MRSC 全局表则必须部署在三个区域（三个副本，或两个副本和一个见证者），且提供强一致性，并实现零恢复点目标（RPO）。

创建全局表所需的权限取决于您创建的全局表是否具有见证者。

#### 创建全局表的权限
<a name="globaltables-creation-iam-all-types"></a>

初始全局表创建和以后添加副本都需要以下权限。这些权限适用于多区域最终一致性（MREC）和多区域强一致性（MRSC）全局表。
+ 全局表需要跨区域复制，DynamoDB 通过 [`AWSServiceRoleForDynamoDBReplication`](#globaltables-replication-slr) 服务相关角色（SLR）进行管理。以下权限允许 DynamoDB 在您首次创建全局表时自动创建此角色：
  + `iam:CreateServiceLinkedRole`
+ 要使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) API 创建全局表或添加副本，您必须具有对源表资源的以下权限：
  + `dynamodb:UpdateTable`
+ 要添加副本，您必须具有对区域中表资源的以下权限：
  + `dynamodb:CreateTable`
  + `dynamodb:CreateTableReplica`
  + `dynamodb:Query`
  + `dynamodb:Scan`
  + `dynamodb:UpdateItem`
  + `dynamodb:PutItem`
  + `dynamodb:GetItem`
  + `dynamodb:DeleteItem`
  + `dynamodb:BatchWriteItem`

#### 使用见证者的 MRSC 全局表的其他权限
<a name="globaltables-creation-iam-witness"></a>

在创建带有见证者区域的多区域强一致性（MRSC）全局表时，对于所有参与区域（包括副本区域和见证者区域）的表资源，您必须具有以下权限：
+ `dynamodb:CreateGlobalTableWitness`

#### 用于创建全局表的 IAM 策略示例
<a name="globaltables-creation-iam-example"></a>

##### 创建跨三个区域的 MREC 或 MRSC 全局表
<a name="globaltables-creation-iam-example-three-regions"></a>

以下基于身份的策略让您可以在三个区域中创建名为“users”的 MREC 或 MRSC 全局表，包括创建所需的 DynamoDB 复制服务相关角色。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowCreatingUsersGlobalTable",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateTable",
        "dynamodb:CreateTableReplica",
        "dynamodb:UpdateTable",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:UpdateItem",
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem",
        "dynamodb:BatchWriteItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/users",
        "arn:aws:dynamodb:us-east-2:123456789012:table/users",
        "arn:aws:dynamodb:us-west-2:123456789012:table/users"
      ]
    },
    {
      "Sid": "AllowCreatingSLR",
      "Effect": "Allow",
      "Action": [
        "iam:CreateServiceLinkedRole"
      ],
      "Resource": [
        "arn:aws:iam::123456789012:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication"
      ]
    }
  ]
}
```

------

##### 将 MREC 或 MRSC 全局表的创建限制在特定区域
<a name="globaltables-creation-iam-example-restrict-regions"></a>

以下基于身份的策略让您可以使用 [aws:RequestedRegion](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-requestedregion) 条件键，跨特定区域创建 DynamoDB 全局表副本，包括创建所需的 DynamoDB 复制服务相关角色。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowAddingReplicasToSourceTable",
      "Effect": "Allow",
      "Action": [
        "dynamodb:UpdateTable"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": [
            "us-east-1"
          ]
        }
      }
    },
    {
      "Sid": "AllowCreatingReplicas",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateTable",
        "dynamodb:CreateTableReplica",
        "dynamodb:UpdateTable",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:UpdateItem",
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem",
        "dynamodb:BatchWriteItem"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": [
            "us-east-2",
            "us-west-2"
          ]
        }
      }
    },
    {
      "Sid": "AllowCreatingSLR",
      "Effect": "Allow",
      "Action": [
        "iam:CreateServiceLinkedRole"
      ],
      "Resource": [
        "arn:aws:iam::123456789012:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication"
      ]
    }
  ]
}
```

------

##### 创建具有见证者的 MRSC 全局表
<a name="globaltables-creation-iam-example-witness"></a>

以下基于身份的策略让您可以创建一个名为“users”的 DynamoDB MRSC 全局表（包括创建所需的 DynamoDB 复制服务相关角色），其副本位于 us-east-1 和 us-east-2 中，见证者位于 us-west-2 中。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowCreatingUsersGlobalTableWithWitness",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateTable",
        "dynamodb:CreateTableReplica",
        "dynamodb:CreateGlobalTableWitness",
        "dynamodb:UpdateTable",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:UpdateItem",
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem",
        "dynamodb:BatchWriteItem"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/users",
        "arn:aws:dynamodb:us-east-2:123456789012:table/users"
      ]
    },
    {
      "Sid": "AllowCreatingSLR",
      "Effect": "Allow",
      "Action": [
        "iam:CreateServiceLinkedRole"
      ],
      "Resource": [
        "arn:aws:iam::123456789012:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication"
      ]
    }
  ]
}
```

------

##### 将创建 MRSC 见证者限制在特定区域
<a name="globaltables-creation-iam-example-restrict-witness-regions"></a>

此基于身份的策略让您可以使用 [aws:RequestedRegion](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-requestedregion) 条件键创建 MRSC 全局表（包括创建所需的 DynamoDB 复制服务相关角色），并将副本限制在特定区域，而不限制可在所有区域上的创建见证者。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowCreatingReplicas",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateTable",
        "dynamodb:CreateTableReplica",
        "dynamodb:UpdateTable",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:UpdateItem",
        "dynamodb:PutItem",
        "dynamodb:GetItem",
        "dynamodb:DeleteItem",
        "dynamodb:BatchWriteItem"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": [
            "us-east-1",
            "us-east-2"
          ]
        }
      }
    },
    {
      "Sid": "AllowCreatingWitness",
      "Effect": "Allow",
      "Action": [
        "dynamodb:CreateGlobalTableWitness"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowCreatingSLR",
      "Effect": "Allow",
      "Action": [
        "iam:CreateServiceLinkedRole"
      ],
      "Resource": [
        "arn:aws:iam::123456789012:role/aws-service-role/replication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBReplication"
      ]
    }
  ]
}
```

------

### 更新全局表
<a name="globaltables-update-iam"></a>

要使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) API 修改现有全局表的副本设置，对于进行 API 调用的区域中的表资源，您需要具有以下权限：
+ `dynamodb:UpdateTable`

此外，您还可以更新其他全局表配置，例如自动扩缩策略和生存时间设置。这些额外的更新操作需要以下权限：
+ 要通过 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTableReplicaAutoScaling.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTableReplicaAutoScaling.html) API 更新副本自动扩缩策略，对于包含副本的所有区域中的表资源，您必须具有以下权限：
  + `application-autoscaling:DeleteScalingPolicy`
  + `application-autoscaling:DeleteScheduledAction`
  + `application-autoscaling:DeregisterScalableTarget`
  + `application-autoscaling:DescribeScalableTargets`
  + `application-autoscaling:DescribeScalingActivities`
  + `application-autoscaling:DescribeScalingPolicies`
  + `application-autoscaling:DescribeScheduledActions`
  + `application-autoscaling:PutScalingPolicy`
  + `application-autoscaling:PutScheduledAction`
  + `application-autoscaling:RegisterScalableTarget`
+ 要使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTimeToLive.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTimeToLive.html) API 更新生存时间设置，对于包含副本的所有区域中的表资源，您必须具有以下权限：
  + `dynamodb:UpdateTimeToLive`

  请注意，只有配置了多区域最终一致性（MREC）的全局表支持生存时间（TTL）。有关全局表与 TTL 结合使用的更多信息，请参阅 [DynamoDB 全局表工作原理](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html)。

### 删除全局表和移除副本
<a name="globaltables-delete-iam"></a>

要删除全局表，您必须移除所有副本。根据您删除的是带还是不带见证者区域的全局表，此操作所需的权限会有所不同。

#### 删除全局表和移除副本的权限
<a name="globaltables-delete-iam-all-types"></a>

删除单个副本和完全删除全局表均需要以下权限。删除全局表配置只会移除不同区域中表之间的复制关系。它不会删除剩下的最后一个区域中的基础 DynamoDB 表。最后一个区域中的表会作为标准 DynamoDB 表继续存在，具有相同的数据和设置。这些权限适用于多区域最终一致性（MREC）和多区域强一致性（MRSC）全局表。
+ 要使用 [https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) API 从全局表中移除副本，对于进行 API 调用的区域中的表资源，您需要具有以下权限：
  + `dynamodb:UpdateTable`
+ 在要移除副本的每个区域，您需要具有对表资源的以下权限：
  + `dynamodb:DeleteTable`
  + `dynamodb:DeleteTableReplica`

#### 使用见证者的 MRSC 全局表的其他权限
<a name="globaltables-delete-iam-witness"></a>

要删除带有见证者的多区域强一致性（MRSC）全局表，对于所有参与区域（包括副本区域和见证者区域）的表资源，您必须具有以下权限：
+ `dynamodb:DeleteGlobalTableWitness`

#### 删除全局表副本的 IAM 策略示例
<a name="globaltables-delete-iam-example"></a>

##### 删除全局表副本
<a name="globaltables-delete-replicas-iam-example"></a>

此基于身份的策略让您可以删除名为“users”的 DynamoDB 全局表及其跨三个区域的副本：

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:UpdateTable",
        "dynamodb:DeleteTable",
        "dynamodb:DeleteTableReplica"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/users",
        "arn:aws:dynamodb:us-east-2:123456789012:table/users",
        "arn:aws:dynamodb:us-west-2:123456789012:table/users"
      ]
    }
  ]
}
```

------

##### 删除带有见证者的 MRSC 全局表
<a name="globaltables-delete-witness-iam-example"></a>

此基于身份的策略让您可以删除名为“users”的 MRSC 全局表的副本和见证者：

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:UpdateTable",
        "dynamodb:DeleteTable",
        "dynamodb:DeleteTableReplica",
        "dynamodb:DeleteGlobalTableWitness"
      ],
      "Resource": [
        "arn:aws:dynamodb:us-east-1:123456789012:table/users",
        "arn:aws:dynamodb:us-east-2:123456789012:table/users"
      ]
    }
  ]
}
```

------

## 全局表如何使用 AWS KMS
<a name="globaltables-kms"></a>

与所有 DynamoDB 表一样，全局表副本始终使用存储在 AWS Key Management Service（AWS KMS）中的加密密钥对静态数据进行加密。

全局表中的所有副本都必须配置为使用相同类型的 KMS 密钥（AWS 拥有的密钥、AWS 托管式密钥或客户自主管理型密钥）。

**重要**  
DynamoDB 需要访问副本的加密密钥才能删除副本。如果您因删除副本而想要禁用或删除用于加密该副本的客户自主管理型密钥，则应先删除该副本，等待剩余副本之一的表状态更改为 `ACTIVE`，然后再禁用或删除该密钥。

对于配置为多区域最终一致性（MREC）的全局表，如果您禁用或撤销 DynamoDB 对用于加密副本的客户自主管理型密钥的访问权限，则复制到副本和从副本进行复制的过程将停止，而副本状态将更改为 `INACCESSIBLE_ENCRYPTION_CREDENTIALS`。如果 MREC 全局表中的副本保持 `INACCESSIBLE_ENCRYPTION_CREDENTIALS` 状态的时间超过 20 小时，则该副本将不可逆地转换为单区域 DynamoDB 表。

对于配置为多区域强一致性（MRSC）的全局表，如果您禁用或撤销 DynamoDB 对用于加密副本的客户自主管理型密钥的访问权限，则复制到副本和从副本进行复制的过程将停止，尝试对副本执行写入或强一致性读取操作将返回错误，而副本状态将更改为 `INACCESSIBLE_ENCRYPTION_CREDENTIALS`。如果 MRSC 全局表中的副本保持 `INACCESSIBLE_ENCRYPTION_CREDENTIALS` 状态的时间超过七天，则根据撤销的特定权限，该副本将归档或变得永久无法访问。

# DynamoDB 多账户全局表
<a name="globaltables-MultiAccount"></a>

多账户全局表可自动跨多个 AWS 区域和多个 AWS 账户复制 DynamoDB 表数据，从而提高韧性，在账户级别隔离工作负载，并应用不同的安全和治理控制措施。每个副本表驻留在不同的 AWS 账户中，来实现区域和账户级别的故障隔离。您还可以让副本与您的 AWS 组织结构保持一致。相比同账户全局表，多账户全局表提供了额外的隔离、治理和安全性优势。

多账户全局表具有以下优点：
+ 跨您选择的 AWS 账户和区域自动复制 DynamoDB 表数据
+ 通过在具有不同策略、护栏和合规边界的账户之间复制数据，增强安全性和治理措施
+ 通过将副本放在单独的 AWS 账户中，提高运营韧性和实现账户级别的故障隔离
+ 使用多账户策略时，按业务部门或所有权来匹配工作负载
+ 将每个副本的费用计入各自的 AWS 账户，简化了成本归因

有关更多信息，请参阅 [Benefits of using multiple AWS accounts](https://docs.aws.amazon.com/whitepapers/latest/organizing-your-aws-environment/benefits-of-using-multiple-aws-accounts.html)。如果您的工作负载不需要多账户复制，或者您希望通过本地覆盖实现更简单的副本管理，则可以继续使用同账户全局表。

您可以配置具有[多区域最终一致性（MREC）](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes.mrec)的多账户全局表。配置了[多区域强一致性（MRSC）](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes.mrsc)的全局表不支持多账户模型。

**Topics**
+ [DynamoDB 全局表工作原理](V2globaltables_MA_HowItWorks.md)
+ [教程：创建多账户全局表](V2globaltables_MA.tutorial.md)
+ [DynamoDB 全局表安全性](globaltables_MA_security.md)

# DynamoDB 全局表工作原理
<a name="V2globaltables_MA_HowItWorks"></a>

多账户全局表将 DynamoDB 全局表扩展为完全托管式、无服务器、多区域且具备多活功能的表，可以跨多个 AWS 账户。多账户全局表跨 AWS 区域和账户复制数据，提供与同账户全局表相同的双活功能。在您写入任一副本时，DynamoDB 会将数据复制到所有其他副本。

与同账户全局表的主要区别包括：
+ 多区域最终一致性（MREC）全局表支持多账户复制。
+ 您只能从单区域表开始添加副本。不支持将现有的同账户全局表转换为多账户设置。要进行迁移，您必须先删除现有副本以返回到单区域表，然后再创建新的多账户全局表。
+ 每个副本必须位于一个单独的 AWS 账户中。对于具有 *N* 个副本的多账户全局表，您必须有 *N* 个账户。
+ 默认情况下，多账户全局表对所有副本使用统一的表设置。所有副本自动共享相同的配置（例如吞吐量模式和 TTL），与同账户全局表不同的是，不能覆盖各个副本的这些设置。
+ 客户必须在其资源策略中，向 DynamoDB 全局表服务主体提供复制权限。

多账户全局表使用与同账户全局表相同的底层复制技术。表设置会自动复制到所有区域副本，并且客户无法覆盖或自定义各个副本的设置。对于加入到同一个全局表的多个 AWS 账户，这样可以确保在各个账户之间保持一致的配置和可预测的行为。

DynamoDB 全局表中的设置定义了表的行为方式以及如何跨区域复制数据。这些设置是在创建表或添加新的区域副本时，通过 DynamoDB 控制面板 API 进行配置。

创建多账户全局表时，客户必须为每个区域副本设置 `GlobalTableSettingsReplicationMode = ENABLED`。这样可以确保在一个区域中所做的配置更改，会自动传播到加入了全局表的所有其他区域。

您可以在表创建之后启用设置复制。这样便能支持如下场景：表最初创建作为单个区域表，后来升级为多账户全局表。

**同步的设置**

对于多账户全局表，以下表设置始终在所有副本之间同步：

**注意**  
与同账户全局表不同，多账户全局表不允许在各个区域中覆盖这些设置。唯一的例外是，允许对读取自动扩缩策略（表和 GSI）进行覆盖，因为这些是独立的外部资源。
+ 容量模式（预置容量或按需）
+ 表的预调配读取和写入容量
+ 表的读取和写入自动扩缩
+ 本地二级索引（LSI）定义
+ 全局二级索引（GSI）定义
+ GSI 的预调配读取和写入容量
+ GSI 的读取和写入自动扩缩
+ MREC 模式下的流定义
+ 生存时间（TTL）
+ 热吞吐量
+ 按需最大读取和写入吞吐量

**不同步的设置**

以下设置不在副本之间同步，必须为每个区域中的每个副本表单独配置。
+ 表类
+ 服务器端加密（SSE）类型
+ 时间点故障恢复
+ 服务器端加密（SSE）KMS 密钥 ID
+ 删除保护
+ Kinesis Data Streams（KDSD）
+ 标签
+ 资源策略
+ 表 Cloudwatch-Contributor Insights（CCI）
+ GSI Cloudwatch-Contributor Insights（CCI）

## 监控
<a name="V2globaltables_MA_HowItWorks.monitoring"></a>

配置为多区域最终一致性（MREC）的全局表会将 [`ReplicationLatency`](metrics-dimensions.md#ReplicationLatency) 指标发布到 CloudWatch。此指标跟踪从一个项目写入到副本表到该项目出现在全局表的另一个副本中之间经过的时间。`ReplicationLatency` 以毫秒表示，并针对全局表中的每个源区域和目标区域对发出。

典型的 `ReplicationLatency` 值取决于所选 AWS 区域之间的距离以及工作负载类型和吞吐量等其它变量。例如，与非洲（开普敦）（af-south-1）区域相比，美国西部（北加利福尼亚）（us-west-1）区域的源副本到美国西部（俄勒冈州）（us-west-2）区域的 `ReplicationLatency` 更低。

`ReplicationLatency` 值不断增加可能表明来自一个副本的更新没有及时传播到其它副本表。在这种情况下，可以临时将应用程序的读取和写入活动重定向到不同的 AWS 区域。

**处理多账户全局表中的复制延迟问题**

如果由于副本表上客户引发的问题导致 `ReplicationLatency` 超过 3 小时，DynamoDB 会发送通知，要求客户解决潜在问题。客户引发的可能会阻碍复制的常见问题包括：
+ 从副本表的资源策略中移除了所需的权限
+ 选择退出托管多账户全局表副本的 AWS 区域
+ 拒绝解密数据所需的表的 AWS KMS 密钥权限

DynamoDB 会在 3 小时的延长复制延迟时间内发送初始通知，如果问题仍未解决，则会在 20 小时后发送第二份通知。如果问题未在要求的时间窗口内得到纠正，DynamoDB 将自动取消副本与全局表的关联。然后，受影响的副本将转换为区域表。

# 教程：创建多账户全局表
<a name="V2globaltables_MA.tutorial"></a>

本节提供分步说明，指导您创建跨多个 AWS 账户的 DynamoDB 全局表。

## 使用 DynamoDB 控制台创建多账户全局表
<a name="create-ma-gt-console"></a>

按照以下步骤，使用 AWS 管理控制台创建多账户全局表。以下示例创建了一个全局表，并在美国创建了副本表。

1. 对于第一个账户（假设为 *111122223333*），登录 AWS 管理控制台并打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 对于本示例，从导航栏的区域选择器中选择**美国东部（俄亥俄州）**。

1. 在控制台左侧的导航窗格中，选择**表**。

1. 选择**创建表**。

1. 在**创建表**页面上：

   1. 对于**表名称**，输入 **MusicTable**。

   1. 对于**分区键**，输入 **Artist**。

   1. 对于**排序键**，输入 **SongTitle**。

   1. 保留其它默认设置，然后选择**创建表**。

1. 将以下资源策略添加到表中

------
#### [ JSON ]

****  

   ```
   {
   "Version":"2012-10-17",		 	 	 
   "Statement": [
       {
           "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
           "Effect": "Allow",
           "Action": [
               "dynamodb:ReadDataForReplication",
               "dynamodb:WriteDataForReplication",
               "dynamodb:ReplicateSettings"
           ],
           "Resource": "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable",
           "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
           "Condition": {
               "StringEquals": {
                   "aws:SourceAccount": ["444455556666","111122223333"],
                   "aws:SourceArn": [
                       "arn:aws:dynamodb:us-east-1:444455556666:table/MusicTable",
                       "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable"
                   ]
               }
           }
       },
       {
           "Sid": "AllowTrustedAccountsToJoinThisGlobalTable",
           "Effect": "Allow",
           "Action": [
               "dynamodb:AssociateTableReplica"
           ],
           "Resource": "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable",
           "Principal": {"AWS": ["444455556666"]}
       }
   ]
   }
   ```

------

1. 此新表在新的全局表中用作第一个副本表。这是您稍后添加的其他副本表的原型。

1. 等待表的状态变为**活动**。对于新创建的表，从**全局表**选项卡中导航到**设置复制**，然后单击**启用**。

1. 从此账户（此处为 *111122223333*）退出。

1. 对于第二个账户（假设为 *444455556666*），登录 AWS 管理控制台并打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 对于本示例，从导航栏的区域选择器中选择**美国东部（弗吉尼亚州北部）**。

1. 控制台将确保所选区域中不存在同名的表。如果有同名的表，则必须删除现有表，然后才能在该区域创建新的副本表。

1. 在**创建表**旁边的下拉列表中，选择**从另一个账户创建**

1. 在**从另一个账户创建表**页面上：

   1. 添加 **arn:aws:dynamodb:us-east-2:*111122223333*:table/MusicTable** 作为源表的表 ARN。

   1. 在**副本表 ARN** 中，再次添加源表的 ARN **arn:aws:dynamodb:us-east-2:*111122223333*:table/MusicTable**。如果多账户全局表中已经存在多个副本，则必须将每个现有副本添加到 ReplicaTableARN。

   1. 保留其他默认设置，然后选择**提交**。

1. 音乐表（以及任何其他副本表）的**全局表**选项卡将显示该表已在多个区域中复制。

1. 测试复制：

   1. 您可以使用任何存在此表副本的区域

   1. 选择**浏览表项目**。

   1. 选择**创建项目**。

   1. 对于**艺术家**，输入 **item\$11**；而对于**歌名**，则输入 **Song Value 1**。

   1. 选择**创建项目**。

   1. 通过切换到其它区域来验证复制：

   1. 验证 Music 表中是否包含您创建的项目。

## 使用 AWS CLI 创建多账户全局表
<a name="ma-gt-cli"></a>

以下示例说明如何使用 AWS CLI 创建多账户全局表。这些示例演示了设置跨账户复制的完整工作流。

------
#### [ CLI ]

使用以下 AWS CLI 命令创建具有跨账户复制功能的多账户全局表。

```
# STEP 1: Setting resource policy for the table in account 111122223333

cat > /tmp/source-resource-policy.json << 'EOF'
{
    "Version": "2012-10-17", 		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": ["444455556666","111122223333"],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:us-east-1:444455556666:table/MusicTable",
                        "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable"
                    ]
                }
            }
        },
        {
            "Sid": "AllowTrustedAccountsToJoinThisGlobalTable",
            "Effect": "Allow",
            "Action": [
                "dynamodb:AssociateTableReplica"
            ],
            "Resource": "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable",
            "Principal": {"AWS": ["444455556666"]}
        }
    ]
}
EOF

# Step 2: Create a new table (MusicTable) in US East (Ohio), 
#   with DynamoDB Streams enabled (NEW_AND_OLD_IMAGES),
#   and Settings Replication ENABLED on the account 111122223333

aws dynamodb create-table \
    --table-name MusicTable \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --billing-mode PAY_PER_REQUEST \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES \
    --global-table-settings-replication-mode ENABLED \
    --resource-policy file:///tmp/source-resource-policy.json \
    --region us-east-2 


# Step 3: Creating replica table in account 444455556666

# Resource policy for account 444455556666
cat > /tmp/dest-resource-policy.json << 'EOF'
{
    "Version": "2012-10-17", 		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:444455556666:table/MusicTable",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": ["444455556666","111122223333"],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:us-east-1:444455556666:table/MusicTable",
                        "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable"
                    ]
                }
            }
        }
    ]
}
EOF

# Execute the replica table creation
aws dynamodb create-table \
    --table-name MusicTable \
    --global-table-source-arn "arn:aws:dynamodb:us-east-2:111122223333:table/MusicTable" \
    --resource-policy file:///tmp/dest-resource-policy.json \
    --global-table-settings-replication-mode ENABLED \
    --region us-east-1

# Step 4: View the list of replicas created using describe-table
aws dynamodb describe-table \
    --table-name MusicTable \
    --region us-east-2 \
    --query 'Table.{TableName:TableName,TableStatus:TableStatus,MultiRegionConsistency:MultiRegionConsistency,Replicas:Replicas[*].{Region:RegionName,Status:ReplicaStatus}}'

# Step 5: To verify that replication is working, add a new item to the Music table in US East (Ohio)
aws dynamodb put-item \
    --table-name MusicTable \
    --item '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region us-east-2

# Step 6: Wait for a few seconds, and then check to see whether the item has been 
# successfully replicated to US East (N. Virginia) and Europe (Ireland)
aws dynamodb get-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region us-east-1

aws dynamodb get-item \
    --table-name MusicTable \
    --key '{"Artist": {"S":"item_1"},"SongTitle": {"S":"Song Value 1"}}' \
    --region us-east-2

# Step 7: Delete the replica table in US East (N. Virginia) Region
aws dynamodb delete-table \
    --table-name MusicTable \
    --region us-east-1

# Clean up: Delete the primary table
aws dynamodb delete-table \
    --table-name MusicTable \
    --region us-east-2
```

------

# DynamoDB 全局表安全性
<a name="globaltables_MA_security"></a>

全局表副本是 DynamoDB 表，因此，您可以使用与控制单区域表的访问权限相同的方法，来控制副本的访问权限，包括使用 AWS Identity and Access Management（IAM）身份策略和基于资源的策略。本主题介绍如何使用 IAM 权限和 AWS Key Management Service（AWS KMS）加密来保护 DynamoDB 多账户全局表。您将了解对于多区域最终一致性（MREC）表，用于实现跨区域、跨账户复制和自动扩缩的基于资源的策略与服务相关角色（SLR），以及创建、更新和删除全局表所需的 IAM 权限。您还将学习用于安全地管理跨区域复制的 AWS KMS 加密密钥。

其中提供了详细信息，介绍建立跨账户和跨区域表复制所需的基于资源的策略和权限。对于需要实施安全的跨账户数据复制解决方案的客户来说，了解这种安全模型至关重要。

## 复制的服务主体授权
<a name="globaltables_MA_service_principal"></a>

DynamoDB 的多账户全局表使用不同的授权方法，因为复制是跨账户边界执行的。此过程使用 DynamoDB 的复制服务主体完成：`replication.dynamodb.amazonaws.com`。每个参与账户都必须在副本表的资源策略中明确允许该主体，向其授予权限，这些权限可以根据键（例如 `aws:SourceAccount`、`aws:SourceArn` 等）的源上下文条件限制到特定副本，有关更多详细信息，请参阅 [AWS 全局条件键](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html)。权限是双向的，这意味着所有副本都必须明确地相互授予权限，然后才能在任何特定的副本对之间建立复制关系。

对于跨账户复制，以下服务主体权限至关重要：
+ `dynamodb:ReadDataForReplication` 授予读取数据用于复制用途的能力。通过此权限可以读取一个副本中的更改，并将更改传播到其他副本。
+ `dynamodb:WriteDataForReplication` 允许将复制的数据写入目标表。通过此权限可以在全局表中的所有副本之间同步更改。
+ `dynamodb:ReplicateSettings` 实现了跨副本的表设置同步，从而为所有加入的表提供一致的配置。

每个副本都必须向所有其他副本及其自身授予上述权限，也就是说，源上下文条件必须包括构成全局表的完整副本集。在将每个新副本添加到多账户全局表时，都会对其验证这些权限。这可以确保只有获得授权的 DynamoDB 服务才能执行复制操作，并且只能在所需的表之间执行。

## 多账户全局表的服务相关角色
<a name="globaltables_MA_service_linked_roles"></a>

DynamoDB 多账户全局表跨所有副本复制设置，因此每个副本的设置完全相同，吞吐量一致，并提供无缝的失效转移体验。这些设置的复制通过服务主体上的 `ReplicateSettings` 权限进行控制，不过也可以依靠服务相关角色（SLR）来管理某些跨账户跨区域复制和自动扩缩功能。每个 AWS 账户只需设置一次这些角色。创建后，同一个角色将用于您的账户中的所有全局表。有关服务相关角色的更多信息，请参见《IAM 用户指南》中的[使用服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create-service-linked-role.html)。

### 设置管理服务相关角色
<a name="globaltables_MA_settings_management_slr"></a>

当您在账户中创建第一个多账户全局表副本时，Amazon DynamoDB 会自动创建 AWSServiceRoleForDynamoDBGlobalTableSettingsManagement 服务相关角色（SLR）。此角色管理您的跨账户跨区域复制设置。

在将基于资源的策略应用于副本时，请确认您没有拒绝在 `AWSServiceRoleForDynamoDBGlobalTableSettingsManagement` 中定义的向 SLR 主体授予的任何权限，因为这可能会影响到设置管理，并且如果副本或 GSI 的吞吐量不匹配，则可能会损害复制。如果您拒绝所需的 SLR 权限，则与受影响副本之间的复制将停止，副本表的状态将更改为 `REPLICATION_NOT_AUTHORIZED`。对于多账户全局表，如果副本保持在 `REPLICATION_NOT_AUTHORIZED` 状态的时间超过 20 小时，则该副本将不可逆地转换为单区域 DynamoDB 表。SLR 具有以下权限：
+ `application-autoscaling:DeleteScalingPolicy`
+ `application-autoscaling:DescribeScalableTargets`
+ `application-autoscaling:DescribeScalingPolicies`
+ `application-autoscaling:DeregisterScalableTarget`
+ `application-autoscaling:PutScalingPolicy`
+ `application-autoscaling:RegisterScalableTarget`

### 自动扩缩服务相关角色
<a name="globaltables_MA_autoscaling_slr"></a>

在将全局表配置为调配容量模式时，必须为全局表配置自动扩缩。DynamoDB 自动扩缩功能使用 AWS Application Auto Scaling 服务来动态调整全局表副本上的预调配吞吐能力。Application Auto Scaling 服务创建名为 [AWSServiceRoleForApplicationAutoScaling\$1DynamoDBTable](https://docs.aws.amazon.com/autoscaling/application/userguide/application-auto-scaling-service-linked-roles.html) 的服务相关角色（SLR）。当您首次为 DynamoDB 表配置自动扩缩时，将在您的 AWS 账户中自动创建此服务相关角色。此角色允许 Application Auto Scaling 管理预调配的表容量并创建 CloudWatch 警报。

在将基于资源的策略应用于副本时，请确保不要拒绝在 [AWSApplicationAutoscalingDynamoDBTablePolicy](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSApplicationAutoscalingDynamoDBTablePolicy.html) 中定义的向 Application Auto Scaling SLR 主体授予的任何权限，因为这将中断自动扩缩功能。

## 全局表如何使用 AWS IAM
<a name="globaltables_MA_iam"></a>

以下各节介绍不同全局表操作所需的权限，并提供了策略示例，以帮助您为用户和应用程序配置适当的访问权限。

**注意**  
描述的所有权限都必须应用于受影响区域中的特定表资源 ARN。表资源 ARN 采用格式 `arn:aws:dynamodb:region:account-id:table/table-name`，您需要指定实际区域、账户 ID 和表名值。

以下是我们在接下来的各节中分步骤介绍的主题：
+ 创建多账户全局表和添加副本
+ 更新多账户全局表
+ 删除全局表和移除副本

### 创建全局表和添加副本
<a name="globaltables_MA_creating"></a>

#### 创建全局表的权限
<a name="globaltables_MA_creating_permissions"></a>

将新副本添加到区域表来构成多账户全局表或者添加到现有的多账户全局表时，执行操作的 IAM 主体必须获得所有现有成员的授权。要想成功添加副本，所有现有成员都需要在其表策略中授予以下权限：
+ `dynamodb:AssociateTableReplica`：此权限允许将表加入到全局表设置中。这是基础权限，用于建立初始复制关系。

这种精确的控制只允许授权账户加入全局表设置。

#### 用于创建全局表的 IAM 策略示例
<a name="globaltables_MA_creating_examples"></a>

##### 适用于双副本设置的 IAM 策略示例
<a name="globaltables_MA_2replica_example"></a>

多账户全局表的设置遵循用于提供安全复制的特定授权流程。接下来我们将了解它在实践中是如何运作的，在这个场景中，客户想要建立一个包含两个副本的全局表。第一个副本（ReplicaA）位于 ap-east-1 区域的账户 A 中，而第二个副本（ReplicaB）位于 eu-south-1 区域的账户 B 中。
+ 在源账户（账户 A）中，流程首先创建主副本表。账户管理员必须将基于资源的策略附加到此表，向目标账户（账户 B）明确授予执行关联所需的权限。此策略还授权 DynamoDB 复制服务执行基本的复制操作。
+ 目标账户（账户 B）遵循类似的流程，即在创建副本时附加相应的基于资源的策略，并引用用于创建副本的源表 ARN。此策略体现了账户 A 授予的权限，用于创建可信的双向关系。在建立复制之前，DynamoDB 会验证这些跨账户权限，以确保正确进行了授权。

要建立此设置，请执行以下操作：
+ 账户 A 的管理员必须首先将基于资源的策略附加到 ReplicaA。此策略向账户 B 和 DynamoDB 复制服务明确授予必要的权限。
+ 同样，在用于创建副本 B（引用副本 A 作为源表）的创建表调用中，账户 B 的管理员必须将匹配的策略附加到 ReplicaB，进行反向的账户引用以向账户 A 授予相应的权限。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": [ "111122223333", "444455556666" ],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                        "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB"
                    ]
                }
            }
        },
        {
            "Sid": "AllowTrustedAccountsToJoinThisGlobalTable",
            "Effect": "Allow",
            "Action": [
                "dynamodb:AssociateTableReplica"
            ],
            "Resource": "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
            "Principal": {"AWS": ["444455556666"]}
        }
    ]
}
```

------

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": [ "111122223333", "444455556666" ],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                        "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB"
                    ]
                }
            }
        }
    ]
}
```

------

##### 适用于三副本设置的 IAM 策略示例
<a name="globaltables_MA_3replica_example"></a>

在此环境中，账户 A、账户 B 和账户 C 中分别有 3 个副本 ReplicaA、ReplicaB 和 ReplicaC。ReplicaA 是第一个副本，首先作为区域表创建，然后将 ReplicaB 和 ReplicaC 添加到其中。
+ 账户 A 的管理员必须先将基于资源的策略附加到 ReplicaA，允许所有成员进行复制，并允许账户 B 和账户 C 的 IAM 主体添加副本。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": [ "111122223333", "444455556666", "123456789012" ],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                        "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB",
                        "arn:aws:dynamodb:us-east-1:123456789012:table/ReplicaC"
                    ]
                }
            }
        },
        {
            "Sid": "AllowTrustedAccountsToJoinThisGlobalTable",
            "Effect": "Allow",
            "Action": [
                "dynamodb:AssociateTableReplica"
            ],
            "Resource": "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
            "Principal": { "AWS": [ "444455556666", "123456789012" ] }
        }
    ]
}
```

------
+ 账户 B 的管理员必须添加一个指向 ReplicaA（源）的副本，即 ReplicaB。ReplicaB 具有以下策略，允许在所有成员之间进行复制，并允许账户 C 添加副本：

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": [ "111122223333", "444455556666", "123456789012" ],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                        "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB",
                        "arn:aws:dynamodb:us-east-1:123456789012:table/ReplicaC"
                    ]
                }
            }
        },
        {
            "Sid": "AllowTrustedAccountsToJoinThisGlobalTable",
            "Effect": "Allow",
            "Action": [
                "dynamodb:AssociateTableReplica"
            ],
            "Resource": "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB",
            "Principal": { "AWS": [ "123456789012" ] }
        }
    ]
}
```

------
+ 最后，账户 C 的管理员创建具有以下策略的副本，允许所有成员之间的复制权限。该策略不允许再添加任何副本。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Sid": "DynamoDBActionsNeededForSteadyStateReplication",
            "Effect": "Allow",
            "Action": [
                "dynamodb:ReadDataForReplication",
                "dynamodb:WriteDataForReplication",
                "dynamodb:ReplicateSettings"
            ],
            "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/ReplicaC",
            "Principal": {"Service": ["replication.dynamodb.amazonaws.com"]},
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": [ "111122223333", "444455556666" ],
                    "aws:SourceArn": [
                        "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                        "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB"
                    ]
                }
            }
        }
    ]
}
```

------

### 更新多账户全局表
<a name="globaltables_MA_updating"></a>

要使用 UpdateTable API 修改现有全局表的副本设置，对于进行 API 调用的区域中的表资源，您需要具有以下权限：`dynamodb:UpdateTable`

此外，您还可以更新其他全局表配置，例如自动扩缩策略和生存时间设置。这些额外的更新操作需要以下权限：

要使用 `UpdateTimeToLive` API 更新生存时间设置，对于包含副本的所有区域中的表资源，您必须具有以下权限：`dynamodb:UpdateTimeToLive`

要通过 `UpdateTableReplicaAutoScaling` API 更新副本自动扩缩策略，对于包含副本的所有区域中的表资源，您必须具有以下权限：
+ `application-autoscaling:DeleteScalingPolicy`
+ `application-autoscaling:DeleteScheduledAction`
+ `application-autoscaling:DeregisterScalableTarget`
+ `application-autoscaling:DescribeScalableTargets`
+ `application-autoscaling:DescribeScalingActivities`
+ `application-autoscaling:DescribeScalingPolicies`
+ `application-autoscaling:DescribeScheduledActions`
+ `application-autoscaling:PutScalingPolicy`
+ `application-autoscaling:PutScheduledAction`
+ `application-autoscaling:RegisterScalableTarget`

**注意**  
要成功更新表，您必须向所有副本区域和账户提供 `dynamodb:ReplicateSettings` 权限。如果多账户全局表中的任意副本未提供权限，导致无法将设置复制到任意其他副本，则在权限得到修复之前，所有副本的所有更新操作都将失败并出现 `AccessDeniedException`。

### 删除全局表和移除副本
<a name="globaltables_MA_deleting"></a>

要删除全局表，您必须移除所有副本。与同账户全局表不同，您不能使用 `UpdateTable` 删除远程区域中的副本表，您必须从控制副本表的账户中，通过 `DeleteTable` API 删除各个副本。

#### 删除全局表和移除副本的权限
<a name="globaltables_MA_deleting_permissions"></a>

删除单个副本和完全删除全局表均需要以下权限。删除全局表配置只会移除不同区域中表之间的复制关系。它不会删除剩下的最后一个区域中的基础 DynamoDB 表。最后一个区域中的表会作为标准 DynamoDB 表继续存在，具有相同的数据和设置。

在要移除副本的每个区域，您需要具有对表资源的以下权限：
+ `dynamodb:DeleteTable`
+ `dynamodb:DeleteTableReplica`

## 全局表如何使用 AWS KMS
<a name="globaltables_MA_kms"></a>

与所有 DynamoDB 表一样，全局表副本始终使用存储在 AWS Key Management Service（AWS KMS）中的加密密钥对静态数据进行加密。

**注意**  
与同账户全局表不同，多账户全局表中的不同副本可以配置为使用不同类型的 AWS KMS 密钥（AWS 拥有的密钥，或客户自主管理型密钥）。多账户全局表不支持 AWS 托管式密钥。

使用 CMK 的多账户全局表要求每个副本的密钥策略向 DynamoDB 复制服务主体 (`replication.dynamodb.amazonaws.com`) 授予权限，以便访问密钥用于复制和设置管理。需要以下权限：
+ `kms:Decrypt`
+ `kms:ReEncrypt*`
+ `kms:GenerateDataKey*`
+ `kms:DescribeKey`

**重要提示**

DynamoDB 需要访问副本的加密密钥才能删除副本。如果您因删除副本而想要禁用或删除加密该副本所用的客户自主管理型密钥，则应先删除该副本，等待表从复制组中删除（通过在任一其他副本中调用 describe），然后再禁用或删除该密钥。

对于加密副本所用的客户自主管理型密钥，如果您禁用或撤销 DynamoDB 对密钥的访问权限，则复制到副本以及从副本进行复制的过程将停止，而副本状态将更改为 `INACCESSIBLE_ENCRYPTION_CREDENTIALS`。如果副本保持 `INACCESSIBLE_ENCRYPTION_CREDENTIALS` 状态的时间超过 20 小时，则该副本将不可逆地转换为单区域 DynamoDB 表。

### AWS KMS 策略示例
<a name="globaltables_MA_kms_example"></a>

AWS KMS 策略允许 DynamoDB 访问两个 AWS KMS 密钥，以便在副本 A 和 B 之间进行复制。每个账户中附加到 DynamoDB 副本的 AWS KMS 密钥需要使用以下策略进行更新：

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": { "Service": "replication.dynamodb.amazonaws.com" },
        "Action": [
            "kms:Decrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey*",
            "kms:DescribeKey"
        ],
        "Resource": "*",
        "Condition": {
            "StringEquals": {
                "aws:SourceAccount": [ "111122223333", "444455556666" ],
                "aws:SourceArn": [
                    "arn:aws:dynamodb:ap-east-1:111122223333:table/ReplicaA",
                    "arn:aws:dynamodb:eu-south-1:444455556666:table/ReplicaB"
                ]
            }
        }
      }
   ]
 }
```

------

# 了解 Amazon DynamoDB 全局表计费
<a name="global-tables-billing"></a>

本指南介绍了 DynamoDB 如何对全局表进行计费，并确定了导致全局表费用的各个组件，包括一个实际示例。

[Amazon DynamoDB 全局表](GlobalTables.md)是一个完全托管式、无服务器、多区域和多活数据库。全局表旨在实现 [99.999% 的可用性](https://aws.amazon.com/dynamodb/sla/)，并提供更高的应用程序弹性和改进的业务连续性。全局表可跨所选的 AWS 区域自动复制 DynamoDB 表，因此，您可以实现快速的本地读写性能。

## 工作原理
<a name="global-tables-billing-how-it-works"></a>

全局表的计费模式与单区域 DynamoDB 表的计费模式不同。单区域 DynamoDB 表的写入操作按以下单位计费：
+ 适用于按需容量模式的写入请求单位（WRU），其中对于每次写入（最大可达 1 KB）收取一个 WRU 的费用
+ 适用于预置容量模式的写入容量单位（WCU），其中一个 WCU 每秒可提供一次写入，最大可达 1 KB

当您通过向现有单区域表添加副本表来创建全局表时，该单区域表将变成副本表，这意味着用于对写入表的操作进行计费的单位也会发生变化。对副本表的写入操作按以下单位计费：
+ 适用于按需容量模式的复制的写入请求单位（rWRU），其中对于每次写入（最大可达 1 KB）按每个副本表收取一个 rWRU 的费用
+ 适用于预置容量模式的复制的写入容量单位（rWCU），其中每个副本表一个 WCU 每秒可提供一次写入，最大可达 1 KB

即使 GSI 的基表是副本表，对全局二级索引（GSI）的更新也使用与单区域 DynamoDB 表相同的单位进行计费。GSI 的更新操作按以下单位计费：
+ 适用于按需容量模式的写入请求单位（WRU），其中对于每次写入（最大可达 1 KB）收取一个 WRU 的费用
+ 适用于预置容量模式的写入容量单位（WCU），其中一个 WCU 每秒可提供一次写入，最大可达 1 KB

复制的写入单位（rWCU 和 rWRU）与单区域写入单位（WCU 和 WRU）的定价相同。由于是跨区域复制数据，因此对全局表收取跨区域数据传输费用。每个包含全局表的副本表的区域都会产生复制的写入（rWCU 或 rWRU）费用。

来自单区域表和副本表的读取操作使用以下单位：
+ 适用于按需容量模式的读取请求单位（RRU），其中对于每次强一致性读取（最大可达 4 KB）收取一个 RRU 的费用
+ 适用于预置表的读取容量单位（RCU），其中一个 RCU 每秒可提供一次强一致性读取，最大可达 4 KB

## 一致性模式和计费
<a name="global-tables-billing-consistency-modes"></a>

对于多区域强一致性（MRSC）和多区域最终一致性（MREC）模式，用于为写入操作计费的复制写入单元（rWCU 和 rWRU）是相同的。使用配置了见证者的多区域强一致性（MRSC）模式的全局表，在向见证者进行复制时，不会产生复制写入单元成本（rWCU 和 rWRU）、存储成本或数据传输成本。

## DynamoDB 全局表计费示例
<a name="global-tables-billing-example"></a>

让我们来看一个为期多天的示例场景，以了解全局表写入请求在实践中是如何计费的（请注意，此示例仅考虑写入请求，不包括示例中可能产生的表还原和跨区域数据传输费用）：

**第 1 天 - 单区域表：**您在 us-west-2 区域中有一个名为 Table\$1A 的单区域按需 DynamoDB 表。您向 Table\$1A 中写入 100 个 1 KB 的项目。对于这些单区域写入操作，每写入 1 KB，您需要支付 1 个写入请求单位（WRU）的费用。您第 1 天的费用为：
+ us-west-2 区域中有 100 个 WRU 用于单区域写入

第 1 天收费的请求单位总数：**100 个 WRU**。

**第 2 天 - 创建全局表：**您通过在 us-east-2 区域中向 Table\$1A 添加副本来创建全局表。Table\$1A 现在是一个全局表，具有两个副本表；一个位于 us-west-2 区域，另一个位于 us-east-2 区域。您向 us-west-2 区域的副本表中写入 150 个 1 KB 项目。您第 2 天的费用为：
+ us-west-2 区域中有 150 个 rWRU 用于复制的写入
+ us-east-2 区域中有 150 个 rWRU 用于复制的写入

第 2 天收费的请求单位总数：**300 个 rWRU**。

**第 3 天 - 添加全局二级索引：**您向 us-east-2 区域的副本表中添加全局二级索引（GSI），用于投影基表（副本）表中的所有属性。全局表会自动在 us-west-2 区域的副本表上为您创建 GSI。您向 us-west-2 区域的副本表中写入 200 条新的 1 KB 记录。您第 3 天的费用为：
+ • us-west-2 区域中有 200 个 rWRU 用于复制的写入
+ • us-west-2 区域中有 200 个 WRU 用于 GSI 更新
+ • us-east-2 区域中有 200 个 rWRU 用于复制的写入
+ • us-east-2 区域中有 200 个 WRU 用于 GSI 更新

第 3 天收费的写入请求单位总数：**400 个 WRU 和 400 个 rWRU**。

所有三天的总写入单位费用为 500 个 WRU（第 1 天 100 个 WRU \$1 第 3 天 400 个 WRU）和 700 个 rWRU（第 2 天 300 个 rWRU \$1 第 3 天 400 个 rWRU）。

总之，在包含副本表的所有区域中，副本表写入操作都以复制的写入单位计费。如果您有全局二级索引，则在包含 GSI 的所有区域（在全局表中是包含副本表的所有区域）中，您需要为 GSI 的更新支付写入单位的费用。

# DynamoDB 全局表版本
<a name="V2globaltables_versions"></a>

DynamoDB 全局表有两个版本可用：全局表版本 2019.11.21（当前版）和全局表版本 2017.11.29（旧版）。我们建议使用全局表版本 2019.11.21（当前版），因为相比 2017.11.29（旧版），它更易于使用、在更多区域中受支持，并且对于大部分工作负载而言更具有成本效益。

## 确定全局表的版本
<a name="globaltables.DetermineVersion"></a>

### 使用 AWS CLI 确定版本
<a name="globaltables.CLI"></a>

#### 确定版本 2019.11.21（当前版）全局表副本
<a name="globaltables.CLI.current"></a>

要确定表是否为全局表版本 2019.11.21（当前版）副本，请对该表调用 `describe-table` 命令。如果输出包含值为“2019.11.21”的 `GlobalTableVersion` 属性，则该表为版本 2019.11.21（当前版）全局表副本。

`describe-table` 的示例 CLI 命令：

```
aws dynamodb describe-table \
--table-name users \
--region us-east-2
```

（删节）输出包含值为“2019.11.21”的 `GlobalTableVersion` 属性，因此该表为版本 2019.11.21（当前版）全局表副本。

```
{
    "Table": {
        "AttributeDefinitions": [
            {
                "AttributeName": "id",
                "AttributeType": "S"
            },
            {
                "AttributeName": "name",
                "AttributeType": "S"
            }
        ],
        "TableName": "users",
        ...
        "GlobalTableVersion": "2019.11.21",
        "Replicas": [
            {
                "RegionName": "us-west-2",
                "ReplicaStatus": "ACTIVE",
            }
        ],
        ...
    }
}
```

#### 确定版本 2017.11.29（旧版）全局表副本
<a name="globaltables.CLI.legacy"></a>

全局表版本 2017.11.29（旧版）使用一组专用的命令来进行全局表管理。要确定表是否为全局表版本 2017.11.29（旧版）副本，请对该表调用 `describe-global-table` 命令。如果您收到成功响应，则该表为版本 2017.11.29（旧版）全局表副本。如果 `describe-global-table` 命令返回 `GlobalTableNotFoundException` 错误，则该表不是版本 2017.11.29（旧版）副本。

`describe-global-table` 的示例 CLI 命令：

```
aws dynamodb describe-global-table \
--table-name users \
--region us-east-2
```

该命令返回成功的响应，因此，此表是版本 2017.11.29（旧版）全局表副本。

```
{
    "GlobalTableDescription": {
        "ReplicationGroup": [
            {
                "RegionName": "us-west-2"
            },
            {
                "RegionName": "us-east-2"
            }
        ],
        "GlobalTableArn": "arn:aws:dynamodb::123456789012:global-table/users",
        "CreationDateTime": "2025-06-10T13:55:53.630000-04:00",
        "GlobalTableStatus": "ACTIVE",
        "GlobalTableName": "users"
    }
}
```

### 使用 DynamoDB 控制台确定版本
<a name="globaltables.console"></a>

要确定全局表副本的版本，请执行以下操作：

1. 打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/home](https://console.aws.amazon.com/dynamodb/home)。

1. 在控制台左侧的导航窗格中，选择**表**。

1. 选择要确定其全局表版本的表。

1. 选择**全局表**选项卡。

   *摘要*部分显示正在使用的全局表的版本。

## 旧版与当前版之间的行为差异
<a name="DiffLegacyVsCurrent"></a>

以下列表介绍了全局表的旧版和当前版之间的行为差异。
+ 与版本 2017.11.29（旧版）相比，版本 2019.11.21（当前版）在执行某些 DynamoDB 操作时使用的写入容量更少，因此，对于大部分客户而言，更具有成本效益。这些 DynamoDB 操作的差异如下：
  + 在 2017.11.29（旧版）中，针对一个区域的 1 KB 项目调用 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html)，然后复制到其它区域时，每个区域需要 2 个 rWRU；但在 2019.11.21（当前版）中，仅需要 1 个 rWRU。
  + 在 2017.11.29（旧版）中，针对 1 KB 项目调用 [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 时，源区域需要 2 个 rWRU，每个目标区域需要 1 个 rWRU，但在 2019.11.21（当前版）中，源区域和目标区域都只需要 1 个 rWRU。
  + 在 2017.11.29（旧版）中，针对 1 KB 项目调用 [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) 时，源区域需要 1 个 rWRU，每个目标区域需要 2 个 rWRU，但在 2019.11.21（当前版）中，源区域或目标区域都只需要 1 个 rWRU。

  下表显示两个区域中 1 KB 项目 2017.11.29（旧版）和 2019.11.21（最新版）表的 rWRU 消耗量    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/V2globaltables_versions.html)
+ 版本 2017.11.29（旧版）仅在 11 个 AWS 区域中可用。但是，版本 2019.11.21（当前版）在所有 AWS 区域中都可用。
+ 要创建版本 2017.11.29（旧版）全局表，请首先创建一组空的区域表，然后调用 [CreateGlobalTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateGlobalTable.html) API 来创建全局表。您可以通过调用 [UpdateTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) API 来将副本添加到现有区域表，以此创建版本 2019.11.21（当前版）全局表。
+ 版本 2017.11.29（旧版）要求您在新区域中添加副本之前（包括创建期间）清空表中的所有副本。版本 2019.11.21（当前版）支持您在区域内的已包含数据的表中添加和删除副本。
+ 版本 2017.11.29（旧版）通过以下一组专用的控制面板 API 来管理副本：
  + [CreateGlobalTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateGlobalTable.html)
  + [DescribeGlobalTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeGlobalTable.html)
  + [DescribeGlobalTableSettings](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeGlobalTableSettings.html)
  + [ListGlobalTables](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListGlobalTables.html)
  + [UpdateGlobalTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateGlobalTable.html)
  + [UpdateGlobalTableSettings](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateGlobalTableSettings.html)

  版本 2019.11.21（当前版）使用 [DescribeTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) 和 [UpdateTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateTable.html) API 来管理副本。
+ 版本 2017.11.29（旧版）针对每次写入操作发布两条 DynamoDB Streams 记录。版本 2019.11.21（当前版）针对每次写入操作仅发布一条 DynamoDB Streams 记录。
+ 版本 2017.11.29（旧版）填充和更新 `aws:rep:deleting`、`aws:rep:updateregion` 和 `aws:rep:updatetime` 属性。版本 2019.11.21（当前版）不填充或更新这些属性。
+ 版本 2017.11.29（旧版）不在副本间同步[在 DynamoDB 中使用生存时间（TTL）](TTL.md)设置。版本 2019.11.21（当前版）在副本间同步 TTL 设置。
+ 版本 2017.11.29（旧版）不将 TTL 删除操作复制到其它副本。版本 2019.11.21（当前版）会将 TTL 删除操作复制到所有副本。
+ 版本 2017.11.29（旧版）不在副本间同步[自动扩缩](AutoScaling.md)设置。版本 2019.11.21（当前版）会在副本间同步自动扩缩设置。
+ 版本 2017.11.29（旧版）不在副本间同步[全局二级索引（GSI）](GSI.md)设置。版本 2019.11.21（当前版）在副本间同步 GSI 设置。
+ 版本 2017.11.29（旧版）不在副本间同步[静态加密](encryption.usagenotes.md)设置。版本 2019.11.21（当前版）在副本间同步静态加密设置。
+ 版本 2017.11.29（旧版）发布 `PendingReplicationCount` 指标。版本 2019.11.21（当前版）不发布此指标。

## 升级到当前版本
<a name="upgrading-to-current-version"></a>

### 全局表升级所需的权限
<a name="V2globaltables_versions.Notes-permissions"></a>

要升级到版本 2019.11.21（当前版），您必须在包含副本的所有区域中具有 `dynamodb:UpdateGlobalTableversion` 权限。除了访问 DynamoDB 控制台和查看表所必需的权限之外，还需要这些权限。

下面的 IAM 策略授予将任何全局表升级到版本 2019.11.21（当前版）的权限。

```
{
    "version": "2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "dynamodb:UpdateGlobalTableversion",
            "Resource": "*"
        }
    ]
}
```

下面的 IAM 策略授予仅将在两个区域中具有副本的 `Music` 全局表升级为版本 2019.11.21（当前版）的权限。

```
{
    "version": "2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "dynamodb:UpdateGlobalTableversion",
            "Resource": [
                "arn:aws:dynamodb::123456789012:global-table/Music",
                "arn:aws:dynamodb:ap-southeast-1:123456789012:table/Music",
                "arn:aws:dynamodb:us-east-2:123456789012:table/Music"
            ]
        }
    ]
}
```

### 升级期间的情况
<a name="V2GlobalTablesUpgradeExpectations"></a>
+ 升级时，所有全局表副本将继续处理读取和写入流量。
+ 升级过程需要几分钟到几小时不等，具体取决于表大小和副本数量。
+ 在升级过程中，[TableStatus](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TableDescription.html#DDB-Type-TableDescription-TableStatus) 的值将从 `ACTIVE` 变为 `UPDATING`。您可以通过调用 [DescribeTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) API 或使用 DynamoDB 控制台中的**表**视图来查看表的状态。
+ 在升级全局表期间，自动扩缩不会调整全局表的预置容量设置。强烈建议您在升级期间将表设置为[按需](capacity-mode.md#capacity-mode-on-demand)容量模式。
+ 如果您选择在升级时使用[预置](provisioned-capacity-mode.md)容量模式和自动扩缩，则必须增加策略的最小读写吞吐量，以适应升级期间流量的预期增加，避免节流。
+ `ReplicationLatency` 指标可以在升级过程中临时报告延迟峰值或停止报告指标数据。有关更多信息，请参阅[ReplicationLatency](metrics-dimensions.md#ReplicationLatency)。
+ 升级过程完成后，表状态将变为 `ACTIVE`。

### DynamoDB Streams 在升级之前、期间和之后的行为
<a name="V2GlobalTablesUpgradeDDBStreamsBehavior"></a>

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/V2globaltables_versions.html)

### 升级到版本 2019.11.21（当前版）
<a name="V2globaltables_versions.upgrade"></a>

请按照以下步骤，使用 AWS 管理控制台升级您的 DynamoDB 全局表版本。

**将全局表升级到版本 2019.11.21（当前版）**

1. 打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/home](https://console.aws.amazon.com/dynamodb/home)。

1. 在控制台左侧的导航窗格中，选择**表**，然后选择要升级到版本 2019.11.21（当前版）的全局表。

1. 选择**全局表**选项卡。

1. 选择**更新版本**。  
![\[显示“更新版本”按钮的控制台屏幕截图。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GlobalTables-upgrade.png)

1. 阅读并同意新要求，然后选择**更新版本**。

1. 升级过程完成后，控制台上显示的全局表版本将更改为 **2019.11.21**。

# 全局表的最佳实践
<a name="globaltables-bestpractices"></a>

以下各节介绍用于部署和使用全局表的最佳实践。

## 版本
<a name="globaltables-bestpractices-version"></a>

DynamoDB 全局表有两个版本可用：版本 2019.11.21（当前版）和[版本 2017.11.29（旧版）](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/globaltables.V1.html)。您应尽可能使用版本 2019.11.21（当前版）。

## 删除保护
<a name="globaltables-bestpractices-deletionprotection"></a>

您应该对要防止意外删除的全局表副本启用删除保护。必须对每个副本启用删除保护。

## 使用 AWS CloudFormation
<a name="globaltables-bestpractices-cloudformation"></a>

CloudFormation 目前不支持跨堆栈协调多区域资源，例如全局表。如果您在单独的区域堆栈中定义全局表的每个副本，则由于在执行副本更新时检测到堆栈间存在偏差，您将遇到错误。为避免出现此问题，您应选择一个区域作为部署全局表的参考区域，并在该区域的堆栈中定义全局表的所有副本。

**重要**  
无法通过更改模板中的类型将 `AWS::DynamoDB::Table` 类型的资源转换为 `AWS::DynamoDB::GlobalTable` 类型的资源。尝试通过更改单区域表的 CloudFormation 资源类型来将该表转换为全局表，可能会导致 DynamoDB 表被删除。

可以使用 `AWS::DynamoDB::GlobalTable` 资源在单个区域中创建表。此表将像任何其它单区域表一样进行部署。如果您稍后更新堆栈以将其它区域添加到资源中，则副本将添加到表中，而该表将安全地转换为全局表。

如果您希望将现有的 `AWS::DynamoDB::Table` 资源转换为 `AWS::DynamoDB::GlobalTable` 资源，建议按照以下步骤转换资源类型：

1. 将 `AWS::DynamoDB::Table` 删除策略设置为保留。

1. 从堆栈定义中移除该表。

1. 在 AWS 管理控制台中将副本添加到单区域表，同时将单区域表转换为全局表。

1. 将新的全局表作为新的 `AWS::DynamoDB::GlobalTable` 资源导入到堆栈中。

## 备份和时间点故障恢复
<a name="globaltables-bestpractices-backups"></a>

为全局表中的一个副本启用自动备份和时间点故障恢复（PITR），可能足以实现灾难恢复目标。使用 AWS-Backup 创建的副本备份可以自动跨区域复制，以提高韧性。在选择备份和 PITR 支持策略时，请考虑多区域高可用性背景下的灾难恢复计划目标。

## 专为实现多区域高可用性而设计
<a name="globaltables-bestpractices-multiregion"></a>

有关部署全局表的规范性指导，请参阅 [DynamoDB 全局表设计的最佳实践](bp-global-table-design.md)。

# 使用 DynamoDB 中的项目和属性
<a name="WorkingWithItems"></a>

在 Amazon DynamoDB 中，*项目*是属性的集合。每个属性都有各自的名称和值。属性值可以为标量、集或文档类型。有关更多信息，请参阅 [Amazon DynamoDB：工作原理](HowItWorks.md)。

DynamoDB 提供了用于基本的创建、读取、更新和删除 (CRUD) 功能的四项操作。所有这些操作都是原子操作。
+ `PutItem` — 创建项目。
+ `GetItem` — 读取项目。
+ `UpdateItem` — 更新项目。
+ `DeleteItem` — 删除项目。

其中每项操作均需要您指定要处理的项目的主键。例如，要使用 `GetItem` 读取项目，您必须指定该项目的分区键和排序键（如果适用）。

除了四项基本 CRUD 操作之外，DynamoDB 还提供了以下操作：
+ `BatchGetItem` — 从一个或多个表中读取多达 100 个项目。
+ `BatchWriteItem` — 在一个或多个表中创建或删除多达 25 个项目。

这些批处理操作可将多项 CRUD 操作组合成一个请求。此外，批处理操作还可并行读取和写入项目以最大程度地减少响应延迟。

本节将介绍如何使用这些操作并包含了相关主题，例如有条件更新和原子计数器。本节还包括使用 AWS SDK 的示例代码。

**Topics**
+ [DynamoDB 项目大小和格式](CapacityUnitCalculations.md)
+ [读取项目](#WorkingWithItems.ReadingData)
+ [写入项目](#WorkingWithItems.WritingData)
+ [返回值](#WorkingWithItems.ReturnValues)
+ [分批操作](#WorkingWithItems.BatchOperations)
+ [原子计数器](#WorkingWithItems.AtomicCounters)
+ [有条件写入](#WorkingWithItems.ConditionalUpdate)
+ [在 DynamoDB 中使用表达式](Expressions.md)
+ [在 DynamoDB 中使用生存时间（TTL）](TTL.md)
+ [在 DynamoDB 中查询表](Query.md)
+ [在 DynamoDB 中扫描表](Scan.md)
+ [PartiQL – 用于 Amazon DynamoDB 的 SQL 兼容语言](ql-reference.md)
+ [处理项目：Java](JavaDocumentAPIItemCRUD.md)
+ [使用项目：.NET](LowLevelDotNetItemCRUD.md)

# DynamoDB 项目大小和格式
<a name="CapacityUnitCalculations"></a>

DynamoDB 表是无架构的（主键除外），因此，表中的项目可具有不同的属性、大小和数据类型。

项目的总大小是其属性名称和值的长度总和，加上任何适用的开销，如下所述。您可以使用以下准则来估算属性大小：
+ 字符串是使用 UTF-8 二进制编码的 Unicode。字符串大小为 *（属性名 UTF-8 编码的字节数）\$1（UTF-8 编码的字节数）*。
+ 数字的长度是可变的，最多 38 个有效位。系统会删减开头和结尾的 0。数字大小约为 *（属性名 UTF-8 编码的字节数）\$1（每 2 个有效位对应 1 个字节）\$1（1 个字节）*。
+ 必须先采用 base64 格式对二进制值进行编码，然后才能将其发送到 DynamoDB，不过使用值的原始字节长度来计算大小。二进制属性的大小为 *（属性名 UTF-8 编码的字节数）\$1（原始字节数）*。
+ 空属性或布尔属性的大小为 *（属性名 UTF-8 编码的字节数）\$1（1 字节）*。
+ 对于类型为 `List` 或 `Map` 的属性，不论其内容如何，都需要 3 个字节的开销。`List` 或 `Map` 的大小为*（属性名 UTF-8 编码的字节数）\$1 总和（嵌套元素大小）\$1（3 字节）*。空 `List` 或 `Map` 的大小为 *（属性名 UTF-8 编码的字节数）\$1（3 字节）*。
+ 每个 `List` 或 `Map` 元素还需要 1 字节的开销。

**注意**  
建议您选择较短的属性名，而不要选择较长的属性名。这可以帮助您减少所需的存储量，但也可能会降低您使用的 RCU/WCU 量。

出于存储计费目的，每个项目都包括按项目的存储开销，这取决于您启用的功能。
+ DynamoDB 中的所有项目都需要 100 字节的存储开销才能进行索引。
+ 某些 DynamoDB 功能（全局表、事务、使用 DynamoDB 的 Kinesis Data Streams 更改数据捕获）需要额外的存储开销，才能考虑因启用这些功能而产生的系统创建属性。例如，全局表需要额外 48 字节的存储开销。

## 读取项目
<a name="WorkingWithItems.ReadingData"></a>

要从 DynamoDB 表中读取项目，请使用 `GetItem` 操作。必需提供表的名称和所需项目的主键。

**Example**  
以下 AWS CLI 示例将演示如何从 `ProductCatalog` 表读取项目。  

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"1"}}'
```

**注意**  
使用 `GetItem` 时，您必须指定*整个* 主键，而不仅仅是部分主键。例如，如果某个表具有复合主键（分区键和排序键），您必须为分区键和排序键分别提供一个值。

默认情况下，`GetItem` 请求将执行最终一致性读取。您可以改用 `ConsistentRead` 参数来请求强一致性读取。（这会占用额外的读取容量单位，但会返回该项目的最新版本。）

`GetItem` 返回项目的所有属性。您可以使用*投影表达式* 来仅返回一部分属性。有关更多信息，请参阅 [在 DynamoDB 中使用投影表达式](Expressions.ProjectionExpressions.md)。

要返回由 `GetItem` 占用的读取容量单位数，请将 `ReturnConsumedCapacity` 参数设置为 `TOTAL`。

**Example**  
以下 AWS Command Line Interface (AWS CLI) 示例将演示一些可选的 `GetItem` 参数。  

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"1"}}' \
    --consistent-read \
    --projection-expression "Description, Price, RelatedItems" \
    --return-consumed-capacity TOTAL
```

## 写入项目
<a name="WorkingWithItems.WritingData"></a>

要创建、更新或删除 DynamoDB 表中的项目，请使用以下操作之一：
+ `PutItem`
+ `UpdateItem`
+ `DeleteItem`

对于这些操作中的每一项，您必须指定完整的主键，而不仅仅是部分主键。例如，如果某个表具有复合主键（分区键和排序键），您必须为分区键和排序键分别提供一个值。

要返回其中任何操作占用的写入容量单位数，请将 `ReturnConsumedCapacity` 参数设置为以下项之一：
+ `TOTAL` — 返回占用的写入容量单位总数。
+ `INDEXES` — 返回占用的写入容量单位总数，其中包含表的小计和受该操作影响的任何二级索引。
+ `NONE` — 不返回任何写入容量详细信息。（这是默认值。）

### PutItem
<a name="WorkingWithItems.WritingData.PutItem"></a>

`PutItem` 创建新项目。如果表中已存在具有相同键的项目，它将被替换为新项目。

**Example**  
将新项目写入 `Thread` 表。`Thread` 的主键包含 `ForumName`（分区键）和 `Subject`（排序键）。  

```
aws dynamodb put-item \
    --table-name Thread \
    --item file://item.json
```
`--item` 的参数存储在 `item.json` 文件中。  

```
{
    "ForumName": {"S": "Amazon DynamoDB"},
    "Subject": {"S": "New discussion thread"},
    "Message": {"S": "First post in this thread"},
    "LastPostedBy": {"S": "fred@example.com"},
    "LastPostDateTime": {"S": "201603190422"}
}
```

### UpdateItem
<a name="WorkingWithItems.WritingData.UpdateItem"></a>

如果带指定键的项目不存在，则 `UpdateItem` 会创建一个新项目。否则，它会修改现有项目的属性。

您可使用*更新表达式* 指定要修改的属性及其新值。有关更多信息，请参阅 [在 DynamoDB 中使用更新表达式](Expressions.UpdateExpressions.md)。

在更新表达式内，您可使用表达式属性值作为实际值的占位符。有关更多信息，请参阅 [在 DynamoDB 中使用表达式属性值](Expressions.ExpressionAttributeValues.md)。

**Example**  
修改 `Thread` 项目中的各种属性。可选 `ReturnValues` 参数按更新后的情况显示项目。有关更多信息，请参阅 [返回值](#WorkingWithItems.ReturnValues)。  

```
aws dynamodb update-item \
    --table-name Thread \
    --key file://key.json \
    --update-expression "SET Answered = :zero, Replies = :zero, LastPostedBy = :lastpostedby" \
    --expression-attribute-values file://expression-attribute-values.json \
    --return-values ALL_NEW
```

`--key` 的参数存储在 `key.json` 文件中。

```
{
    "ForumName": {"S": "Amazon DynamoDB"},
    "Subject": {"S": "New discussion thread"}
}
```

`--expression-attribute-values` 的参数存储在 `expression-attribute-values.json` 文件中。

```
{
    ":zero": {"N":"0"},
    ":lastpostedby": {"S":"barney@example.com"}
}
```

### DeleteItem
<a name="WorkingWithItems.WritingData.DeleteItem"></a>

`DeleteItem` 删除带指定键的项目。

**Example**  
下面的 AWS CLI 示例说明如何删除 `Thread` 表。  

```
aws dynamodb delete-item \
    --table-name Thread \
    --key file://key.json
```

## 返回值
<a name="WorkingWithItems.ReturnValues"></a>

在某些情况下，您可能希望 DynamoDB 按您修改特定属性值之前或之后的情况返回这些值。`PutItem`、`UpdateItem` 和 `DeleteItem` 操作均具有一个 `ReturnValues` 参数，您可使用该参数返回属性在修改前或修改后的值。

`ReturnValues` 的默认值为 `NONE`，这表示 DynamoDB 不会返回有关已修改属性的任何信息。

下面是 `ReturnValues` 的其他有效设置，这些设置按照 DynamoDB API 操作排列。

### PutItem
<a name="WorkingWithItems.ReturnValues.PutItem"></a>
+ `ReturnValues`: `ALL_OLD`
  + 如果您覆盖了现有项目，`ALL_OLD` 将按覆盖前的情况返回整个项目。
  + 如果您写入了不存在的项目，则 `ALL_OLD` 无效。

### UpdateItem
<a name="WorkingWithItems.ReturnValues.UpdateItem"></a>

`UpdateItem` 的最常见用途是更新现有项目。但是，`UpdateItem` 实际上会执行 *upsert* 操作，这意味着，如果项目尚不存在，upsert 将自动创建项目。
+ `ReturnValues`: `ALL_OLD`
  + 如果您更新了现有项目，`ALL_OLD` 将按更新前的情况返回整个项目。
  + 如果您更新了不存在的项目 (upsert)，则 `ALL_OLD` 无效。
+ `ReturnValues`: `ALL_NEW`
  + 如果您更新了现有项目，`ALL_NEW` 将按更新后的情况返回整个项目。
  + 如果您更新了不存在的项目 (upsert)，`ALL_NEW` 将返回整个项目。
+ `ReturnValues`: `UPDATED_OLD`
  + 如果您更新了现有项目，`UPDATED_OLD` 将仅返回已更新的属性（按更新前的情况）。
  + 如果您更新了不存在的项目 (upsert)，则 `UPDATED_OLD` 无效。
+ `ReturnValues`: `UPDATED_NEW`
  + 如果您更新了现有项目，`UPDATED_NEW` 将仅返回受影响的属性（按更新后的情况）。
  + 如果您更新了不存在的项目（upsert），`UPDATED_NEW` 将仅返回已更新的属性（按更新后的情况）。

### DeleteItem
<a name="WorkingWithItems.ReturnValues.DeleteItem"></a>
+ `ReturnValues`: `ALL_OLD`
  + 如果您删除了现有项目，`ALL_OLD` 将按删除前情况返回整个项目。
  + 如果您删除了不存在的项目，`ALL_OLD` 不会返回任何数据。

## 分批操作
<a name="WorkingWithItems.BatchOperations"></a>

对于需要读取和写入多个项目的应用程序，DynamoDB 提供了 `BatchGetItem` 和 `BatchWriteItem` 操作。使用这些操作可减少从您的应用程序到 DynamoDB 的网络往返行程数。此外，DynamoDB 还可并行执行各个读取或写入操作。您的应用程序将受益于这种并行机制，并且无需管理并发度或线程。

批处理操作本质上是围绕多个读取或写入请求的包装程序。例如，如果一个 `BatchGetItem` 请求包含五个项目，则 DynamoDB 会代表您执行五次 `GetItem` 操作。同样，如果一个 `BatchWriteItem` 请求包含两个放置请求和四个删除请求，则 DynamoDB 会执行两次 `PutItem` 和四次 `DeleteItem` 请求。

通常，除非一个批处理操作中的*所有* 请求都失败，否则批处理操作不会失败。例如，假设您执行了一个 `BatchGetItem` 操作，但该批处理中的单独的 `GetItem` 请求之一失败。在这种情况下，`BatchGetItem` 会返回来自失败的 `GetItem` 请求的键和数据。该批处理中的其他 `GetItem` 请求不会受影响。

### BatchGetItem
<a name="WorkingWithItems.BatchOperations.BatchGetItem"></a>

一个 `BatchGetItem` 操作可包含多达 100 个单独的 `GetItem` 请求且可检索多达 16 MB 的数据。此外，一个 `BatchGetItem` 操作可从多个表中检索项目。

**Example**  
从 `Thread` 表中检索两个项目，并使用投影表达式仅返回一部分属性。  

```
aws dynamodb batch-get-item \
    --request-items file://request-items.json
```
`--request-items` 的参数存储在 `request-items.json` 文件中。  

```
{
    "Thread": {
        "Keys": [
            {
                "ForumName":{"S": "Amazon DynamoDB"},
                "Subject":{"S": "DynamoDB Thread 1"}
            },
            {
                "ForumName":{"S": "Amazon S3"},
                "Subject":{"S": "S3 Thread 1"}
            }
        ],
        "ProjectionExpression":"ForumName, Subject, LastPostedDateTime, Replies"
    }
}
```

### BatchWriteItem
<a name="WorkingWithItems.BatchOperations.BatchWriteItem"></a>

`BatchWriteItem` 操作可包含多达 25 个单独的 `PutItem` 和 `DeleteItem` 请求且最多可写入 16 MB 的数据。（单个项目的最大大小为 400 KB。） 此外，一个 `BatchWriteItem` 操作可在多个表中放置或删除项目。

**注意**  
`BatchWriteItem` 不支持 `UpdateItem` 请求。

**Example**  
它向 `ProductCatalog` 表中写入两个项目。  

```
aws dynamodb batch-write-item \
    --request-items file://request-items.json
```
`--request-items` 的参数存储在 `request-items.json` 文件中。  

```
{
    "ProductCatalog": [
        {
            "PutRequest": {
                "Item": {
                    "Id": { "N": "601" },
                    "Description": { "S": "Snowboard" },
                    "QuantityOnHand": { "N": "5" },
                    "Price": { "N": "100" }
                }
            }
        },
        {
            "PutRequest": {
                "Item": {
                    "Id": { "N": "602" },
                    "Description": { "S": "Snow shovel" }
                }
            }
        }
    ]
}
```

## 原子计数器
<a name="WorkingWithItems.AtomicCounters"></a>

您可以使用 `UpdateItem` 操作来实施*原子计数器*，一种无条件递增的数字属性，不会干扰其他写入请求。（所有写入请求的应用顺序跟接收顺序相同。） 使用原子计数器时，更新不是幂等的。换言之，该数值在您每次调用 `UpdateItem` 时递增或者递减。如果用于更新原子计数器的增量值为正，则可能导致计数偏多。如果该增量值为负，则可能导致计数偏少。

您可使用原子计数器跟踪网站的访问者的数量。在这种情况下，您的应用程序将以某个数字值递增，无论其当前值如何。如果 `UpdateItem` 操作失败，该应用程序只需重试该操作即可。这会产生更新两次计数器的风险，但您可能能够容忍对网站访问者的计数稍微偏多或偏少。

在无法容忍计数偏多或偏少的情况下（例如，在银行应用程序中），原子计数器将不适用。在此情况下，使用有条件更新比使用原子计数器更安全。

有关更多信息，请参阅 [对数值属性进行加减](Expressions.UpdateExpressions.md#Expressions.UpdateExpressions.SET.IncrementAndDecrement)。

**Example**  
以下 AWS CLI 示例以 5 为递增量提高产品的 `Price`。在本示例中，更新计数器之前，已知该项目存在。（由于 `UpdateItem` 不是幂等的，`Price` 在您每次运行此代码时增加。）   

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id": { "N": "601" }}' \
    --update-expression "SET Price = Price + :incr" \
    --expression-attribute-values '{":incr":{"N":"5"}}' \
    --return-values UPDATED_NEW
```

## 有条件写入
<a name="WorkingWithItems.ConditionalUpdate"></a>

默认情况下，DynamoDB 写入操作（`PutItem`、`DeleteItem`）是*无条件的*：每项操作都会覆盖带指定主键的现有项目。

DynamoDB 可以选择性地对这些操作支持有条件写入。有条件写入仅在项目属性满足一个或多个预期条件时才会成功。否则，它会返回错误。

条件写入会根据项目的最新更新版本检查其条件。请注意，如果该项目以前不存在，或者最近对该项目成功执行的操作是删除，则条件写入将找不到以前的项目。

 有条件写入在很多情况下很有用。例如，您可能希望 `PutItem` 操作仅在尚不存在具有相同主键的项目时成功。或者，如果某个项目的其中一个属性具有一个特定值，您可以阻止 `UpdateItem` 操作修改该项目。

有条件写入在多个用户尝试修改同一项目的情况下很有用。请考虑下图，其中两位用户（Alice 和 Bob）正在处理 DynamoDB 表中的同一项目。

![\[用户 Alice 和 Bob 尝试修改 ID 为 1 的项目，这表明需要有条件写入。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/update-no-condition.png)


假设 Alice 使用 AWS CLI 将 `Price` 属性更新为 8。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"1"}}' \
    --update-expression "SET Price = :newval" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在文件 `expression-attribute-values.json` 中：

```
{
    ":newval":{"N":"8"}
}
```

现在假设 Bob 稍后发出一个相似的 `UpdateItem` 请求，但将 `Price` 更改为 12。对于 Bob，`--expression-attribute-values` 参数类似于以下形式。

```
{
    ":newval":{"N":"12"}
}
```

Bob 的请求成功，但 Alice 之前的更新丢失了。

要请求有条件 `PutItem`、`DeleteItem` 或 `UpdateItem`，请指定一个条件表达式。*条件表达式* 是一个包含属性名称、条件运算符和内置函数的字符串。整个表达式的求值结果必须为 true。否则，该操作将失败。

现在考虑下图，该图展示了有条件写入将如何阻止 Alice 的更新被覆盖。

![\[阻止用户 Bob 的更新覆盖用户 Alice 对同一项目的更改的有条件写入。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/update-yes-condition.png)


Alice 首次尝试将 `Price` 更新为 8，但仅在当前 `Price` 为 10 时才执行此操作。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"1"}}' \
    --update-expression "SET Price = :newval" \
    --condition-expression "Price = :currval" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在 `expression-attribute-values.json` 文件中。

```
{
    ":newval":{"N":"8"},
    ":currval":{"N":"10"}
}
```

由于条件的计算结果为 true，Alice 的更新成功了。

接下来，Bob 尝试将 `Price` 更新为 12，但仅在当前 `Price` 为 10 时才执行此操作。对于 Bob，`--expression-attribute-values` 参数类似于以下形式。

```
{
    ":newval":{"N":"12"},
    ":currval":{"N":"10"}
}
```

由于 Alice 之前已将 `Price` 更改为 8，因此条件表达式的计算结果为 false，Bob 的更新失败。

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

### 带条件写入幂等性
<a name="WorkingWithItems.ConditionalWrites.Idempotence"></a>

如果条件检查位于同一个要更新的属性上，则条件写入可以是*幂等* 的。这意味着，仅当项目中的某些属性值与您在请求时期望它们具有的值匹配时，DynamoDB 才执行给定的写入请求。

例如，假设您发出一个 `UpdateItem` 请求来以 3 为递增量提高某个项目的 `Price`，但仅在 `Price` 当前为 20 时才执行此操作。在已发送该请求但尚未获得返回的结果之间的时间内，网络出现了错误，您不知道该请求是否成功。由于此条件写入是幂等的，您可以重试同一 `UpdateItem` 请求，而 DynamoDB 将仅在 `Price` 当前为 20 时更新项目。

### 带条件写入占用的容量单位
<a name="WorkingWithItems.ConditionalWrites.ReturnConsumedCapacity"></a>

即使 `ConditionExpression` 在条件写入过程中计算结果为 false，DynamoDB 仍消耗表的写入容量。消耗量取决于现有项目的大小（或最少为 1 个）。例如，如果现有项目为 300kb，而您尝试创建或更新的新项目为 310kb，则当条件失败时，消耗的写入容量单位将为 300，当条件成功时，消耗的写入容量单位将为 310。如果这是新项目（没有现有项目），当条件失败时，消耗的写入容量单位将为 1；当条件成功时，则消耗的写入容量单位为 310。

**注意**  
写入操作仅占用*写入*容量单位。它们从不占用*读取*容量单位。

失败的条件写入将返回 `ConditionalCheckFailedException`。发生这种情况时，您不会在响应中收到有关所消耗写入容量的任何信息。

要返回有条件写入过程中占用的写入容量单位的数量，请使用 `ReturnConsumedCapacity` 参数：
+ `TOTAL` — 返回占用的写入容量单位总数。
+ `INDEXES` — 返回占用的写入容量单位总数，其中包含表的小计和受该操作影响的任何二级索引。
+ `NONE` — 不返回任何写入容量详细信息。（这是默认值。）

  

**注意**  
与全局二级索引不同的是，本地二级索引与其表共享其预调配的吞吐容量。对本地二级索引执行的读取和写入活动会占用表的预置的吞吐容量。

# 在 DynamoDB 中使用表达式
<a name="Expressions"></a>

在 Amazon DynamoDB 中，您可以使用*表达式*来指定要从项目中读取哪些属性，在满足条件时写入数据，指定如何更新项目、定义查询和筛选查询结果。

该表描述了基本表达式语法和可用的表达式种类。


| 表达式类型 | 说明 | 
| --- | --- | 
| 投影表达式 | 当您使用 GetItem、Query 或 Scan 等操作时，投影表达式可标识要从项目中检索的属性。 | 
| 条件表达式 | 条件表达式确定在您使用 PutItem、UpdateItem 和 DeleteItem 操作时应修改哪些项目。 | 
| 更新表达式 | 更新表达式指定 UpdateItem 将如何修改项目的属性，例如，设置标量值或者删除列表或映射中的元素。 | 
| 键条件表达式 | 键条件表达式确定查询将从表或索引中读取哪些项目。 | 
| 筛选表达式 | 筛选表达式可确定查询结果中应返回给您的项目。所有其他结果将会丢弃。 | 

请参阅下面几节，了解有关表达式语法的信息以及有关每种表达式类型的详细信息。

**Topics**
+ [在 DynamoDB 中使用表达式时引用项目属性](Expressions.Attributes.md)
+ [DynamoDB 中的表达式属性名称（别名）](Expressions.ExpressionAttributeNames.md)
+ [在 DynamoDB 中使用表达式属性值](Expressions.ExpressionAttributeValues.md)
+ [在 DynamoDB 中使用投影表达式](Expressions.ProjectionExpressions.md)
+ [在 DynamoDB 中使用更新表达式](Expressions.UpdateExpressions.md)
+ [DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)
+ [DynamoDB 条件表达式 CLI 示例](Expressions.ConditionExpressions.md)

**注意**  
为了向后兼容性，DynamoDB 还支持不使用表达式的条件参数。有关更多信息，请参阅 [遗留 DynamoDB 条件参数](LegacyConditionalParameters.md)。  
新应用程序应使用表达式而不是旧式参数。

# 在 DynamoDB 中使用表达式时引用项目属性
<a name="Expressions.Attributes"></a>

本节介绍如何在 Amazon DynamoDB 中的表达式中引用项目属性。您可以使用任何属性，即使它深层嵌套在多个列表和映射中。

**Topics**
+ [顶级属性](#Expressions.Attributes.TopLevelAttributes)
+ [嵌套属性](#Expressions.Attributes.NestedAttributes)
+ [文档路径](#Expressions.Attributes.NestedElements.DocumentPathExamples)

**项目示例：ProductCatalog**  
本页上的示例使用 `ProductCatalog` 表中的以下项目示例。（此表在 [在 DynamoDB 中使用的示例表和数据](AppendixSampleTables.md) 中说明。）

```
{
    "Id": 123,
    "Title": "Bicycle 123",
    "Description": "123 description",
    "BicycleType": "Hybrid",
    "Brand": "Brand-Company C",
    "Price": 500,
    "Color": ["Red", "Black"],
    "ProductCategory": "Bicycle",
    "InStock": true,
    "QuantityOnHand": null,
    "RelatedItems": [
        341,
        472,
        649
    ],
    "Pictures": {
        "FrontView": "http://example.com/products/123_front.jpg",
        "RearView": "http://example.com/products/123_rear.jpg",
        "SideView": "http://example.com/products/123_left_side.jpg"
    },
    "ProductReviews": {
	    "FiveStar": [
	    		"Excellent! Can't recommend it highly enough! Buy it!",
	    		"Do yourself a favor and buy this."
	    ],
	    "OneStar": [
	    		"Terrible product! Do not buy this."
	    ]
    },
    "Comment": "This product sells out quickly during the summer",
    "Safety.Warning": "Always wear a helmet"
 }
```

请注意以下几点：
+ 分区键值 (`Id`) 是 `123`。没有排序键。
+ 大多数属性都具有标量数据类型，例如 `String`、`Number`、`Boolean` 和 `Null`。
+ 一个属性 (`Color`）是一个 `String Set`。
+ 以下属性是文档数据类型：
  + `RelatedItems` 列表。每个元素都是相关产品的 `Id`。
  + `Pictures` 的映射。每个元素都是图片的简短描述，以及相应图片文件的 URL。
  + `ProductReviews` 的映射。每个元素代表一个评级和一个与该评级相对应的评论列表。最初，此映射填充五星级和一星级评论。

## 顶级属性
<a name="Expressions.Attributes.TopLevelAttributes"></a>

如果属性没有嵌入其他属性，则视为*顶级*。对于 `ProductCatalog` 项目，顶级属性如下所示：
+ `Id`
+ `Title`
+ `Description`
+ `BicycleType`
+ `Brand`
+ `Price`
+ `Color`
+ `ProductCategory`
+ `InStock`
+ `QuantityOnHand`
+ `RelatedItems`
+ `Pictures`
+ `ProductReviews`
+ `Comment`
+ `Safety.Warning`

所有这些顶级属性都是标量，除了 `Color`（列表）、`RelatedItems`（列表）、`Pictures`（映射）和 `ProductReviews`（映射）。

## 嵌套属性
<a name="Expressions.Attributes.NestedAttributes"></a>

如果属性嵌入其他属性，则视为*嵌套*。要访问嵌套属性，请使用*取消引用运算符*：
+ `[n]`— 用于列表元素
+ `.`（点）-用于映射元素

### 访问列表元素
<a name="Expressions.Attributes.NestedElements.AccessingListElements"></a>

列表元素的取消引用运算符是 **[*n*]**，其中，*n* 是元素编号。列表元素从 0 开始，因此 [0] 表示列表中的第一个元素，[1] 表示第二个元素，依此类推。下面是一些示例：
+ `MyList[0]`
+ `AnotherList[12]`
+ `ThisList[5][11]`

元素 `ThisList[5]` 本身就是一个嵌套列表。因此，`ThisList[5][11]` 指的是该列表中的第 12 个元素。

方括号内的数字必须为非负整数。因此，以下表达式是无效的：
+ `MyList[-1]`
+ `MyList[0.4]`

### 访问映射元素
<a name="Expressions.Attributes.NestedElements.AccessingMapElements"></a>

地图元素的取消引用运算符为 **.**（一个点）。使用点作为映射中元素之间的分隔符：
+ `MyMap.nestedField`
+ `MyMap.nestedField.deeplyNestedField`

## 文档路径
<a name="Expressions.Attributes.NestedElements.DocumentPathExamples"></a>

在表达式中，您可以使用*文档路径*来告诉 DynamoDB 在哪里可以找到属性。对于顶级属性，文档路径只是属性名称。对于嵌套属性，您可以使用取消引用运算符构建文档路径。

下面是文档路径的一些示例。（请参阅 [在 DynamoDB 中使用表达式时引用项目属性](#Expressions.Attributes)。）
+ 顶级标量属性。

   `Description`
+ 顶级列表属性。（这将返回整个列表，而不仅仅是一些元素。）

  `RelatedItems`
+ 第三个元素来自 `RelatedItems` 列表。（请记住，列表元素是从零开始的。）

  `RelatedItems[2]`
+ 产品的正视图。

  `Pictures.FrontView`
+ 所有五星评论。

  `ProductReviews.FiveStar`
+ 第一个五星级评论。

  `ProductReviews.FiveStar[0]`

**注意**  
文档路径的最大深度为 32。因此，路径中取消引用运算符的数量不能超过此限制。

您可以在文档路径中使用任何属性名称，只要它们符合以下要求：
+ 第一个字符是 `a-z`、`A-Z` 或 `0-9`
+ 第二个字符（如果存在）是 `a-z` 或 `A-Z`

**注意**  
如果属性名称不满足此要求，则您必须将表达式属性名称定义为占位符。

有关更多信息，请参阅 [DynamoDB 中的表达式属性名称（别名）](Expressions.ExpressionAttributeNames.md)。

# DynamoDB 中的表达式属性名称（别名）
<a name="Expressions.ExpressionAttributeNames"></a>

*表达式属性名称*是您在 Amazon DynamoDB 表达式中使用的别名（或占位符），用作实际属性名称的替换项。表达式属性名称必须以井号（`#`）开头，后跟一个或多个字母数字字符。还允许使用下划线（`_`）字符。

本节介绍您必须使用表达式属性名称的几种情况。

**注意**  
本节中的示例使用 AWS Command Line Interface (AWS CLI)。

**Topics**
+ [保留字](#Expressions.ExpressionAttributeNames.ReservedWords)
+ [包含特殊字符的属性名称](#Expressions.ExpressionAttributeNames.AttributeNamesContainingSpecialCharacters)
+ [嵌套属性](#Expressions.ExpressionAttributeNames.NestedAttributes)
+ [重复引用属性名称](#Expressions.ExpressionAttributeNames.RepeatingAttributeNames)

## 保留字
<a name="Expressions.ExpressionAttributeNames.ReservedWords"></a>

有时，您可能需要写入的表达式包含与 DynamoDB 保留字冲突的属性名。（有关保留关键字的完整列表，请参阅 [DynamoDB 中的保留字](ReservedWords.md)。）

例如，以下 AWS CLI 示例将由于 `COMMENT` 是保留字而失败。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "Comment"
```

要解决此问题，您可使用诸如 `Comment` 的表达式属性名称来替换 `#c`。`#`（井号）是必需的，指示这是属性名称的占位符。AWS CLI 示例现在如下所示。

```
aws dynamodb get-item \
     --table-name ProductCatalog \
     --key '{"Id":{"N":"123"}}' \
     --projection-expression "#c" \
     --expression-attribute-names '{"#c":"Comment"}'
```

**注意**  
如果属性名称以数字开头、包含空格或包含保留字，则您*必须* 在表达式中使用表达式属性名称替换该属性的名称。

## 包含特殊字符的属性名称
<a name="Expressions.ExpressionAttributeNames.AttributeNamesContainingSpecialCharacters"></a>

在表达式中，点（“.”）将解释为文档路径中的分隔符字符。然而，DynamoDB 还允许您在属性名称中使用点字符和其他特殊字符，例如连字符（“-”）。在一些情况下这会造成混淆。为了说明这种情况，假设您要从 `Safety.Warning` 项目中检索 `ProductCatalog` 属性（请参阅 [在 DynamoDB 中使用表达式时引用项目属性](Expressions.Attributes.md)）。

假设您希望使用投影表达式访问 `Safety.Warning`。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "Safety.Warning"
```

DynamoDB 将返回空结果，而不是预期字符串 ("`Always wear a helmet`")。这是因为，DynamoDB 将表达式中的一个点解释为文档路径分隔符。在这种情况下，您必须定义表达式属性名称（例如 `#sw`）来替换 `Safety.Warning`。然后，您可以使用以下投影表达式。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "#sw" \
    --expression-attribute-names '{"#sw":"Safety.Warning"}'
```

接下来 DynamoDB 将返回正确结果。

**注意**  
如果属性名称包含圆点（“.”）或连字符（“-”），则*必须* 使用表达式属性名称替换表达式中该属性的名称。

## 嵌套属性
<a name="Expressions.ExpressionAttributeNames.NestedAttributes"></a>

假设您想访问嵌套属性 `ProductReviews.OneStar`。在表达式属性名称中，DynamoDB 将点（“.”）视为属性名称中的字符。要引用嵌套属性，请为文档路径中的每个元素定义一个表达式属性名称：
+ `#pr — ProductReviews`
+ `#1star — OneStar`

然后，您可以对投影表达式使用 `#pr.#1star`。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "#pr.#1star"  \
    --expression-attribute-names '{"#pr":"ProductReviews", "#1star":"OneStar"}'
```

接下来 DynamoDB 将返回正确结果。

## 重复引用属性名称
<a name="Expressions.ExpressionAttributeNames.RepeatingAttributeNames"></a>

表达式属性名称在需要重复引用相同属性名称时很有帮助。例如，请考虑以下用于从 `ProductCatalog` 项目中检索一些评论的表达式。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "ProductReviews.FiveStar, ProductReviews.ThreeStar, ProductReviews.OneStar"
```

要使表达式更加简洁，您可以使用诸如 `ProductReviews` 的表达式属性名称来替换 `#pr`。现在，修订的表达式如下所示。
+  `#pr.FiveStar, #pr.ThreeStar, #pr.OneStar` 

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"123"}}' \
    --projection-expression "#pr.FiveStar, #pr.ThreeStar, #pr.OneStar" \
    --expression-attribute-names '{"#pr":"ProductReviews"}'
```

如果您定义表达式属性名称，则该名称在整个表达式中的使用方式必须一致。另外，您不能忽略 `#` 符号。

# 在 DynamoDB 中使用表达式属性值
<a name="Expressions.ExpressionAttributeValues"></a>

Amazon DynamoDB 中的*表达式属性值*可充当变量。它们是您想要比较的实际值的替代项，您可能直到运行时才知道这些值。表达式属性值必须以冒号 (`:`) 开头，后跟一个或多个字母数字字符。

例如，假设您希望返回提供 `Black` 且成本`500` 或更少的所有 `ProductCatalog` 项目。您可以使用 `Scan` 操作与过滤器表达式相同，如此 AWS Command Line Interface(AWS CLI） 示例。

```
aws dynamodb scan \
    --table-name ProductCatalog \
    --filter-expression "contains(Color, :c) and Price <= :p" \
    --expression-attribute-values file://values.json
```

`--expression-attribute-values` 的参数存储在 `values.json` 文件中。

```
{
    ":c": { "S": "Black" },
    ":p": { "N": "500" }
}
```

如果您定义表达式属性值，则该值在整个表达式中的使用方式必须一致。另外，您不能忽略 `:` 符号。

表达式属性值与关键条件表达式、条件表达式、更新表达式和筛选表达式一起使用。

# 在 DynamoDB 中使用投影表达式
<a name="Expressions.ProjectionExpressions"></a>

要从表中读取数据，您可以使用 `GetItem`、`Query`，或者 `Scan`。默认情况下，Amazon DynamoDB 会返回所有项目属性。要仅获取部分属性而不是全部属性，请使用投影表达式。

*投影表达式*是用于标识所需属性的字符串。要检索单个属性，请指定其名称。对于多个属性，名称必须以逗号分隔。

下面是一些投影表达式的示例，基于 `ProductCatalog` 商品来自 [在 DynamoDB 中使用表达式时引用项目属性](Expressions.Attributes.md)：
+ 单个顶级属性。

  `Title `
+ 三个顶级属性。DynamoDB 检索整个 `Color` 设置。

  `Title, Price, Color`
+ 4 个顶级属性。DynamoDB 将返回 `RelatedItems` 和 `ProductReviews`。

  `Title, Description, RelatedItems, ProductReviews`

**注意**  
投影表达式对预调配吞吐量消耗没有影响。DynamoDB 将依据项目大小确定消耗的容量，而不是依据返回到应用程序的数据量。

**保留字和特殊字符**

DynamoDB 具有保留字和特殊字符。DynamoDB 允许您使用这些保留字和特殊字符作为名称，但建议您不要这样做，因为在表达式中使用这些名称时必须使用其别名。有关完整列表，请参阅[DynamoDB 中的保留字](ReservedWords.md)。

在以下情况下，您需要使用表达式属性名称代替实际名称：
+ 属性名称位于 DynamoDB 中的保留字列表中。
+ 属性名称不符合第一个字符是 `a-z` 或 `A-Z`，第二个字符（如果存在）是 `a-Z`、`A-Z` 或 `0-9` 的要求。
+ 属性名称包含 **\$1**（哈希）或 **:**（冒号）。

以下 AWS CLI 示例介绍了如何将投影表达式与 `GetItem` 运算一起使用。此投影表达式检索顶级标量属性 (`Description`)，列表中的第一个元素 (`RelatedItems[0]`) 和嵌套在地图中的列表 (`ProductReviews.FiveStar`)。

```
aws dynamodb get-item \
    --table-name ProductCatalog \
    --key '"Id": { "N": "123" } \
    --projection-expression "Description, RelatedItems[0], ProductReviews.FiveStar"
```

对于此示例，将返回以下 JSON。

```
{
    "Item": {
        "Description": {
            "S": "123 description"
        },
        "ProductReviews": {
            "M": {
                "FiveStar": {
                    "L": [
                        {
                            "S": "Excellent! Can't recommend it highly enough! Buy it!"
                        },
                        {
                            "S": "Do yourself a favor and buy this."
                        }
                    ]
                }
            }
        },
        "RelatedItems": {
            "L": [
                {
                    "N": "341"
                }
            ]
        }
    }
}
```

# 在 DynamoDB 中使用更新表达式
<a name="Expressions.UpdateExpressions"></a>

`UpdateItem` 操作会更新现有项目，或者将新项目添加到表中（如果该新项目尚不存在）。您必须提供要更新的项目的键。您还必须提供更新表达式，指示您要修改的属性以及要分配给这些属性的值。

*更新表达式* 指定 `UpdateItem` 将如何修改项目的属性，例如，设置标量值或者删除列表或映射中的元素。

下面是更新表达式的语法摘要。

```
update-expression ::=
    [ SET action [, action] ... ]
    [ REMOVE action [, action] ...]
    [ ADD action [, action] ... ]
    [ DELETE action [, action] ...]
```

更新表达式包含一个或多个子句。每个子句以 `SET`、`REMOVE`、`ADD` 或 `DELETE` 关键字开头。您可在更新表达式中按任意顺序包含其中任意子句。但是，每个操作关键字只能出现一次。

每个子句中存在一个或多个操作，用逗号分隔。每个操作表示一个数据修改。

此部分中的示例基于`ProductCatalog`中所示的 [在 DynamoDB 中使用投影表达式](Expressions.ProjectionExpressions.md) 项目。

以下主题介绍了 `SET` 操作的一些不同使用案例。

**Topics**
+ [SET – 修改或添加项目属性](#Expressions.UpdateExpressions.SET)
+ [REMOVE – 从项目中删除属性](#Expressions.UpdateExpressions.REMOVE)
+ [ADD – 更新数值和集](#Expressions.UpdateExpressions.ADD)
+ [DELETE – 从集中删除元素](#Expressions.UpdateExpressions.DELETE)
+ [使用多个更新表达式](#Expressions.UpdateExpressions.Multiple)

## SET – 修改或添加项目属性
<a name="Expressions.UpdateExpressions.SET"></a>

在更新表达式中使用 `SET` 操作可将一个或多个属性添加到项目。如果任意这些属性已存在，则将由新值覆盖。如果您要避免覆盖现有属性，则可以将 `SET` 与 `if_not_exists` 函数结合使用。`if_not_exists` 函数特定于 `SET` 操作，只能在更新表达式中使用。

当您使用 `SET` 更新列表元素时，将使用您指定的新数据替代该元素的内容。如果元素尚不存在，`SET` 会将新元素附加到列表的末尾。

如果在单个 `SET` 操作中添加多个元素，则元素会按照元素编号的顺序排序。

您还可使用 `SET` 来加或减 `Number` 类型的属性。要执行多个 `SET` 操作，请使用逗号分隔它们。

在以下语法摘要中：
+ *path* 元素是项目的文档路径。
+ **operand** 元素可以为项目的文档路径，或者为函数。

```
set-action ::=
    path = value

value ::=
    operand
    | operand '+' operand
    | operand '-' operand

operand ::=
    path | function

function ::=
    if_not_exists (path, value)
```

如果项目在指定路径中不包含属性，则 `if_not_exists` 的求值结果为 `value`。否则，它的求值结果为 `path`。

以下 `PutItem` 操作创建将在示例中引用的示例项目。

```
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json
```

`--item` 的参数存储在 `item.json` 文件中。（为简单起见，仅使用了几个项目属性。）

```
{
    "Id": {"N": "789"},
    "ProductCategory": {"S": "Home Improvement"},
    "Price": {"N": "52"},
    "InStock": {"BOOL": true},
    "Brand": {"S": "Acme"}
}
```

**Topics**
+ [修改属性](#Expressions.UpdateExpressions.SET.ModifyingAttributes)
+ [添加列表和映射](#Expressions.UpdateExpressions.SET.AddingListsAndMaps)
+ [将元素添加到列表](#Expressions.UpdateExpressions.SET.AddingListElements)
+ [添加嵌套映射属性](#Expressions.UpdateExpressions.SET.AddingNestedMapAttributes)
+ [对数值属性进行加减](#Expressions.UpdateExpressions.SET.IncrementAndDecrement)
+ [将元素附加到列表](#Expressions.UpdateExpressions.SET.UpdatingListElements)
+ [防止覆盖现有属性](#Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites)

### 修改属性
<a name="Expressions.UpdateExpressions.SET.ModifyingAttributes"></a>

**Example**  
更新 `ProductCategory` 和 `Price` 属性。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET ProductCategory = :c, Price = :p" \
    --expression-attribute-values file://values.json \
    --return-values ALL_NEW
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":c": { "S": "Hardware" },
    ":p": { "N": "60" }
}
```

**注意**  
在 `UpdateItem` 操作中，`--return-values ALL_NEW` 将导致 DynamoDB 按更新后的情况返回项目。

### 添加列表和映射
<a name="Expressions.UpdateExpressions.SET.AddingListsAndMaps"></a>

**Example**  
添加新列表和新映射。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET RelatedItems = :ri, ProductReviews = :pr" \
    --expression-attribute-values file://values.json \
    --return-values ALL_NEW
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":ri": {
        "L": [
            { "S": "Hammer" }
        ]
    },
    ":pr": {
        "M": {
            "FiveStar": {
                "L": [
                    { "S": "Best product ever!" }
                ]
            }
        }
    }
}
```

### 将元素添加到列表
<a name="Expressions.UpdateExpressions.SET.AddingListElements"></a>

**Example**  
将新元素添加到 `RelatedItems` 列表。（请记住，列表元素从 0 开始，因此 [0] 表示列表中的第一个元素，[1] 表示第二个元素，依此类推。）  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET RelatedItems[1] = :ri" \
    --expression-attribute-values file://values.json \
    --return-values ALL_NEW
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":ri": { "S": "Nails" }
}
```

**注意**  
当您使用 `SET` 更新列表元素时，将使用您指定的新数据替代该元素的内容。如果元素尚不存在，`SET` 会将新元素附加到列表的末尾。  
如果在单个 `SET` 操作中添加多个元素，则元素会按照元素编号的顺序排序。

### 添加嵌套映射属性
<a name="Expressions.UpdateExpressions.SET.AddingNestedMapAttributes"></a>

**Example**  
添加一些嵌套映射属性。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET #pr.#5star[1] = :r5, #pr.#3star = :r3" \
    --expression-attribute-names file://names.json \
    --expression-attribute-values file://values.json \
    --return-values ALL_NEW
```
`--expression-attribute-names` 的参数存储在 `names.json` 文件中。  

```
{
    "#pr": "ProductReviews",
    "#5star": "FiveStar",
    "#3star": "ThreeStar"
}
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":r5": { "S": "Very happy with my purchase" },
    ":r3": {
        "L": [
            { "S": "Just OK - not that great" }
        ]
    }
}
```

**重要**  
如果父映射不存在，则无法更新嵌套映射属性。如果您在父映射 (`ProductReviews`) 不存在时尝试更新嵌套属性（例如 `ProductReviews.FiveStar`），则 DynamoDB 会返回 `ValidationException`，并显示消息*“The document path provided in the update expression is invalid for update.”*  
在创建稍后将更新嵌套映射属性的项目时，请初始化父属性的空映射。例如：  

```
{
    "Id": {"N": "789"},
    "ProductReviews": {"M": {}},
    "Metadata": {"M": {}}
}
```
这使您可以像 `ProductReviews.FiveStar` 一样更新嵌套属性，而不会出现错误。

### 对数值属性进行加减
<a name="Expressions.UpdateExpressions.SET.IncrementAndDecrement"></a>

您可以对现有数值属性执行加减运算。为此，请使用 `+`（加号）和 `-`（减号）运算符。

**Example**  
降低项目的 `Price`。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET Price = Price - :p" \
    --expression-attribute-values '{":p": {"N":"15"}}' \
    --return-values ALL_NEW
```
要提高 `Price`，请在更新表达式中使用 `+` 运算符。

### 将元素附加到列表
<a name="Expressions.UpdateExpressions.SET.UpdatingListElements"></a>

您可将元素添加到列表的末尾。为此，请将 `SET` 与 `list_append` 函数结合使用。（函数名区分大小写。） `list_append` 函数特定于 `SET` 操作，只能在更新表达式中使用。语法如下所示。
+ `list_append (list1, list2)`

该函数将选取两个列表作为输入，并将所有元素从 `list2` 附加到 ` list1`。

**Example**  
在[将元素添加到列表](#Expressions.UpdateExpressions.SET.AddingListElements)中，您创建 `RelatedItems` 列表并填充两个元素：`Hammer` 和 `Nails`。现在您将另外两个元素附加到 `RelatedItems` 的末尾。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET #ri = list_append(#ri, :vals)" \
    --expression-attribute-names '{"#ri": "RelatedItems"}' \
    --expression-attribute-values file://values.json  \
    --return-values ALL_NEW
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":vals": {
        "L": [
            { "S": "Screwdriver" },
            {"S": "Hacksaw" }
        ]
    }
}
```
最后，您将另外一个元素添加到 `RelatedItems` 的 *beginning*。为此，请交换 `list_append` 元素的顺序。（请记住，`list_append` 将选取两个列表作为输入，并会将第二个列表附加到第一个列表。）  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET #ri = list_append(:vals, #ri)" \
    --expression-attribute-names '{"#ri": "RelatedItems"}' \
    --expression-attribute-values '{":vals": {"L": [ { "S": "Chisel" }]}}' \
    --return-values ALL_NEW
```
生成的 `RelatedItems` 属性现在包含 5 个元素，其顺序如下：`Chisel`、`Hammer`、`Nails`、`Screwdriver`、`Hacksaw`。

### 防止覆盖现有属性
<a name="Expressions.UpdateExpressions.SET.PreventingAttributeOverwrites"></a>

**Example**  
设置项目的 `Price`，但仅当项目还没有 `Price` 属性时设置。（如果 `Price` 已存在，则不执行任何操作。）  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET Price = if_not_exists(Price, :p)" \
    --expression-attribute-values '{":p": {"N": "100"}}' \
    --return-values ALL_NEW
```

## REMOVE – 从项目中删除属性
<a name="Expressions.UpdateExpressions.REMOVE"></a>

在更新表达式中使用 `REMOVE` 操作可在 Amazon DynamoDB 中从某个项目删除一个或多个元素。要执行多个 `REMOVE` 操作，请使用逗号分隔它们。

下面是更新表达式中的 `REMOVE` 的语法摘要。唯一的操作数是您要删除的属性的文档路径。

```
remove-action ::=
    path
```

**Example**  
从项目中删除部分属性。（如果属性不存在，则不会执行任何操作。）  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "REMOVE Brand, InStock, QuantityOnHand" \
    --return-values ALL_NEW
```

### 从列表中删除元素
<a name="Expressions.UpdateExpressions.REMOVE.RemovingListElements"></a>

您可使用 `REMOVE` 从列表中删除各个元素。

**Example**  
在[将元素附加到列表](#Expressions.UpdateExpressions.SET.UpdatingListElements)中，您修改了列表属性 (`RelatedItems`)，使它包含 5 个元素：  
+ `[0]`—`Chisel`
+ `[1]`—`Hammer`
+ `[2]`—`Nails`
+ `[3]`—`Screwdriver`
+ `[4]`—`Hacksaw`
以下 AWS Command Line Interface (AWS CLI) 示例从列表中删除 `Hammer` 和 `Nails`。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "REMOVE RelatedItems[1], RelatedItems[2]" \
    --return-values ALL_NEW
```
删除 `Hammer` 和 `Nails` 之后，剩下的元素将会移位。此列表现在包含以下元素：  
+ `[0]`—`Chisel`
+ `[1]`—`Screwdriver`
+ `[2]`—`Hacksaw`

## ADD – 更新数值和集
<a name="Expressions.UpdateExpressions.ADD"></a>

**注意**  
通常，我们建议使用 `SET` 而不是 `ADD` 来确保幂等运算。

在更新表达式中使用 `ADD` 操作可将新属性及其值添加到项目。

如果属性已存在，则 `ADD` 的行为取决于属性的数据类型：
+ 如果属性是数字，并且添加的值也是数字，则该值将按数学运算与现有属性相加。（如果该值为负数，则从现有属性减去该值。）
+ 如果属性是集合，并且您添加的值也是集合，则该值将附加到现有集合中。

**注意**  
`ADD` 操作仅支持数字和集合数据类型。

要执行多个 `ADD` 操作，请使用逗号分隔它们。

在以下语法摘要中：
+ *path* 元素是属性的文档路径。属性必须为 `Number` 或 set 数据类型。
+ *value* 元素是要与属性相加的值（对于 `Number` 数据类型），或者是要附加到属性中的集合（对于 set 类型）。

```
add-action ::=
    path value
```

以下主题介绍了 `ADD` 操作的一些不同使用案例。

**Topics**
+ [添加数值](#Expressions.UpdateExpressions.ADD.Number)
+ [将元素添加到集](#Expressions.UpdateExpressions.ADD.Set)

### 添加数值
<a name="Expressions.UpdateExpressions.ADD.Number"></a>

假设 `QuantityOnHand` 属性不存在。以下 AWS CLI 示例会将 `QuantityOnHand` 设置为 5。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "ADD QuantityOnHand :q" \
    --expression-attribute-values '{":q": {"N": "5"}}' \
    --return-values ALL_NEW
```

既然 `QuantityOnHand` 存在，您可重新运行该示例以使 `QuantityOnHand` 每次增加 5。

### 将元素添加到集
<a name="Expressions.UpdateExpressions.ADD.Set"></a>

假设 `Color` 属性不存在。以下 AWS CLI 示例会将 `Color` 设置为包含两个元素的字符串集。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "ADD Color :c" \
    --expression-attribute-values '{":c": {"SS":["Orange", "Purple"]}}' \
    --return-values ALL_NEW
```

既然 `Color` 存在，您可向其添加更多元素。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "ADD Color :c" \
    --expression-attribute-values '{":c": {"SS":["Yellow", "Green", "Blue"]}}' \
    --return-values ALL_NEW
```

## DELETE – 从集中删除元素
<a name="Expressions.UpdateExpressions.DELETE"></a>

**重要**  
`DELETE` 操作仅支持 `Set` 数据类型。

在更新表达式中使用 `DELETE` 操作可从集合中删除一个或多个元素。要执行多个 `DELETE` 操作，请使用逗号分隔它们。

在以下语法摘要中：
+ *path* 元素是属性的文档路径。该属性必须是集数据类型。
+ *子网* 是您要从 *path* 中删除的一个或多个元素。您必须指定 *subset* 作为集类型。

```
delete-action ::=
    path subset
```

**Example**  
在[将元素添加到集](#Expressions.UpdateExpressions.ADD.Set)中，您创建 `Color` 字符串集合。本示例将从该集合中删除部分元素。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "DELETE Color :p" \
    --expression-attribute-values '{":p": {"SS": ["Yellow", "Purple"]}}' \
    --return-values ALL_NEW
```

## 使用多个更新表达式
<a name="Expressions.UpdateExpressions.Multiple"></a>

您可以在单个更新表达式中使用多个操作。在应用任何操作之前，将根据项目的状态对所有属性引用进行解析。

**Example**  
给定项目 `{"id": "1", "a": 1, "b": 2, "c": 3}` 时，以下表达式删除 `a` 并交换 `b` 及 `c` 的值：  

```
aws dynamodb update-item \
    --table-name test \
    --key '{"id":{"S":"1"}}' \
    --update-expression "REMOVE a SET b = a, c = b" \
    --return-values ALL_NEW
```
结果是 `{"id": "1", "b": 1, "c": 2}`。尽管在同一个表达式中 `a` 被移除并向 `b` 重新赋值，但两个引用都将解析为其原始值。

**Example**  
如果要修改属性的值并彻底删除另一个属性，可以在单个语句中使用 SET 和 REMOVE 操作。此操作会将 `Price` 值降至 15，同时还会从项目中删除 `InStock` 属性。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET Price = Price - :p REMOVE InStock" \
    --expression-attribute-values '{":p": {"N":"15"}}' \
    --return-values ALL_NEW
```

**Example**  
如果您想在添加到列表的同时更改另一个属性的值，则可以在单个语句中使用两个 SET 操作。此操作会将 “Nails” 添加到 `RelatedItems` 列表属性中，并将 `Price` 值设置为 21。  

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"789"}}' \
    --update-expression "SET RelatedItems[1] = :newValue, Price = :newPrice" \
    --expression-attribute-values '{":newValue": {"S":"Nails"}, ":newPrice": {"N":"21"}}'  \
    --return-values ALL_NEW
```

# DynamoDB 中的条件表达式和筛选表达式、运算符及函数
<a name="Expressions.OperatorsAndFunctions"></a>

要操作 DynamoDB 表中的数据，请使用 `PutItem`、`UpdateItem` 和 `DeleteItem` 操作。对于这些数据处理操作，您可指定条件表达式 来确定应修改的项目。如果条件表达式的计算结果为 true，则操作成功。否则，该操作将失败。

本节介绍用于在 Amazon DynamoDB 中编写筛选表达式和条件表达式的内置函数和关键字。有关 DynamoDB 的函数和编程的更多详细信息，请参阅[使用 DynamoDB 和 AWS SDK 编程](Programming.md)和 [DynamoDB API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)。

**Topics**
+ [筛选条件和条件表达式的语法](#Expressions.OperatorsAndFunctions.Syntax)
+ [进行比较](#Expressions.OperatorsAndFunctions.Comparators)
+ [函数](#Expressions.OperatorsAndFunctions.Functions)
+ [逻辑评估](#Expressions.OperatorsAndFunctions.LogicalEvaluations)
+ [圆括号](#Expressions.OperatorsAndFunctions.Parentheses)
+ [条件的优先顺序](#Expressions.OperatorsAndFunctions.Precedence)

## 筛选条件和条件表达式的语法
<a name="Expressions.OperatorsAndFunctions.Syntax"></a>

在以下语法摘要中，*操作数* 可以为下列对象：
+ 顶级属性名称，例如 `Id`、`Title`、`Description` 或 `ProductCategory`
+ 引用嵌套属性的文档路径

```
condition-expression ::=
      operand comparator operand
    | operand BETWEEN operand AND operand
    | operand IN ( operand (',' operand (, ...) ))
    | function
    | condition AND condition
    | condition OR condition
    | NOT condition
    | ( condition )

comparator ::=
    =
    | <>
    | <
    | <=
    | >
    | >=

function ::=
    attribute_exists (path)
    | attribute_not_exists (path)
    | attribute_type (path, type)
    | begins_with (path, substr)
    | contains (path, operand)
    | size (path)
```

## 进行比较
<a name="Expressions.OperatorsAndFunctions.Comparators"></a>

使用以下比较器将操作数与单个值进行比较：
+ `a = b` – 如果 *a* 等于 *b*，则为 True。
+ `a <> b` – 如果 *a* 不等于 *b*，则为 True。
+ `a < b` – 如果 *a* 小于 *b*，则为 True。
+ `a <= b` – 如果 *a* 小于等于 *b*，则为 True。
+ `a > b` – 如果 *a* 大于 *b*，则为 True。
+ `a >= b` – 如果 *a* 大于等于 *b*，则为 True。

使用 `BETWEEN` 和 `IN` 关键字来将操作数与值范围或值的枚举值列表进行比较：
+ `a BETWEEN b AND c` – 如果 *a* 大于或等于 *b*，且小于或等于 *c*，则为 True。
+ `a IN (b, c, d) ` – 如果 *a* 等于列表中的任何值 — 例如 *b*、*c* 或 *d*，则为 True。列表最多可以包含 100 个值，以逗号分隔。

## 函数
<a name="Expressions.OperatorsAndFunctions.Functions"></a>

使用以下函数确定项目中是否存在某个属性，或者对属性求值。这些函数名称区分大小写。对于嵌套属性，您必须提供其完整文档路径。


****  

| 函数 | 说明 | 
| --- | --- | 
|  `attribute_exists (path)`  | 如果项目包含 `path` 指定的属性，则为 true。 示例：检查 `Product` 表中的项目是否具有侧视图图片。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  | 
|  `attribute_not_exists (path)`  | 如果项目中不存在由 `path` 指定的属性，则为 true。 示例：检查项目是否具有 `Manufacturer` 属性。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  | 
|  `attribute_type (path, type)`  |  如果指定路径中的属性为特定数据类型，则为 true。`type` 参数必须是下列类型之一： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 您必须使用 `type` 参数的表达式属性值。 示例：检查 `QuantityOnHand` 属性是否为列表类型。在本示例中，`:v_sub` 为字符串 `L` 的占位符。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 您必须使用 `type` 参数的表达式属性值。  | 
|  `begins_with (path, substr)`  |  如果 `path` 指定的属性以特定子字符串开头，则为 true。 示例：检查前视图图片 URL 的前几个字符是否为 `http://`。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 表达式属性值 `:v_sub` 是 `http://` 的占位符。  | 
|  `contains (path, operand)`  | 如果 `path` 指定的属性为以下之一，则为 true： [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 如果由 `path` 指定的属性为 `String`，则 `operand` 必须为 `String`。如果指定的属性 `path` 是一个 `Set`，`operand` 必须是集合的元素类型。 路径和操作数必须不同。也就是说，`contains (a, a)` 返回错误。 示例：检查 `Brand` 属性是否包含子字符串 `Company`。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 表达式属性值 `:v_sub` 是 `Company` 的占位符。 示例：检查产品是否有红色。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 表达式属性值 `:v_sub` 是 `Red` 的占位符。 | 
|  `size (path)`  | 返回一个代表属性大小的数字。以下是与 `size` 结合使用的有效数据类型。  如果属性类型为 `String`，则 `size` 将返回字符串的长度。 示例：检查字符串 `Brand` 是否少于等于 20 个字符。表达式属性值 `:v_sub` 是 `20` 的占位符。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  如果属性类型为 `Binary`，则 `size` 将返回属性值中的字节数。 示例：假设 `ProductCatalog` 项目有一个名为 `VideoClip` 的二进制属性，该属性包含使用中的产品的简短视频。以下表达式将检查 `VideoClip` 是否超过 64000 个字节。表达式属性值 `:v_sub` 是 `64000` 的占位符。[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  如果属性是一个 `Set` 数据类型，则 `size` 将返回集合中的元素数。 示例：检查产品是否有多种颜色。表达式属性值 `:v_sub` 是 `1` 的占位符。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  如果属性类型为 `List` 或 `Map`，则 `size` 将返回子元素数。 示例：检查 `OneStar` 评论的数量是否超过了特定阈值。表达式属性值 `:v_sub` 是 `3` 的占位符。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  | 

## 逻辑评估
<a name="Expressions.OperatorsAndFunctions.LogicalEvaluations"></a>

使用 `AND`、`OR` 和 `NOT` 关键字执行逻辑评估。在以下列表中，*a* 和 *b* 代表要评估的条件。
+ `a AND b` – 如果 *a* 和 *b* 均为 true，则为 True。
+ `a OR b` – 如果 *a* 和/或 *b* 为 true，则为 True。
+ `NOT a` – 如果 *a* 为 false，则为 True。如果 *a* 为 true，则为 False。

以下是操作中 AND 的代码示例。

`dynamodb-local (*)> select * from exprtest where a > 3 and a < 5;`

## 圆括号
<a name="Expressions.OperatorsAndFunctions.Parentheses"></a>

使用圆括号更改逻辑评估的优先顺序。例如，假设条件 *a* 和 *b* 为 true，而条件 *c* 为 false。以下表达式的计算结果为 True：
+ `a OR b AND c`

但是，如果将一个条件括在圆括号中，则会先对该条件求值。例如，以下表达式的计算结算为 False：
+  `(a OR b) AND c`

**注意**  
您可以在表达式中嵌套圆括号。最里面的部分最先评估。

以下是逻辑评估中带有括号的代码示例。

`dynamodb-local (*)> select * from exprtest where attribute_type(b, string) or ( a = 5 and c = “coffee”);`

## 条件的优先顺序
<a name="Expressions.OperatorsAndFunctions.Precedence"></a>

 DynamoDB 使用以下优先顺序规则从左向右评估条件：
+ `= <> < <= > >=`
+ `IN`
+ `BETWEEN`
+ `attribute_exists attribute_not_exists begins_with contains`
+ 圆括号
+ `NOT`
+ `AND`
+ `OR`

# DynamoDB 条件表达式 CLI 示例
<a name="Expressions.ConditionExpressions"></a>

下面是使用条件表达式的一些 AWS Command Line Interface (AWS CLI) 示例。这些示例基于`ProductCatalog`中介绍的 [在 DynamoDB 中使用表达式时引用项目属性](Expressions.Attributes.md) 表。此表的分区键是 `Id`；没有排序键。以下 `PutItem` 操作创建示例所引用的 `ProductCatalog` 项目例子。

```
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json
```

`--item` 的参数存储在 `item.json` 文件中。（为简单起见，仅使用了几个项目属性。）

```
{
    "Id": {"N": "456" },
    "ProductCategory": {"S": "Sporting Goods" },
    "Price": {"N": "650" }
}
```

**Topics**
+ [带条件放置](#Expressions.ConditionExpressions.PreventingOverwrites)
+ [带条件删除](#Expressions.ConditionExpressions.AdvancedComparisons)
+ [带条件更新](#Expressions.ConditionExpressions.SimpleComparisons)
+ [条件表达式示例](#Expressions.ConditionExpressions.ConditionalExamples)

## 带条件放置
<a name="Expressions.ConditionExpressions.PreventingOverwrites"></a>

`PutItem` 操作覆盖具有相同主键的项目（如果存在）。如果要避免这种情况，请使用条件表达式。这样，只有当相关的项目还没有相同的主键时，才能继续写入。

以下示例使用 `attribute_not_exists()` 在尝试写入操作之前检查表中是否存在主键。

**注意**  
如果主键同时包含分区键（pk）和排序键（sk），该参数将在尝试写入操作之前检查 `attribute_not_exists(pk)` 和 `attribute_not_exists(sk)` 作为整个语句的计算结果是 true 还是 false。

```
aws dynamodb put-item \
    --table-name ProductCatalog \
    --item file://item.json \
    --condition-expression "attribute_not_exists(Id)"
```

如果条件表达式的计算结果为 false，DynamoDB 将返回以下错误消息：有条件请求失败。

**注意**  
有关 `attribute_not_exists` 和其他函数的更多信息，请参阅 [DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)。

## 带条件删除
<a name="Expressions.ConditionExpressions.AdvancedComparisons"></a>

要执行有条件删除，请将 `DeleteItem` 操作与条件表达式一起使用。要继续执行操作，条件表达式的求值结果必须为 true；否则操作将失败。

考虑上面定义的项目。

假设您要删除该项目，但只能在以下条件下删除：
+  `ProductCategory` 为“Sporting Goods”或“Gardening Supplies”。
+  `Price` 介于 500 和 600 之间。

以下示例尝试删除该项目。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id":{"N":"456"}}' \
    --condition-expression "(ProductCategory IN (:cat1, :cat2)) and (Price between :lo and :hi)" \
    --expression-attribute-values file://values.json
```

`--expression-attribute-values` 的参数存储在 `values.json` 文件中。

```
{
    ":cat1": {"S": "Sporting Goods"},
    ":cat2": {"S": "Gardening Supplies"},
    ":lo": {"N": "500"},
    ":hi": {"N": "600"}
}
```

**注意**  
在条件表达式中，`:`（冒号字符）表示*表达式属性值*-实际值的占位符。有关更多信息，请参阅 [在 DynamoDB 中使用表达式属性值](Expressions.ExpressionAttributeValues.md)。  
有关 `IN`、`AND` 和其他关键字的更多信息，请参阅[DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)。

在本示例中，`ProductCategory` 比较的计算结果为 true，但 `Price` 比较的计算结果为 false。这导致条件表达式的计算结果为 false，并且 `DeleteItem` 操作失败。

## 带条件更新
<a name="Expressions.ConditionExpressions.SimpleComparisons"></a>

要执行有条件更新，请将 `UpdateItem` 操作与条件表达式一起使用。要继续执行操作，条件表达式的求值结果必须为 true；否则操作将失败。

**注意**  
`UpdateItem` 还支持*更新表达式*，您在其中指定要对项目进行的修改。有关更多信息，请参阅 [在 DynamoDB 中使用更新表达式](Expressions.UpdateExpressions.md)。

假定您从上面定义的项目开始。

以下示例执行 `UpdateItem` 操作。它试图将产品的 `Price` 减少 75，但是如果当前 `Price` 小于或等于 500，条件表达式会阻止更新。

```
aws dynamodb update-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --update-expression "SET Price = Price - :discount" \
    --condition-expression "Price > :limit" \
    --expression-attribute-values file://values.json
```

`--expression-attribute-values` 的参数存储在 `values.json` 文件中。

```
{
    ":discount": { "N": "75"},
    ":limit": {"N": "500"}
}
```

如果起始 `Price` 为 650，则 `UpdateItem` 操作会将 `Price` 降至 575。如果您再次运行 `UpdateItem` 操作，`Price` 将降至 500。如果您第三次运行该操作，则条件表达式的计算结果为 false，并且更新失败。

**注意**  
在条件表达式中，`:`（冒号字符）表示*表达式属性值*-实际值的占位符。有关更多信息，请参阅 [在 DynamoDB 中使用表达式属性值](Expressions.ExpressionAttributeValues.md)。  
有关“*>*”和其他运算符的更多信息，请参阅 [DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)。

## 条件表达式示例
<a name="Expressions.ConditionExpressions.ConditionalExamples"></a>

有关以下示例中使用的函数的更多信息，请参阅 [DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)。若要详细了解如何在表达式中指定不同的属性类型，请参阅 [在 DynamoDB 中使用表达式时引用项目属性](Expressions.Attributes.md)。

### 检查项目中的属性
<a name="Expressions.ConditionExpressions.CheckingForAttributes"></a>

您可以检查任何属性是否存在。如果条件表达式的计算结果为 true，则操作成功；否则操作失败。

以下示例使用了 `attribute_not_exists`，以便仅当产品没有 `Price` 属性时才删除产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "attribute_not_exists(Price)"
```

DynamoDB 还提供了一个 `attribute_exists` 函数。以下示例仅当收到不好的评价时删除产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "attribute_exists(ProductReviews.OneStar)"
```

### 检查属性类型
<a name="Expressions.ConditionExpressions.CheckingForAttributeType"></a>

您可以使用 `attribute_type` 函数检查属性值的数据类型。如果条件表达式的计算结果为 true，则操作成功；否则操作失败。

以下示例使用 `attribute_type` 删除具有类型为“字符串集”的 `Color` 属性的产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "attribute_type(Color, :v_sub)" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在 expression-attribute-values.json 文件中。

```
{
    ":v_sub":{"S":"SS"}
}
```

### 检查字符串的起始值
<a name="Expressions.ConditionExpressions.CheckingBeginsWith"></a>

您可以使用 `begins_with` 函数检查字符串属性值是否以特定子字符串开头。如果条件表达式的计算结果为 true，则操作成功；否则操作失败。

以下示例使用 `begins_with` 删除 `FrontView` 映射的 `Pictures` 元素以特定值开头的产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "begins_with(Pictures.FrontView, :v_sub)" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在 expression-attribute-values.json 文件中。

```
{
    ":v_sub":{"S":"http://"}
}
```

### 检查集中的元素
<a name="Expressions.ConditionExpressions.CheckingForContains"></a>

您可以使用 `contains` 函数检查集中的元素或在字符串内查找子字符串。如果条件表达式的计算结果为 true，则操作成功；否则操作失败。

以下示例使用 `contains` 删除 `Color` 字符串集中包含具有特定值的元素的产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "contains(Color, :v_sub)" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在 expression-attribute-values.json 文件中。

```
{
    ":v_sub":{"S":"Red"}
}
```

### 检查属性值的大小
<a name="Expressions.ConditionExpressions.CheckingForSize"></a>

您可以使用 `size` 函数检查属性值的大小。如果条件表达式的计算结果为 true，则操作成功；否则操作失败。

以下示例使用 `size` 删除 `VideoClip` 二进制属性的大小超过 `64000` 字节的产品。

```
aws dynamodb delete-item \
    --table-name ProductCatalog \
    --key '{"Id": {"N": "456"}}' \
    --condition-expression "size(VideoClip) > :v_sub" \
    --expression-attribute-values file://expression-attribute-values.json
```

`--expression-attribute-values` 的参数存储在 expression-attribute-values.json 文件中。

```
{
    ":v_sub":{"N":"64000"}
}
```

# 在 DynamoDB 中使用生存时间（TTL）
<a name="TTL"></a>

DynamoDB 的生存时间（TTL）是一种经济实惠的方法，用于删除不再相关的项目。通过 TTL，您可以定义每个项目的过期时间戳，指示何时不再需要某个项目。DynamoDB 会在项目过期时间到期后的几天内自动将其删除，而不会消耗写入吞吐量。

要使用 TTL，请先在表上启用它，然后定义一个特定的属性来存储 TTL 过期时间戳。时间戳必须以[数字](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)数据类型存储，采用 [Unix 纪元时间格式](https://en.wikipedia.org/wiki/Unix_time)，精确到秒级粒度。如果项目具有非数字类型的 TTL 属性，TTL 流程会忽略该项目。每次创建或更新项目时，您都可以计算过期时间并将其保存在 TTL 属性中。

系统可以随时删除具有有效、已过期 TTL 属性的项目，通常是在过期后的几天内。您仍然可以更新待删除的过期项目，包括更改或删除其 TTL 属性。更新过期项目时，我们建议您使用条件表达式来确保该项目随后未被删除。使用筛选表达式从[扫描](Scan.md#Scan.FilterExpression)和[查询](Query.FilterExpression.md)结果中删除过期的项目。

已删除项目的工作原理与通过典型删除操作删除的项目类似。删除后，项目会以服务删除而不是用户删除的形式进入 DynamoDB Streams，并像其它删除操作一样从本地二级索引和全局二级索引中删除。

如果使用全局表的[全局表版本 2019.11.21（当前版）](GlobalTables.md)，并且还使用生存时间特征，则 DynamoDB 会将 TTL 删除复制到所有副本表。在出现 TTL 到期的区域中，初始 TTL 删除不会消耗写入容量单位（WCU）。但是，在每个副本区域中，当使用预置的容量时，复制到副本表的 TTL 删除将消耗一个复制的写入容量单位，或在使用按需容量模式时消耗一个复制的写入容量单位，并且将收取适用的费用。

有关 TTL 的更多信息，请参阅以下主题：

**Topics**
+ [在 DynamoDB 中启用生存时间（TTL）](time-to-live-ttl-how-to.md)
+ [在 DynamoDB 中计算生存时间（TTL）](time-to-live-ttl-before-you-start.md)
+ [使用过期项目和生存时间（TTL）](ttl-expired-items.md)

# 在 DynamoDB 中启用生存时间（TTL）
<a name="time-to-live-ttl-how-to"></a>

**注意**  
为了协助调试 TTL 功能和验证该功能是否正常运行，为项目 TTL 提供的值将以纯文本形式记录在 DynamoDB 诊断日志中。

您可以在 Amazon DynamoDB 控制台中，在 AWS Command Line Interface（AWS CLI）中，或者对于任何支持的 AWS SDK 使用 [Amazon DynamoDB API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)，来启用 TTL。在所有分区中启用 TTL 大约需要一个小时。

## 使用 AWS 控制台启用 DynamoDB TTL
<a name="time-to-live-ttl-how-to-enable-console"></a>

1. 登录 AWS 管理控制台，打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 选择**表**，然后选择您要修改的表。

1. 在**其它设置**选项卡的**生存时间(TTL)** 部分中，选择**开启**来启用 TTL。

1. 在表上启用 TTL 时，DynamoDB 要求您标识此服务在确定项目是否符合过期条件时将查找的特定属性名称。如下所示的 TTL 属性名称区分大小写，并且必须与读取和写入操作中定义的属性相匹配。不匹配将导致已过期的项目被取消删除。重命名 TTL 属性需要您禁用 TTL，然后使用新属性重新启用它。禁用后，TTL 将在大约 30 分钟内继续处理删除。必须对已恢复的表重新配置 TTL。  
![\[区分大小写的 TTL 属性名称，DynamoDB 使用该属性来确定项目是否符合过期条件。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/EnableTTL-Settings.png)

1. （可选）您可以通过模拟过期日期和时间并匹配几个项目来执行测试。这为您提供了项目的样本列表，并确认有些项目包含随过期时间提供的 TTL 属性名称。

TTL 启用后，当您在 DynamoDB 控制台上查看项目时，TTL 属性被标记为 **TTL**。您可以通过将指针悬停在属性上来查看项目过期的日期和时间。

## 使用 API 启用 DynamoDB TTL
<a name="time-to-live-ttl-how-to-enable-api"></a>

------
#### [ Python ]

您可以使用 [UpdateTimeToLive](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/update_time_to_live.html) 操作通过代码启用 TTL。

```
import boto3


def enable_ttl(table_name, ttl_attribute_name):
    """
    Enables TTL on DynamoDB table for a given attribute name
        on success, returns a status code of 200
        on error, throws an exception

    :param table_name: Name of the DynamoDB table
    :param ttl_attribute_name: The name of the TTL attribute being provided to the table.
    """
    try:
        dynamodb = boto3.client('dynamodb')

        # Enable TTL on an existing DynamoDB table
        response = dynamodb.update_time_to_live(
            TableName=table_name,
            TimeToLiveSpecification={
                'Enabled': True,
                'AttributeName': ttl_attribute_name
            }
        )

        # In the returned response, check for a successful status code.
        if response['ResponseMetadata']['HTTPStatusCode'] == 200:
            print("TTL has been enabled successfully.")
        else:
            print(f"Failed to enable TTL, status code {response['ResponseMetadata']['HTTPStatusCode']}")
    except Exception as ex:
        print("Couldn't enable TTL in table %s. Here's why: %s" % (table_name, ex))
        raise


# your values
enable_ttl('your-table-name', 'expirationDate')
```

您可以使用 [DescribeTimeToLive](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb/client/describe_time_to_live.html) 操作确认 TTL 已启用，该操作描述了表上的 TTL 状态。`TimeToLive` 状态为 `ENABLED` 或 `DISABLED`。

```
# create a DynamoDB client
dynamodb = boto3.client('dynamodb')

# set the table name
table_name = 'YourTable'

# describe TTL
response = dynamodb.describe_time_to_live(TableName=table_name)
```

------
#### [ JavaScript ]

您可以使用 [UpdateTimeToLiveCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-dynamodb/Class/UpdateTimeToLiveCommand/) 操作通过代码启用 TTL。

```
import { DynamoDBClient, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";

const enableTTL = async (tableName, ttlAttribute) => {

    const client = new DynamoDBClient({});

    const params = {
        TableName: tableName,
        TimeToLiveSpecification: {
            Enabled: true,
            AttributeName: ttlAttribute
        }
    };

    try {
        const response = await client.send(new UpdateTimeToLiveCommand(params));
        if (response.$metadata.httpStatusCode === 200) {
            console.log(`TTL enabled successfully for table ${tableName}, using attribute name ${ttlAttribute}.`);
        } else {
            console.log(`Failed to enable TTL for table ${tableName}, response object: ${response}`);
        }
        return response;
    } catch (e) {
        console.error(`Error enabling TTL: ${e}`);
        throw e;
    }
};

// call with your own values
enableTTL('ExampleTable', 'exampleTtlAttribute');
```

------

## 使用 AWS CLI 启用生存时间
<a name="time-to-live-ttl-how-to-enable-cli-sdk"></a>

1. 在 `TTLExample` 表上启用 TTL。

   ```
   aws dynamodb update-time-to-live --table-name TTLExample --time-to-live-specification "Enabled=true, AttributeName=ttl"
   ```

1. 在 `TTLExample` 表上描述 TTL。

   ```
   aws dynamodb describe-time-to-live --table-name TTLExample
   {
       "TimeToLiveDescription": {
           "AttributeName": "ttl",
           "TimeToLiveStatus": "ENABLED"
       }
   }
   ```

1. 通过使用 BASH shell 和 `TTLExample` 设置生存时间属性将项目添加至 AWS CLI 表。

   ```
   EXP=`date -d '+5 days' +%s`
   aws dynamodb put-item --table-name "TTLExample" --item '{"id": {"N": "1"}, "ttl": {"N": "'$EXP'"}}'
   ```

此示例从当前日期开始，并在当前日期上增加 5 天来创建过期时间。然后，它将过期时间转换为纪元时间格式，以便最终添加项目到“`TTLExample`”表。

**注意**  
 为生存时间设置过期值的一种方式是计算添加到过期时间的秒数。例如，5 天是 432000 秒。但是，人们通常习惯于从某个日期算起。

获取当前时间的纪元时间格式非常简单，如下例中所示。
+ Linux 终端：`date +%s`
+ Python：`import time; int(time.time())`
+ Java：`System.currentTimeMillis() / 1000L`
+ JavaScript: `Math.floor(Date.now() / 1000)`

## 使用 CloudFormation 启用 DynamoDB TTL
<a name="time-to-live-ttl-how-to-enable-cf"></a>

```
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  TTLExampleTable:
    Type: AWS::DynamoDB::Table
    Description: "A DynamoDB table with TTL Specification enabled"
    Properties:
      AttributeDefinitions:
        - AttributeName: "Album"
          AttributeType: "S"
        - AttributeName: "Artist"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "Album"
          KeyType: "HASH"
        - AttributeName: "Artist"
          KeyType: "RANGE"
      ProvisionedThroughput:
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"
      TimeToLiveSpecification:
        AttributeName: "TTLExampleAttribute"
        Enabled: true
```

可以在[此处](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-timetolivespecification.html)找到有关在 CloudFormation 模板中使用 TTL 的更多详细信息。

# 在 DynamoDB 中计算生存时间（TTL）
<a name="time-to-live-ttl-before-you-start"></a>

实现 TTL 的常用方法是根据项目的创建时间或上次更新时间为其设置过期时间。这可以通过在 `createdAt` 和 `updatedAt` 时间戳中添加时间来完成。例如，可以将新创建项目的 TTL 设置为 `createdAt` \$1 90 天。项目更新后，TTL 可以重新计算为 `updatedAt` \$1 90 天。

计算出的过期时间必须采用纪元格式，以秒为单位。考虑到过期和删除的情况，TTL 不能超过过去五年。如果您使用任何其他格式，TTL 进程将忽略该项目。如果您根据需要将过期时间设置为将来的某个时间，则项目会在该时间之后过期。例如，假设您将过期时间设置为 1724241326 [即 2024 年 8 月 21 日星期一 11:55:26（UTC）]。该项目会在指定时间后过期。没有最低 TTL 持续时间。您可以将过期时间设置为任何将来的时间，例如从当前时间起 5 分钟。但是，DynamoDB 通常会在过期项目过期后的 48 小时内将其删除，而不是在项目过期后立即删除。

**Topics**
+ [创建一个项目并设置生存时间](#time-to-live-ttl-before-you-start-create)
+ [更新项目并刷新生存时间](#time-to-live-ttl-before-you-start-update)

## 创建一个项目并设置生存时间
<a name="time-to-live-ttl-before-you-start-create"></a>

以下示例演示了如何使用 `expireAt` 作为 TTL 属性名称，来计算创建新项目时的过期时间。赋值语句以变量形式获取当前时间。在示例中，过期时间计算为从当前时间起 90 天。然后将时间转换为纪元格式，并在 TTL 属性中保存为整数数据类型。

以下代码示例展示如何创建设置了 TTL 的项目。

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  

```
package com.amazon.samplelib.ttl;

import com.amazon.samplelib.CodeSampleUtils;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.PutItemResponse;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Creates an item in a DynamoDB table with TTL attributes.
 * This class demonstrates how to add TTL expiration timestamps to DynamoDB items.
 */
public class CreateTTL {

    private static final String USAGE =
        """
            Usage:
                <tableName> <primaryKey> <sortKey> <region>
            Where:
                tableName - The Amazon DynamoDB table being queried.
                primaryKey - The name of the primary key. Also known as the hash or partition key.
                sortKey - The name of the sort key. Also known as the range attribute.
                region (optional) - The AWS region that the Amazon DynamoDB table is located in. (Default: us-east-1)
            """;
    private static final int DAYS_TO_EXPIRE = 90;
    private static final int SECONDS_PER_DAY = 24 * 60 * 60;
    private static final String PRIMARY_KEY_ATTR = "primaryKey";
    private static final String SORT_KEY_ATTR = "sortKey";
    private static final String CREATION_DATE_ATTR = "creationDate";
    private static final String EXPIRE_AT_ATTR = "expireAt";
    private static final String SUCCESS_MESSAGE = "%s PutItem operation with TTL successful.";
    private static final String TABLE_NOT_FOUND_ERROR = "Error: The Amazon DynamoDB table \"%s\" can't be found.";

    private final DynamoDbClient dynamoDbClient;

    /**
     * Constructs a CreateTTL instance with the specified DynamoDB client.
     *
     * @param dynamoDbClient The DynamoDB client to use
     */
    public CreateTTL(final DynamoDbClient dynamoDbClient) {
        this.dynamoDbClient = dynamoDbClient;
    }

    /**
     * Constructs a CreateTTL with a default DynamoDB client.
     */
    public CreateTTL() {
        this.dynamoDbClient = null;
    }

    /**
     * Main method to demonstrate creating an item with TTL.
     *
     * @param args Command line arguments
     */
    public static void main(final String[] args) {
        try {
            int result = new CreateTTL().processArgs(args);
            System.exit(result);
        } catch (Exception e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }

    /**
     * Process command line arguments and create an item with TTL.
     *
     * @param args Command line arguments
     * @return 0 if successful, non-zero otherwise
     * @throws ResourceNotFoundException If the table doesn't exist
     * @throws DynamoDbException If an error occurs during the operation
     * @throws IllegalArgumentException If arguments are invalid
     */
    public int processArgs(final String[] args) {
        // Argument validation (remove or replace this line when reusing this code)
        CodeSampleUtils.validateArgs(args, new int[] {3, 4}, USAGE);

        final String tableName = args[0];
        final String primaryKey = args[1];
        final String sortKey = args[2];
        final Region region = Optional.ofNullable(args.length > 3 ? args[3] : null)
            .map(Region::of)
            .orElse(Region.US_EAST_1);

        try (DynamoDbClient ddb = dynamoDbClient != null
            ? dynamoDbClient
            : DynamoDbClient.builder().region(region).build()) {
            final CreateTTL createTTL = new CreateTTL(ddb);
            createTTL.createItemWithTTL(tableName, primaryKey, sortKey);
            return 0;
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * Creates an item in the specified table with TTL attributes.
     *
     * @param tableName The name of the table
     * @param primaryKeyValue The value for the primary key
     * @param sortKeyValue The value for the sort key
     * @return The response from the PutItem operation
     * @throws ResourceNotFoundException If the table doesn't exist
     * @throws DynamoDbException If an error occurs during the operation
     */
    public PutItemResponse createItemWithTTL(
        final String tableName, final String primaryKeyValue, final String sortKeyValue) {
        // Get current time in epoch second format
        final long createDate = System.currentTimeMillis() / 1000;

        // Calculate expiration time 90 days from now in epoch second format
        final long expireDate = createDate + (DAYS_TO_EXPIRE * SECONDS_PER_DAY);

        final Map<String, AttributeValue> itemMap = new HashMap<>();
        itemMap.put(
            PRIMARY_KEY_ATTR, AttributeValue.builder().s(primaryKeyValue).build());
        itemMap.put(SORT_KEY_ATTR, AttributeValue.builder().s(sortKeyValue).build());
        itemMap.put(
            CREATION_DATE_ATTR,
            AttributeValue.builder().n(String.valueOf(createDate)).build());
        itemMap.put(
            EXPIRE_AT_ATTR,
            AttributeValue.builder().n(String.valueOf(expireDate)).build());

        final PutItemRequest request =
            PutItemRequest.builder().tableName(tableName).item(itemMap).build();

        try {
            final PutItemResponse response = dynamoDbClient.putItem(request);
            System.out.println(String.format(SUCCESS_MESSAGE, tableName));
            return response;
        } catch (ResourceNotFoundException e) {
            System.err.format(TABLE_NOT_FOUND_ERROR, tableName);
            throw e;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            throw e;
        }
    }
}
```
+  有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的 [PutItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/PutItem)。

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  

```
import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";

export function createDynamoDBItem(table_name, region, partition_key, sort_key) {
    const client = new DynamoDBClient({
        region: region,
        endpoint: `https://dynamodb.${region}.amazonaws.com`
    });

    // Get the current time in epoch second format
    const current_time = Math.floor(new Date().getTime() / 1000);

    // Calculate the expireAt time (90 days from now) in epoch second format
    const expire_at = Math.floor((new Date().getTime() + 90 * 24 * 60 * 60 * 1000) / 1000);

    // Create DynamoDB item
    const item = {
        'partitionKey': {'S': partition_key},
        'sortKey': {'S': sort_key},
        'createdAt': {'N': current_time.toString()},
        'expireAt': {'N': expire_at.toString()}
    };

    const putItemCommand = new PutItemCommand({
        TableName: table_name,
        Item: item,
        ProvisionedThroughput: {
            ReadCapacityUnits: 1,
            WriteCapacityUnits: 1,
        },
    });

    client.send(putItemCommand, function(err, data) {
        if (err) {
            console.log("Exception encountered when creating item %s, here's what happened: ", data, err);
            throw err;
        } else {
            console.log("Item created successfully: %s.", data);
            return data;
        }
    });
}

// Example usage (commented out for testing)
// createDynamoDBItem('your-table-name', 'us-east-1', 'your-partition-key-value', 'your-sort-key-value');
```
+  有关 API 详细信息，请参阅《适用于 JavaScript 的 AWS SDK API Reference》**中的 [PutItem](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/PutItemCommand)。

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  

```
from datetime import datetime, timedelta

import boto3


def create_dynamodb_item(table_name, region, primary_key, sort_key):
    """
    Creates a DynamoDB item with an attached expiry attribute.

    :param table_name: Table name for the boto3 resource to target when creating an item
    :param region: string representing the AWS region. Example: `us-east-1`
    :param primary_key: one attribute known as the partition key.
    :param sort_key: Also known as a range attribute.
    :return: Void (nothing)
    """
    try:
        dynamodb = boto3.resource("dynamodb", region_name=region)
        table = dynamodb.Table(table_name)

        # Get the current time in epoch second format
        current_time = int(datetime.now().timestamp())

        # Calculate the expiration time (90 days from now) in epoch second format
        expiration_time = int((datetime.now() + timedelta(days=90)).timestamp())

        item = {
            "primaryKey": primary_key,
            "sortKey": sort_key,
            "creationDate": current_time,
            "expireAt": expiration_time,
        }
        response = table.put_item(Item=item)

        print("Item created successfully.")
        return response
    except Exception as e:
        print(f"Error creating item: {e}")
        raise e


# Use your own values
create_dynamodb_item(
    "your-table-name", "us-west-2", "your-partition-key-value", "your-sort-key-value"
)
```
+  有关 API 详细信息，请参阅《AWS SDK for Python (Boto3) API Reference》**中的 [PutItem](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/PutItem)。

------

## 更新项目并刷新生存时间
<a name="time-to-live-ttl-before-you-start-update"></a>

此示例是[上一节](#time-to-live-ttl-before-you-start-create)所讲示例的延续。如果更新了项目，则可以重新计算过期时间。以下示例将 `expireAt` 时间戳重新计算为自当前时间起 90 天。

以下代码示例演示了如何更新项目的 TTL。

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  
更新表中现有 DynamoDB 项目的 TTL  

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

    public UpdateItemResponse updateItemWithTTL(
        final String tableName, final String primaryKeyValue, final String sortKeyValue) {
        // Get current time in epoch second format
        final long currentTime = System.currentTimeMillis() / 1000;

        // Calculate expiration time 90 days from now in epoch second format
        final long expireDate = currentTime + (DAYS_TO_EXPIRE * SECONDS_PER_DAY);

        // Create the key map for the item to update
        final Map<String, AttributeValue> keyMap = new HashMap<>();
        keyMap.put(PRIMARY_KEY_ATTR, AttributeValue.builder().s(primaryKeyValue).build());
        keyMap.put(SORT_KEY_ATTR, AttributeValue.builder().s(sortKeyValue).build());

        // Create the expression attribute values
        final Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
        expressionAttributeValues.put(
            ":c", AttributeValue.builder().n(String.valueOf(currentTime)).build());
        expressionAttributeValues.put(
            ":e", AttributeValue.builder().n(String.valueOf(expireDate)).build());

        final UpdateItemRequest request = UpdateItemRequest.builder()
            .tableName(tableName)
            .key(keyMap)
            .updateExpression(UPDATE_EXPRESSION)
            .expressionAttributeValues(expressionAttributeValues)
            .build();

        try {
            final UpdateItemResponse response = dynamoDbClient.updateItem(request);
            System.out.println(String.format(SUCCESS_MESSAGE, tableName));
            return response;
        } catch (ResourceNotFoundException e) {
            System.err.format(TABLE_NOT_FOUND_ERROR, tableName);
            throw e;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            throw e;
        }
    }
```
+  有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateItem)。

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  

```
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";

export const updateItem = async (tableName, partitionKey, sortKey, region = 'us-east-1') => {
    const client = new DynamoDBClient({
        region: region,
        endpoint: `https://dynamodb.${region}.amazonaws.com`
    });

    const currentTime = Math.floor(Date.now() / 1000);
    const expireAt = Math.floor((Date.now() + 90 * 24 * 60 * 60 * 1000) / 1000);

    const params = {
        TableName: tableName,
        Key: marshall({
            partitionKey: partitionKey,
            sortKey: sortKey
        }),
        UpdateExpression: "SET updatedAt = :c, expireAt = :e",
        ExpressionAttributeValues: marshall({
            ":c": currentTime,
            ":e": expireAt
        }),
    };

    try {
        const data = await client.send(new UpdateItemCommand(params));
        const responseData = unmarshall(data.Attributes);
        console.log("Item updated successfully: %s", responseData);
        return responseData;
    } catch (err) {
        console.error("Error updating item:", err);
        throw err;
    }
}

// Example usage (commented out for testing)
// updateItem('your-table-name', 'your-partition-key-value', 'your-sort-key-value');
```
+  有关 API 详细信息，请参阅《适用于 JavaScript 的 AWS SDK API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/UpdateItemCommand)。

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  

```
from datetime import datetime, timedelta

import boto3


def update_dynamodb_item(table_name, region, primary_key, sort_key):
    """
    Update an existing DynamoDB item with a TTL.
    :param table_name: Name of the DynamoDB table
    :param region: AWS Region of the table - example `us-east-1`
    :param primary_key: one attribute known as the partition key.
    :param sort_key: Also known as a range attribute.
    :return: Void (nothing)
    """
    try:
        # Create the DynamoDB resource.
        dynamodb = boto3.resource("dynamodb", region_name=region)
        table = dynamodb.Table(table_name)

        # Get the current time in epoch second format
        current_time = int(datetime.now().timestamp())

        # Calculate the expireAt time (90 days from now) in epoch second format
        expire_at = int((datetime.now() + timedelta(days=90)).timestamp())

        table.update_item(
            Key={"partitionKey": primary_key, "sortKey": sort_key},
            UpdateExpression="set updatedAt=:c, expireAt=:e",
            ExpressionAttributeValues={":c": current_time, ":e": expire_at},
        )

        print("Item updated successfully.")
    except Exception as e:
        print(f"Error updating item: {e}")


# Replace with your own values
update_dynamodb_item(
    "your-table-name", "us-west-2", "your-partition-key-value", "your-sort-key-value"
)
```
+  有关 API 详细信息，请参阅《AWS SDK for Python（Boto3）API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/UpdateItem)。

------

本简介中讨论的 TTL 示例演示了一种确保表中仅保留最近更新的项目的方法。更新的项目会延长寿命，而创建后未更新的项目将过期并被免费删除，从而减少存储空间并保持表整洁。

# 使用过期项目和生存时间（TTL）
<a name="ttl-expired-items"></a>

可以通过读取和写入操作筛选待删除的过期项目。这在过期数据不再有效且不会使用的情况下很有用。如果未将其筛选出来，它们将继续显示在读取和写入操作中，直到它们被后台进程删除。

**注意**  
这些项目在被删除之前仍会计入存储和读取费用中。

可以在 DynamoDB Streams 中识别 TTL 删除，但只能在执行删除的区域中识别。对于删除复制到的区域，复制到全局表区域的 TTL 删除在 DynamoDB Streams 中无法识别。

## 从读取操作中筛选过期项目
<a name="ttl-expired-items-filter"></a>

对于诸如[扫描](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)和[查询](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html)之类的读取操作，筛选表达式可以筛选出待删除的过期项目。如下面的代码段所示，筛选表达式可以筛选出 TTL 时间等于或小于当前时间的项目。例如，Python SDK 代码包含一个赋值语句，该语句将当前时间作为变量（`now`）获取，并将其转换为纪元时间格式 `int`。

以下代码示例演示了如何查询 TTL 项目。

------
#### [ Java ]

**SDK for Java 2.x**  
查询对表达式进行了筛选，以使用AWS SDK for Java 2.x 在 DynamoDB 表中收集 TTL 项目。  

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;

import java.util.Map;
import java.util.Optional;

        final QueryRequest request = QueryRequest.builder()
            .tableName(tableName)
            .keyConditionExpression(KEY_CONDITION_EXPRESSION)
            .filterExpression(FILTER_EXPRESSION)
            .expressionAttributeNames(expressionAttributeNames)
            .expressionAttributeValues(expressionAttributeValues)
            .build();

        try (DynamoDbClient ddb = dynamoDbClient != null
            ? dynamoDbClient
            : DynamoDbClient.builder().region(region).build()) {
            final QueryResponse response = ddb.query(request);
            System.out.println("Query successful. Found " + response.count() + " items that have not expired yet.");

            // Print each item
            response.items().forEach(item -> {
                System.out.println("Item: " + item);
            });

            return 0;
        } catch (ResourceNotFoundException e) {
            System.err.format(TABLE_NOT_FOUND_ERROR, tableName);
            throw e;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            throw e;
        }
```
+  有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的 [Query](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/Query)。

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  
查询对表达式进行了筛选，以使用适用于 JavaScript 的 AWS SDK 在 DynamoDB 表中收集 TTL 项目。  

```
import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";

export const queryFiltered = async (tableName, primaryKey, region = 'us-east-1') => {
    const client = new DynamoDBClient({
        region: region,
        endpoint: `https://dynamodb.${region}.amazonaws.com`
    });

    const currentTime = Math.floor(Date.now() / 1000);

    const params = {
        TableName: tableName,
        KeyConditionExpression: "#pk = :pk",
        FilterExpression: "#ea > :ea",
        ExpressionAttributeNames: {
            "#pk": "primaryKey",
            "#ea": "expireAt"
        },
        ExpressionAttributeValues: marshall({
            ":pk": primaryKey,
            ":ea": currentTime
        })
    };

    try {
        const { Items } = await client.send(new QueryCommand(params));
        Items.forEach(item => {
            console.log(unmarshall(item))
        });
        return Items;
    } catch (err) {
        console.error(`Error querying items: ${err}`);
        throw err;
    }
}

// Example usage (commented out for testing)
// queryFiltered('your-table-name', 'your-partition-key-value');
```
+  有关 API 详细信息，请参阅《适用于 JavaScript 的 AWS SDK API Reference》**中的 [Query](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/QueryCommand)。

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  
查询对表达式进行了筛选，以使用适用于 Python (Boto3) 的 AWS SDK 在 DynamoDB 表中收集 TTL 项目。  

```
from datetime import datetime

import boto3


def query_dynamodb_items(table_name, partition_key):
    """

    :param table_name: Name of the DynamoDB table
    :param partition_key:
    :return:
    """
    try:
        # Initialize a DynamoDB resource
        dynamodb = boto3.resource("dynamodb", region_name="us-east-1")

        # Specify your table
        table = dynamodb.Table(table_name)

        # Get the current time in epoch format
        current_time = int(datetime.now().timestamp())

        # Perform the query operation with a filter expression to exclude expired items
        # response = table.query(
        #    KeyConditionExpression=boto3.dynamodb.conditions.Key('partitionKey').eq(partition_key),
        #    FilterExpression=boto3.dynamodb.conditions.Attr('expireAt').gt(current_time)
        # )
        response = table.query(
            KeyConditionExpression=dynamodb.conditions.Key("partitionKey").eq(partition_key),
            FilterExpression=dynamodb.conditions.Attr("expireAt").gt(current_time),
        )

        # Print the items that are not expired
        for item in response["Items"]:
            print(item)

    except Exception as e:
        print(f"Error querying items: {e}")


# Call the function with your values
query_dynamodb_items("Music", "your-partition-key-value")
```
+  有关 API 详细信息，请参阅《AWS SDK for Python (Boto3) API Reference》**中的 [Query](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/Query)。

------

## 有条件地写入过期项目
<a name="ttl-expired-items-conditional-write"></a>

条件表达式可用于避免写入过期项目。下面的代码段是一个有条件的更新，用于检查过期时间是否大于当前时间。如果为 true，则写入操作将继续。

以下代码示例演示了如何有条件地更新项目的 TTL。

------
#### [ Java ]

**适用于 Java 的 SDK 2.x**  
使用条件更新表中现有 DynamoDB 项目的 TTL。  

```
package com.amazon.samplelib.ttl;

import com.amazon.samplelib.CodeSampleUtils;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;

import java.util.Map;
import java.util.Optional;

/**
 * Updates an item in a DynamoDB table with TTL attributes using a conditional expression.
 * This class demonstrates how to conditionally update TTL expiration timestamps.
 */
public class UpdateTTLConditional {

    private static final String USAGE =
        """
            Usage:
                <tableName> <primaryKey> <sortKey> <region>
            Where:
                tableName - The Amazon DynamoDB table being queried.
                primaryKey - The name of the primary key. Also known as the hash or partition key.
                sortKey - The name of the sort key. Also known as the range attribute.
                region (optional) - The AWS region that the Amazon DynamoDB table is located in. (Default: us-east-1)
            """;
    private static final int DAYS_TO_EXPIRE = 90;
    private static final int SECONDS_PER_DAY = 24 * 60 * 60;
    private static final String PRIMARY_KEY_ATTR = "primaryKey";
    private static final String SORT_KEY_ATTR = "sortKey";
    private static final String UPDATED_AT_ATTR = "updatedAt";
    private static final String EXPIRE_AT_ATTR = "expireAt";
    private static final String UPDATE_EXPRESSION = "SET " + UPDATED_AT_ATTR + "=:c, " + EXPIRE_AT_ATTR + "=:e";
    private static final String CONDITION_EXPRESSION = "attribute_exists(" + PRIMARY_KEY_ATTR + ")";
    private static final String SUCCESS_MESSAGE = "%s UpdateItem operation with TTL successful.";
    private static final String CONDITION_FAILED_MESSAGE = "Condition check failed. Item does not exist.";
    private static final String TABLE_NOT_FOUND_ERROR = "Error: The Amazon DynamoDB table \"%s\" can't be found.";

    private final DynamoDbClient dynamoDbClient;

    /**
     * Constructs an UpdateTTLConditional with a default DynamoDB client.
     */
    public UpdateTTLConditional() {
        this.dynamoDbClient = null;
    }

    /**
     * Constructs an UpdateTTLConditional with the specified DynamoDB client.
     *
     * @param dynamoDbClient The DynamoDB client to use
     */
    public UpdateTTLConditional(final DynamoDbClient dynamoDbClient) {
        this.dynamoDbClient = dynamoDbClient;
    }

    /**
     * Main method to demonstrate conditionally updating an item with TTL.
     *
     * @param args Command line arguments
     */
    public static void main(final String[] args) {
        try {
            int result = new UpdateTTLConditional().processArgs(args);
            System.exit(result);
        } catch (Exception e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }

    /**
     * Process command line arguments and conditionally update an item with TTL.
     *
     * @param args Command line arguments
     * @return 0 if successful, non-zero otherwise
     * @throws ResourceNotFoundException If the table doesn't exist
     * @throws DynamoDbException If an error occurs during the operation
     * @throws IllegalArgumentException If arguments are invalid
     */
    public int processArgs(final String[] args) {
        // Argument validation (remove or replace this line when reusing this code)
        CodeSampleUtils.validateArgs(args, new int[] {3, 4}, USAGE);

        final String tableName = args[0];
        final String primaryKey = args[1];
        final String sortKey = args[2];
        final Region region = Optional.ofNullable(args.length > 3 ? args[3] : null)
            .map(Region::of)
            .orElse(Region.US_EAST_1);

        // Get current time in epoch second format
        final long currentTime = System.currentTimeMillis() / 1000;

        // Calculate expiration time 90 days from now in epoch second format
        final long expireDate = currentTime + (DAYS_TO_EXPIRE * SECONDS_PER_DAY);

        // Create the key map for the item to update
        final Map<String, AttributeValue> keyMap = Map.of(
            PRIMARY_KEY_ATTR, AttributeValue.builder().s(primaryKey).build(),
            SORT_KEY_ATTR, AttributeValue.builder().s(sortKey).build());

        // Create the expression attribute values
        final Map<String, AttributeValue> expressionAttributeValues = Map.of(
            ":c", AttributeValue.builder().n(String.valueOf(currentTime)).build(),
            ":e", AttributeValue.builder().n(String.valueOf(expireDate)).build());

        final UpdateItemRequest request = UpdateItemRequest.builder()
            .tableName(tableName)
            .key(keyMap)
            .updateExpression(UPDATE_EXPRESSION)
            .conditionExpression(CONDITION_EXPRESSION)
            .expressionAttributeValues(expressionAttributeValues)
            .build();

        try (DynamoDbClient ddb = dynamoDbClient != null
            ? dynamoDbClient
            : DynamoDbClient.builder().region(region).build()) {
            final UpdateItemResponse response = ddb.updateItem(request);
            System.out.println(String.format(SUCCESS_MESSAGE, tableName));
            return 0;
        } catch (ConditionalCheckFailedException e) {
            System.err.println(CONDITION_FAILED_MESSAGE);
            throw e;
        } catch (ResourceNotFoundException e) {
            System.err.format(TABLE_NOT_FOUND_ERROR, tableName);
            throw e;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            throw e;
        }
    }
}
```
+  有关 API 详细信息，请参阅《AWS SDK for Java 2.x API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateItem)。

------
#### [ JavaScript ]

**SDK for JavaScript（v3）**  
使用条件更新表中现有 DynamoDB 项目的 TTL。  

```
import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";

export const updateItemConditional = async (tableName, partitionKey, sortKey, region = 'us-east-1', newAttribute = 'default-value') => {
    const client = new DynamoDBClient({
        region: region,
        endpoint: `https://dynamodb.${region}.amazonaws.com`
    });

    const currentTime = Math.floor(Date.now() / 1000);

    const params = {
        TableName: tableName,
        Key: marshall({
            artist: partitionKey,
            album: sortKey
        }),
        UpdateExpression: "SET newAttribute = :newAttribute",
        ConditionExpression: "expireAt > :expiration",
        ExpressionAttributeValues: marshall({
            ':newAttribute': newAttribute,
            ':expiration': currentTime
        }),
        ReturnValues: "ALL_NEW"
    };

    try {
        const response = await client.send(new UpdateItemCommand(params));
        const responseData = unmarshall(response.Attributes);
        console.log("Item updated successfully: ", responseData);
        return responseData;
    } catch (error) {
        if (error.name === "ConditionalCheckFailedException") {
            console.log("Condition check failed: Item's 'expireAt' is expired.");
        } else {
            console.error("Error updating item: ", error);
        }
        throw error;
    }
};

// Example usage (commented out for testing)
// updateItemConditional('your-table-name', 'your-partition-key-value', 'your-sort-key-value');
```
+  有关 API 详细信息，请参阅《适用于 JavaScript 的 AWS SDK API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/UpdateItemCommand)。

------
#### [ Python ]

**适用于 Python 的 SDK（Boto3）**  
使用条件更新表中现有 DynamoDB 项目的 TTL。  

```
from datetime import datetime, timedelta

import boto3
from botocore.exceptions import ClientError


def update_dynamodb_item_ttl(table_name, region, primary_key, sort_key, ttl_attribute):
    """
    Updates an existing record in a DynamoDB table with a new or updated TTL attribute.

    :param table_name: Name of the DynamoDB table
    :param region: AWS Region of the table - example `us-east-1`
    :param primary_key: one attribute known as the partition key.
    :param sort_key: Also known as a range attribute.
    :param ttl_attribute: name of the TTL attribute in the target DynamoDB table
    :return:
    """
    try:
        dynamodb = boto3.resource("dynamodb", region_name=region)
        table = dynamodb.Table(table_name)

        # Generate updated TTL in epoch second format
        updated_expiration_time = int((datetime.now() + timedelta(days=90)).timestamp())

        # Define the update expression for adding/updating a new attribute
        update_expression = "SET newAttribute = :val1"

        # Define the condition expression for checking if 'expireAt' is not expired
        condition_expression = "expireAt > :val2"

        # Define the expression attribute values
        expression_attribute_values = {":val1": ttl_attribute, ":val2": updated_expiration_time}

        response = table.update_item(
            Key={"primaryKey": primary_key, "sortKey": sort_key},
            UpdateExpression=update_expression,
            ConditionExpression=condition_expression,
            ExpressionAttributeValues=expression_attribute_values,
        )

        print("Item updated successfully.")
        return response["ResponseMetadata"]["HTTPStatusCode"]  # Ideally a 200 OK
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            print("Condition check failed: Item's 'expireAt' is expired.")
        else:
            print(f"Error updating item: {e}")
    except Exception as e:
        print(f"Error updating item: {e}")


# replace with your values
update_dynamodb_item_ttl(
    "your-table-name",
    "us-east-1",
    "your-partition-key-value",
    "your-sort-key-value",
    "your-ttl-attribute-value",
)
```
+  有关 API 详细信息，请参阅《AWS SDK for Python（Boto3）API Reference》**中的 [UpdateItem](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/UpdateItem)。

------

## 识别 DynamoDB Streams 中已删除的项目
<a name="ttl-expired-items-identifying"></a>

流记录包含用户身份字段`Records[<index>].userIdentity`。被 TTL 过程删除的项目包含以下字段：

```
Records[<index>].userIdentity.type
"Service"

Records[<index>].userIdentity.principalId
"dynamodb.amazonaws.com"
```

以下 JSON 显示单个流记录的相关部分：

```
"Records": [ 
  { 
	... 
		"userIdentity": {
		"type": "Service", 
      	"principalId": "dynamodb.amazonaws.com" 
   	} 
   ... 
	} 
]
```

# 在 DynamoDB 中查询表
<a name="Query"></a>

您可以使用 Amazon DynamoDB 中的 `Query` API 操作基于主键值查找项目。

您必须提供分区键属性的名称以及该属性的一个值。`Query` 将返回具有该分区键值的所有项目。（可选）您可以提供排序键属性，并使用比较运算符来细化搜索结果。

有关如何使用 `Query` 的更多信息，例如请求语法、响应参数和其他示例，请参阅《Amazon DynamoDB API 参考》**中的[查询](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html)。

**Topics**
+ [DynamoDB 中的查询操作的键条件表达式](Query.KeyConditionExpressions.md)
+ [DynamoDB 中的查询操作的筛选表达式](Query.FilterExpression.md)
+ [在 DynamoDB 中对表查询结果分页](Query.Pagination.md)
+ [在 DynamoDB 中使用查询操作的其他分面](Query.Other.md)

# DynamoDB 中的查询操作的键条件表达式
<a name="Query.KeyConditionExpressions"></a>

您可以在键条件表达式中使用任意属性名称，前提是第一个字符是 `a-z` 或 `A-Z`，其余字符（从第二个字符开始，如果存在）为 `a-z`、`A-Z` 或 `0-9`。此外，属性名称不得为 DynamoDB 保留字。（有关这些保留关键字的完整列表，请参阅[DynamoDB 中的保留字](ReservedWords.md)。） 如果属性名称不满足这些要求，则您必须将表达式属性名称定义为占位符。有关更多信息，请参阅 [DynamoDB 中的表达式属性名称（别名）](Expressions.ExpressionAttributeNames.md)。

对于具有给定分区键值的项目，DynamoDB 会将这些项目存储在紧邻位置并按照排序键值对其进行排序。在 `Query` 操作中，DynamoDB 按照排序顺序检索项目，然后使用 `KeyConditionExpression` 和可能存在的任何 `FilterExpression` 处理项目。只有在此时才会将 `Query` 结果发送回客户端。

`Query` 操作始终返回结果集。如果未找到匹配的项目，结果集将为空。

`Query` 结果始终按排序键值排序。如果排序键的数据类型为 `Number`，则按照数值顺序返回结果。否则，按照 UTF-8 字节的顺序返回结果。默认情况下，系统按升序排序。要颠倒顺序，请将 `ScanIndexForward` 参数设置为 `false`。

单个 `Query` 操作最多可检索 1 MB 的数据。在向结果应用任何 `FilterExpression` 或 `ProjectionExpression` 之前，将应用此限制。如果 `LastEvaluatedKey` 包含在响应中且为非 null 值，则您必须为结果集分页（请参阅[在 DynamoDB 中对表查询结果分页](Query.Pagination.md)）。

## 键条件表达式示例
<a name="Query.KeyConditionExpressions-example"></a>

要指定搜索条件，请使用*键条件表达式*—用于确定要从表或索引中读取的项目的字符串。

您必须指定分区键名称和值作为等式条件。无法在键条件表达式中使用非键属性。

您可选择为排序键提供另一个条件（如果有）。排序键条件必须使用下列比较运算符之一：
+ `a = b` — 如果属性 *a* 等于值 *b*，则为 true
+ `a < b` — 如果 *a* 小于 *b*，则为 true
+ `a <= b` — 如果 *a* 小于等于 *b*，则为 true
+ `a > b` — 如果 *a* 大于 *b*，则为 true
+ `a >= b` — 如果 *a* 大于等于 *b*，则为 true
+ `a BETWEEN b AND c` — 如果 *a* 大于或等于 *b*，且小于或等于 *c*，则为 true。

以下函数也受支持：
+ `begins_with (a, substr)` — 如果属性 `a` 的值以特定子字符串开头，则为 true。

以下 AWS Command Line Interface (AWS CLI) 示例将演示键条件表达式的用法。这些表达式使用占位符（例如 `:name` 和 `:sub`）而不是实际的值。有关更多信息，请参阅[DynamoDB 中的表达式属性名称（别名）](Expressions.ExpressionAttributeNames.md)和[在 DynamoDB 中使用表达式属性值](Expressions.ExpressionAttributeValues.md)。

**Example**  
在 `Thread` 表中查询特定的 `ForumName`（分区键）。具有 `ForumName` 值的所有项目将由查询进行读取，因为排序键 (`Subject`) 未包括在 `KeyConditionExpression` 中。  

```
aws dynamodb query \
    --table-name Thread \
    --key-condition-expression "ForumName = :name" \
    --expression-attribute-values  '{":name":{"S":"Amazon DynamoDB"}}'
```

**Example**  
在 `Thread` 表中查询特定的 `ForumName`（分区键），但这一次仅返回具有给定 `Subject`（排序键）的项目。  

```
aws dynamodb query \
    --table-name Thread \
    --key-condition-expression "ForumName = :name and Subject = :sub" \
    --expression-attribute-values  file://values.json
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":name":{"S":"Amazon DynamoDB"},
    ":sub":{"S":"DynamoDB Thread 1"}
}
```

**Example**  
在 `Reply` 表中查询特定的 `Id`（分区键），但仅返回其 `ReplyDateTime`（排序键）以特定字符开头的项目。  

```
aws dynamodb query \
    --table-name Reply \
    --key-condition-expression "Id = :id and begins_with(ReplyDateTime, :dt)" \
    --expression-attribute-values  file://values.json
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":id":{"S":"Amazon DynamoDB#DynamoDB Thread 1"},
    ":dt":{"S":"2015-09"}
}
```

# DynamoDB 中的查询操作的筛选表达式
<a name="Query.FilterExpression"></a>

如果您需要进一步细化 `Query` 结果，则可以选择性地提供筛选表达式。*筛选表达式*可确定 `Query` 结果中应返回给您的项目。所有其他结果将会丢弃。

筛选表达式在 `Query` 已完成但结果尚未返回时应用。因此，无论是否存在筛选表达式，`Query` 都将占用同等数量的读取容量。

`Query` 操作最多可检索 1 MB 的数据。此限制在计算筛选表达式之前应用。

筛选表达式不得包含分区键或排序键属性。您需要在关键字条件表达式而不是筛选表达式中指定这些属性。

筛选表达式的语法与关键条件表达式的语法相似。筛选表达式可使用的比较运算符、函数和逻辑运算符与关键条件表达式可使用的相同。此外，筛选表达式可以使用不等于运算符 (`<>`)、`OR` 运算符、`CONTAINS` 运算符、`IN` 运算符、`BEGINS_WITH` 运算符、`BETWEEN` 运算符、`EXISTS` 运算符和 `SIZE` 运算符。有关更多信息，请参阅[DynamoDB 中的查询操作的键条件表达式](Query.KeyConditionExpressions.md)和[筛选条件和条件表达式的语法](Expressions.OperatorsAndFunctions.md#Expressions.OperatorsAndFunctions.Syntax)。

**Example**  
以下 AWS CLI 示例在 `Thread` 表中查询特定 `ForumName`（分区键）和 `Subject`（排序键）。在找到的项目中，只返回最常用的讨论线程，换句话说，只有那些具有超过一定数量 `Views` 的线程。  

```
aws dynamodb query \
    --table-name Thread \
    --key-condition-expression "ForumName = :fn and Subject begins_with :sub" \
    --filter-expression "#v >= :num" \
    --expression-attribute-names '{"#v": "Views"}' \
    --expression-attribute-values file://values.json
```
`--expression-attribute-values` 的参数存储在 `values.json` 文件中。  

```
{
    ":fn":{"S":"Amazon DynamoDB"},
    ":sub":{"S":"DynamoDB Thread 1"},
    ":num":{"N":"3"}
}
```
请注意，`Views` 在 DynamoDB 中是一个保留字（请参阅 [DynamoDB 中的保留字](ReservedWords.md)），因此本示例使用 `#v` 作为占位符。有关更多信息，请参阅 [DynamoDB 中的表达式属性名称（别名）](Expressions.ExpressionAttributeNames.md)。

**注意**  
筛选表达式将从 `Query` 结果集中删除项目。在您预计会检索到大量项目并且还需要丢弃其中大多数项目的情况下，请尽量避免使用 `Query`。

# 在 DynamoDB 中对表查询结果分页
<a name="Query.Pagination"></a>

DynamoDB *分页*来自 `Query` 操作的结果。利用分页，`Query` 结果将分成若干“页”大小为 1 MB（或更小）的数据。应用程序可以先处理第一页结果，然后处理第二页结果，依此类推。

单次 `Query` 只会返回符合 1 MB 大小限制的结果集。要确定是否存在更多结果，并一次检索一页结果，应用程序应执行以下操作：

1. 检查低级别 `Query` 结果：
   + 如果结果包含 `LastEvaluatedKey` 元素并且非空，请继续步骤 2。
   + 如果结果中*没有* `LastEvaluatedKey`，则表示没有其他要检索的项目。

1. 使用相同的 `KeyConditionExpression` 构造 `Query`。但是，此次获取来自步骤 1 的 `LastEvaluatedKey` 值，并将其用作新 `ExclusiveStartKey` 请求中的 `Query` 参数。

1. 运行新的 `Query` 请求。

1. 前往步骤 1。

换言之，`LastEvaluatedKey` 响应中的 `Query` 应该用作下一 `ExclusiveStartKey` 请求的 `Query`。如果 `LastEvaluatedKey` 响应中没有 `Query` 元素，则表示您已检索最后一页结果。如果 `LastEvaluatedKey` 不为空，并不一定意味着结果集中有更多数据。检查 `LastEvaluatedKey` 是否为空是确定您是否已到达结果集末尾的唯一方式。

您可以使用 AWS CLI 查看此行为。AWS CLI 向 DynamoDB 反复发送低级别 `Query` 请求，直到请求中不再有 `LastEvaluatedKey`。考虑以下 AWS CLI 示例，此示例检索特定年份的电影标题。

```
aws dynamodb query --table-name Movies \
    --projection-expression "title" \
    --key-condition-expression "#y = :yyyy" \
    --expression-attribute-names '{"#y":"year"}' \
    --expression-attribute-values '{":yyyy":{"N":"1993"}}' \
    --page-size 5 \
    --debug
```

通常，AWS CLI 自动处理分页。但是，在此例中，AWS CLI `--page-size` 参数限制了每页项目数。`--debug` 参数输出有关请求和响应的低级别信息。

如果您运行该示例，DynamoDB 的首次响应类似如下内容。

```
2017-07-07 11:13:15,603 - MainThread - botocore.parsers - DEBUG - Response body:
b'{"Count":5,"Items":[{"title":{"S":"A Bronx Tale"}},
{"title":{"S":"A Perfect World"}},{"title":{"S":"Addams Family Values"}},
{"title":{"S":"Alive"}},{"title":{"S":"Benny & Joon"}}],
"LastEvaluatedKey":{"year":{"N":"1993"},"title":{"S":"Benny & Joon"}},
"ScannedCount":5}'
```

响应中的 `LastEvaluatedKey` 指示并未检索所有项目。随后，AWS CLI 会将另一个 `Query` 请求发送到 DynamoDB。此请求和响应模式继续，直到收到最终响应。

```
2017-07-07 11:13:16,291 - MainThread - botocore.parsers - DEBUG - Response body:
b'{"Count":1,"Items":[{"title":{"S":"What\'s Eating Gilbert Grape"}}],"ScannedCount":1}'
```

如果不存在 `LastEvaluatedKey`，则表示没有其他要检索的项目。

**注意**  
AWS SDK 处理低级别的 DynamoDB 响应（包括是否存在 `LastEvaluatedKey`），并提供各种抽象概念对 `Query` 结果进行分页。例如，适用于 Java 的 SDK 文档接口提供 `java.util.Iterator` 支持，以便您能够一次处理一个结果。  
对于各种编程语言的代码示例，请参阅本地化的 [Amazon DynamoDB 入门指南](https://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/)和 AWS SDK 文档。

此外，您还可以通过使用 `Query` 操作的 `Limit` 参数来限制结果集中的项目数，以此减少页面大小。

有关用 DynamoDB 查询的更多信息，请参阅[在 DynamoDB 中查询表](Query.md)。

# 在 DynamoDB 中使用查询操作的其他分面
<a name="Query.Other"></a>

此部分介绍了 DynamoDB 查询操作的其他方面，包括限制结果大小、计算已扫描项目与已返回项目的数量、监控读取容量消耗以及控制读取一致性。

## 限制结果集中的项目数
<a name="Query.Limit"></a>

使用 `Query` 操作，您可以限制它读取的项目数。为此，请将 `Limit` 参数设置为您需要的最大项目数。

例如，假设您对某个表进行 `Query`，`Limit` 值为 `6`，并且没有筛选表达式。`Query` 结果将包含表中与请求中的键条件表达式匹配的前 6 个项目。

现在假设您向 `Query` 添加了一个筛选表达式。在这种情况下，DynamoDB 最多可读取六个项目，然后仅返回与筛选表达式匹配的项目。最终 `Query` 结果包含六个或更少的项目，即使更多项目（如果 DynamoDB 继续读取更多项目）与过滤表达式匹配，也是如此。

## 对结果中的项目进行计数
<a name="Query.Count"></a>

除了与您的条件匹配的项目之外，`Query` 响应还包含以下元素：
+ `ScannedCount` — 在应用筛选表达式（如果有）*之前*，与关键字条件表达式匹配的项目的数量。
+ `Count` — 在应用筛选表达式（如果有）*之后*，剩余的项目数量。

**注意**  
如果您不使用筛选表达式，那么 `ScannedCount` 和 `Count` 具有相同的值。

如果 `Query` 结果集的大小大于 1 MB，则 `ScannedCount` 和 `Count` 将仅表示项目总数的部分计数。您需要执行多次 `Query` 操作才能检索所有结果（请参阅[在 DynamoDB 中对表查询结果分页](Query.Pagination.md)）。

所有 `Query` 响应都将包含由该特定 `ScannedCount` 请求处理的项目的 `Count` 和 `Query`。要获取所有 `Query` 请求的总和，您可以对 `ScannedCount` 和 `Count` 记录流水账。

## 查询占用的容量单位
<a name="Query.CapacityUnits"></a>

您可以对任何表或二级索引进行 `Query`，只要您提供分区键属性的名称和该属性的单个值即可。`Query` 返回具有该分区键值的所有项目。（可选）您可以提供排序键属性，并使用比较运算符来细化搜索结果。`Query`API 操作将消耗读取容量单位，如下所示。


****  

| 如果对...进行 `Query` | DynamoDB 将占用...的读取容量单位 | 
| --- | --- | 
| 表 | 表的预置读取容量。 | 
| 全局二级索引 | 索引的预置读取容量。 | 
| 本地二级索引 | 基表的预置读取容量。 | 

默认情况下，`Query` 操作不会返回任何有关它占用的读取容量大小的数据。但是，您可在 `ReturnConsumedCapacity` 请求中指定 `Query` 参数以获取此信息。下面是 `ReturnConsumedCapacity` 的有效设置：
+ `NONE` — 不返回任何已占用容量数据。（这是默认值。）
+ `TOTAL` — 响应包含占用的读取容量单位的总数。
+ `INDEXES` — 响应显示占用的读取容量单位的总数，以及访问的每个表和索引的占用容量。

DynamoDB 将基于项目数量以及这些项目的大小，而不是基于返回到应用程序的数据量来计算消耗的读取容量单位数。因此，无论您是请求所有属性（默认行为）还是只请求部分属性（使用投影表达式），占用的容量单位数都是相同的。无论您是否使用筛选表达式，该数值也都是一样的。`Query` 使用最小的读取容量单位，来为高达 4 KB 的项目每秒执行一次强一致性读取，或每秒执行两次最终一致性读取。如果您需要读取大于 4KB 的项目，DynamoDB 需要额外的读取请求单位。空表和分区键数量稀疏的超大表，可能会有在超出查询的数据量后对一些额外的 RCU 收费的情况。这包括处理 `Query` 请求的费用，即使不存在数据也是如此。

## 查询的读取一致性
<a name="Query.ReadConsistency"></a>

默认情况下，`Query` 操作将执行最终一致性读取。这意味着 `Query` 结果可能无法反映由最近完成的 `PutItem` 或 `UpdateItem` 操作导致的更改。有关更多信息，请参阅 [DynamoDB 读取一致性](HowItWorks.ReadConsistency.md)。

如果您需要强一致性读取，请在 `ConsistentRead` 请求中将 `true` 参数设置为 `Query`。

# 在 DynamoDB 中扫描表
<a name="Scan"></a>

Amazon DynamoDB 中的 `Scan` 操作读取表或二级索引中的每个项目。默认情况下，`Scan` 操作返回表或索引中每个项目的全部数据属性。您可以使用 `ProjectionExpression` 参数，以便 `Scan` 仅返回部分属性而不是全部属性。

`Scan` 始终返回结果集。如果未找到匹配的项目，结果集将为空。

单个 `Scan` 请求最多可检索 1 MB 数据。（可选）DynamoDB 可以向这些数据应用筛选表达式，从而在将数据返回给用户之前缩小结果范围。

**Topics**
+ [扫描的筛选表达式](#Scan.FilterExpression)
+ [限制结果集中的项目数](#Scan.Limit)
+ [对结果分页](#Scan.Pagination)
+ [对结果中的项目进行计数](#Scan.Count)
+ [扫描占用的容量单位](#Scan.CapacityUnits)
+ [扫描的读取一致性](#Scan.ReadConsistency)
+ [并行扫描](#Scan.ParallelScan)

## 扫描的筛选表达式
<a name="Scan.FilterExpression"></a>

如果您需要进一步细化 `Scan` 结果，则可以选择性地提供筛选表达式。*筛选表达式*可确定 `Scan` 结果中应返回给您的项目。所有其他结果将会丢弃。

筛选表达式在 `Scan` 已完成但结果尚未返回时应用。因此，无论是否存在筛选表达式，`Scan` 都将占用同等数量的读取容量。

`Scan` 操作最多可检索 1 MB 的数据。此限制在计算筛选表达式之前应用。

使用 `Scan`，您可以在筛选表达式中指定任何属性，包括分区键和排序键属性。

筛选表达式的语法与条件表达式的相同。筛选表达式可使用的比较运算符、函数和逻辑运算符与条件表达式可使用的相同。有关逻辑运算符的更多信息，请参阅 [DynamoDB 中的条件表达式和筛选表达式、运算符及函数](Expressions.OperatorsAndFunctions.md)。

**Example**  
以下 AWS Command Line Interface (AWS CLI) 示例将扫描 `Thread` 表，此时仅返回此表中由特定用户最新发布到的项目。  

```
aws dynamodb scan \
     --table-name Thread \
     --filter-expression "LastPostedBy = :name" \
     --expression-attribute-values '{":name":{"S":"User A"}}'
```

## 限制结果集中的项目数
<a name="Scan.Limit"></a>

`Scan` 操作可让您限制结果中返回的项目数。为此，将 `Limit` 参数设置为您在筛选条件表达式求值前希望 `Scan` 操作返回的最大项目数。

例如，假设您对某个表进行 `Scan`，`Limit` 值为 `6`，并且没有筛选表达式。`Scan` 结果包含表中的前 6 个项目。

现在假设您向 `Scan` 添加了一个筛选表达式。在这种情况下，DynamoDB 将向返回的前 6 个项目应用筛选表达式，不考虑不匹配的项目。最终的 `Scan` 结果将包含 6 个或更少的项目，具体取决于筛选出的项目数。

## 对结果分页
<a name="Scan.Pagination"></a>

DynamoDB *分页*来自 `Scan` 操作的结果。利用分页，`Scan` 结果将分成若干“页”大小为 1 MB（或更小）的数据。应用程序可以先处理第一页结果，然后处理第二页结果，依此类推。

单次 `Scan` 只会返回符合 1 MB 大小限制的结果集。

要确定是否存在更多结果，并一次检索一页结果，应用程序应执行以下操作：

1. 检查低级别 `Scan` 结果：
   + 如果结果包含 `LastEvaluatedKey` 元素，请继续步骤 2。
   + 如果结果中*没有* `LastEvaluatedKey`，则表示没有其他要检索的项目。

1. 构造新的 `Scan` 请求，使用与前一个请求相同的参数。但是，此次获取来自步骤 1 的 `LastEvaluatedKey` 值，并将其用作新 `ExclusiveStartKey` 请求中的 `Scan` 参数。

1. 运行新的 `Scan` 请求。

1. 前往步骤 1。

换言之，`LastEvaluatedKey` 响应中的 `Scan` 应该用作下一 `ExclusiveStartKey` 请求的 `Scan`。如果 `LastEvaluatedKey` 响应中没有 `Scan` 元素，则表示您已检索最后一页结果。（检查是否没有 `LastEvaluatedKey` 是确定您是否已到达结果集末尾的唯一方式。）

您可以使用 AWS CLI 查看此行为。AWS CLI 向 DynamoDB 反复发送低级别 `Scan` 请求，直到请求中不再有 `LastEvaluatedKey`。请考虑以下 AWS CLI 示例，它扫描整个 `Movies` 表，但仅返回特定流派的电影。

```
aws dynamodb scan \
    --table-name Movies \
    --projection-expression "title" \
    --filter-expression 'contains(info.genres,:gen)' \
    --expression-attribute-values '{":gen":{"S":"Sci-Fi"}}' \
    --page-size 100  \
    --debug
```

通常，AWS CLI 自动处理分页。但是，在此例中，AWS CLI `--page-size` 参数限制了每页项目数。`--debug` 参数输出有关请求和响应的低级别信息。

**注意**  
根据您传递的输入参数，您的分页结果也会有所不同。  
使用 `aws dynamodb scan --table-name Prices --max-items 1` 返回 `NextToken`
使用 `aws dynamodb scan --table-name Prices --limit 1` 返回 `LastEvaluatedKey`。
另请注意，使用 `--starting-token` 特别要求 `NextToken` 值。

如果您运行该示例，DynamoDB 的首次响应类似如下内容。

```
2017-07-07 12:19:14,389 - MainThread - botocore.parsers - DEBUG - Response body:
b'{"Count":7,"Items":[{"title":{"S":"Monster on the Campus"}},{"title":{"S":"+1"}},
{"title":{"S":"100 Degrees Below Zero"}},{"title":{"S":"About Time"}},{"title":{"S":"After Earth"}},
{"title":{"S":"Age of Dinosaurs"}},{"title":{"S":"Cloudy with a Chance of Meatballs 2"}}],
"LastEvaluatedKey":{"year":{"N":"2013"},"title":{"S":"Curse of Chucky"}},"ScannedCount":100}'
```

响应中的 `LastEvaluatedKey` 指示并未检索所有项目。随后，AWS CLI 会将另一个 `Scan` 请求发送到 DynamoDB。此请求和响应模式继续，直到收到最终响应。

```
2017-07-07 12:19:17,830 - MainThread - botocore.parsers - DEBUG - Response body:
b'{"Count":1,"Items":[{"title":{"S":"WarGames"}}],"ScannedCount":6}'
```

如果不存在 `LastEvaluatedKey`，则表示没有其他要检索的项目。

**注意**  
AWS SDK 处理低级别的 DynamoDB 响应（包括是否存在 `LastEvaluatedKey`），并提供各种抽象概念对 `Scan` 结果进行分页。例如，适用于 Java 的 SDK 文档接口提供 `java.util.Iterator` 支持，以便您能够一次处理一个结果。  
对于各种编程语言的代码示例，请参阅本地化的 [Amazon DynamoDB 入门指南](https://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/)和 AWS SDK 文档。

## 对结果中的项目进行计数
<a name="Scan.Count"></a>

除了与您的条件匹配的项目之外，`Scan` 响应还包含以下元素：
+ `ScannedCount` — 在应用任何 `ScanFilter` 之前计算的项目数。`ScannedCount` 值很高但 `Count` 结果很少或为零，指 `Scan` 操作效率低下。如果您没有在请求中使用筛选器，则 `ScannedCount` 与 `Count` 相同。
+ `Count` — 在应用筛选表达式（如果有）*之后*，剩余的项目数量。

**注意**  
如果您不使用筛选表达式，那么 `ScannedCount` 和 `Count` 将具有相同的值。

如果 `Scan` 结果集的大小大于 1 MB，则 `ScannedCount` 和 `Count` 将仅表示项目总数的部分计数。您需要执行多次 `Scan` 操作才能检索所有结果（请参阅[对结果分页](#Scan.Pagination)）。

所有 `Scan` 响应都将包含由该特定 `ScannedCount` 请求处理的项目的 `Count` 和 `Scan`。要获取所有 `Scan` 请求的总和，您可以对 `ScannedCount` 和 `Count` 记录流水账。

## 扫描占用的容量单位
<a name="Scan.CapacityUnits"></a>

您可以对任何表或二级索引执行 `Scan` 操作。`Scan` 操作将占用读取容量单位，如下所示：


****  

| 如果对...进行 `Scan` | DynamoDB 将占用...的读取容量单位 | 
| --- | --- | 
| 表 | 表的预置读取容量。 | 
| 全局二级索引 | 索引的预置读取容量。 | 
| 本地二级索引 | 基表的预置读取容量。 | 

**注意**  
[基于资源的策略](access-control-resource-based.md)目前不支持跨账户访问二级索引扫描操作。

默认情况下，`Scan` 操作不会返回任何有关它占用的读取容量大小的数据。但是，您可在 `ReturnConsumedCapacity` 请求中指定 `Scan` 参数以获取此信息。下面是 `ReturnConsumedCapacity` 的有效设置：
+ `NONE` — 不返回任何已占用容量数据。（这是默认值。）
+ `TOTAL` — 响应包含占用的读取容量单位的总数。
+ `INDEXES` — 响应显示占用的读取容量单位的总数，以及访问的每个表和索引的占用容量。

DynamoDB 将基于项目数量以及这些项目的大小，而不是基于返回到应用程序的数据量来计算消耗的读取容量单位数。因此，无论您是请求所有属性（默认行为）还是只请求部分属性（使用投影表达式），占用的容量单位数都是相同的。无论您是否使用筛选表达式，该数值也都是一样的。`Scan` 消耗最小的读取容量单位，来为高达 4 KB 的项目每秒执行一次强一致性读取，或每秒执行两次最终一致性读取。如果您需要读取大于 4KB 的项目，DynamoDB 需要额外的读取请求单位。空表和分区键数量稀疏的超大表，可能会有在超出扫描的数据量后对一些额外的 RCU 收费的情况。这包括处理 `Scan` 请求的费用，即使不存在数据也是如此。

## 扫描的读取一致性
<a name="Scan.ReadConsistency"></a>

默认情况下，`Scan` 操作将执行最终一致性读取。这意味着 `Scan` 结果可能无法反映由最近完成的 `PutItem` 或 `UpdateItem` 操作导致的更改。有关更多信息，请参阅 [DynamoDB 读取一致性](HowItWorks.ReadConsistency.md)。

如果您需要强一致性读取，请在 `Scan` 开始时在 `ConsistentRead` 请求中将 `true` 参数设置为 `Scan`。这可确保在 `Scan` 开始前完成的所有写入操作都会包括在 `Scan` 响应中。

备份或复制表时，可以将 `ConsistentRead` 设置为 `true`，并结合 [DynamoDB Streams](./Streams.html)。首先，您使用 `Scan` 并将 `ConsistentRead` 设置为 true 来获取表中数据的一致性副本。`Scan` 操作期间，DynamoDB Streams 会记录表上发生的任何其他写入活动。在 `Scan` 完成后，您可以将流中的写入活动应用于该表。

**注意**  
与保留 `Scan` 的默认值 (`ConsistentRead`) 相比，`true` 设置为 `ConsistentRead` 的 `false` 操作将占用两倍的读取容量单位。

## 并行扫描
<a name="Scan.ParallelScan"></a>

默认情况下，`Scan` 操作按顺序处理数据。Amazon DynamoDB 以 1 MB 的增量向应用程序返回数据，应用程序执行其他 `Scan` 操作检索接下来 1 MB 的数据。

扫描的表或索引越大，`Scan` 完成需要的时间越长。此外，一个顺序 `Scan` 可能并不总是能够充分利用预调配的读取吞吐容量：即使 DynamoDB 跨多个物理分区分配大型表的数据，`Scan` 操作一次只能读取一个分区。出于这个原因，`Scan` 受到单个分区的最大吞吐量限制。

为了解决这些问题，`Scan`操作可以逻辑地将表或二级索引分成多个*分段*，多个应用程序工作进程并行扫描这些段。每个工作进程可以是一个线程（在支持多线程的编程语言中），也可以是一个操作系统进程。要执行并行扫描，每个工作进程都会发出自己的 `Scan` 请求，并使用以下参数：
+ `Segment`— 要由特定工作进程扫描的段。每个工作进程应使用不同的 `Segment` 值。
+ `TotalSegments`— 并行扫描的片段总数。该值必须与应用程序将使用的工作进程数量相同。

下图显示了多线程应用程序如何执行具有三度并行的并行 `Scan`。

![\[一种多线程应用程序，通过将表分为三个段来执行并行扫描。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/ParallelScan.png)




在此图中，应用程序生成三个线程，并为每个线程分配一个编号。（段从零开始，因此第一个编号始终为 0。） 每个线程发出 `Scan` 请求，将 `Segment` 设置为其指定的编号并将 `TotalSegments` 设置为 3。每个线程扫描其指定的段，每次检索 1 MB 的数据，并将数据返回到应用程序的主线程。

DynamoDB 通过对每个项目的分区键应用哈希函数，将项目分配给*分段*。对于给定 `TotalSegments` 值，所有具有相同分区键的项目总是被分配给相同的 `Segment`。这意味着，在*项目 1*、*项目 2* 和*项目 3* 都共享 `pk="account#123"`（但排序键不同）的表中，无论排序键值或*项目集合*的大小如何，这些项目都将由同一个 worker 处理。

由于*分段*分配仅基于分区键哈希，因此分段的分布可能不均匀。有些分段可能不包含任何项目，而另一些分段可能包含许多带有大型项目集合的分区键。因此，增加分段总数并不能保证获得更快的扫描性能，尤其是在分区键在键空间中分布不均匀的情况下。

`Segment` 和 `TotalSegments` 值适用于单个 `Scan` 请求，可以随时使用不同的值。您可能需要对这些值以及您使用的工作集成数进行试验，直到您的应用程序达到最佳性能。

**注意**  
具有大量工作进程的并行扫描可以轻松占用正在扫描的表或索引的所有预配置吞吐量。如果表或索引也引起来自其他应用程序的大量读取或写入活动，最好避免进行此类扫描。  
要控制每个请求返回的数据量，请使用 `Limit` 参数。这有助于防止出现一个工作进程占用所有预配置吞吐量而牺牲所有其他工作进程的情况。

# PartiQL – 用于 Amazon DynamoDB 的 SQL 兼容语言
<a name="ql-reference"></a>

Amazon DynamoDB 支持 [PartiQL](https://partiql.org/)（一种 SQL 兼容查询语言），用于在 Amazon DynamoDB 中选择、插入、更新和删除数据。使用 PartiQL，您可以轻松地与 DynamoDB 表进行交互，并使用 AWS 管理控制台、NoSQL Workbench、AWS Command Line Interface 以及用于 PartiQL 的 DynamoDB API 运行临时查询。

PartiQL 操作提供与其他 DynamoDB 数据层面操作相同的可用性、延迟和性能。

以下部分介绍 PartiQL 的 DynamoDB 实现。

**Topics**
+ [什么是 PartiQL？](#ql-reference.what-is)
+ [Amazon DynamoDB 中的 PartiQL](#ql-reference.what-is)
+ [开始使用](ql-gettingstarted.md)
+ [数据类型](ql-reference.data-types.md)
+ [语句](ql-reference.statements.md)
+ [函数](ql-functions.md)
+ [运算符](ql-operators.md)
+ [事务](ql-reference.multiplestatements.transactions.md)
+ [分批操作](ql-reference.multiplestatements.batching.md)
+ [IAM 策略](ql-iam.md)

## 什么是 PartiQL？
<a name="ql-reference.what-is"></a>

*PartiQL* 在包含结构化数据、半结构化数据和嵌套数据的多个数据存储中提供 SQL 兼容的查询访问。它在 Amazon 中广泛使用，现在可作为许多 AWS 服务（包括 DynamoDB）的一部分提供。

有关 PartiQL 规范和核心查询语言的教程，请参阅 [ParameSQL 文档](https://partiql.org/docs.html)。

**注意**  
Amazon DynamoDB 支持 [PartiQL](https://partiql.org/) 查询语言的*子集*。
Amazon DynamoDB 不支持 [Amazon ion](http://amzn.github.io/ion-docs/) 数据格式或 Amazon ion 文字。

## Amazon DynamoDB 中的 PartiQL
<a name="ql-reference.what-is"></a>

要在 DynamoDB 中运行 PartiQL 查询，您可以使用：
+ DynamoDB 控制台
+ NoSQL Workbench
+ AWS Command Line Interface（AWS CLI）
+ DynamoDB API

有关使用这些方法访问 DynamoDB 的信息，请参阅[访问 DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AccessingDynamoDB.html)。

# PartiQL for DynamoDB 入门
<a name="ql-gettingstarted"></a>

本节介绍如何从 Amazon DynamoDB 控制台、AWS Command Line Interface (AWS CLI) 和 DynamoDB API 使用 PartiQL for DynamoDB。

在以下 DynamoDB 例中，[DynamoDB 入门](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStartedDynamoDB.html)教程中定义的 DynamoDB 表是一个前提条件。

有关使用 DynamoDB 控制台、AWS Command Line Interface 或 DynamoDB API 访问 DynamoDB 的信息，请参阅[访问 DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AccessingDynamoDB.html)。

要[下载](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.settingup.html)并使用 [NoSQL Workbench](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.html) 生成 [PartiQL for DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html) 语句，请选择 NoSQL Workbench for DynamoDB [操作生成器](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.querybuilder.operationbuilder.html)右上角的 **PartiQL operations**（PartiQL 操作）。

------
#### [ Console ]

![\[PartiQL 编辑器界面，显示对 Music 表运行查询操作的结果。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/partiqlgettingstarted.png)


1. 登录 AWS 管理控制台，打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 在控制台左侧的导航窗格中，选择 **PartiQL editor (PartiQL 编辑器)**。

1. 选择 **Music** 表。

1. 选择 **Query table (查询表)**。此操作生成不会导致完整表扫描的查询。

1. 将 `partitionKeyValue` 替换为字符串值 `Acme Band`。将 `sortKeyValue` 替换为字符串值 `Happy Day`。

1. 选择 **Run (运行)** 按钮。

1. 选择 **Table view (表视图)** 或 **JSON view (JSON 视图)** 按钮，查看查询结果。

------
#### [ NoSQL workbench ]

![\[NoSQL Workbench 界面。它显示一条 PartiQL SELECT 语句，您可以对 Music 表运行该语句。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/workbench/partiql.single.png)


1. 选择 **PartiQL statement (PartiQL 语句)**。

1. 输入以下 PartiQL [SELECT 语句](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.select.html) 

   ```
   SELECT *                                         
   FROM Music  
   WHERE Artist=? and SongTitle=?
   ```

1. 指定 `Artist` 和 `SongTitle` 参数值：

   1. 选择 **Optional request parameters (可选请求参数)**。

   1. 选择 **Add new parameters (添加新参数)**。

   1. 选择属性类型 **string** 和值 `Acme Band`。

   1. 重复步骤 b 和 c，然后选择类型 **string** 和值 `PartiQL Rocks`。

1. 如果要生成代码，请选择 **Generate code (生成代码)**。

   从显示的选项卡中选择所需的语言。现在，您便可复制此代码并在应用程序中使用它。

1. 如果要立即执行操作，请选择 **Run (执行)**。

------
#### [ AWS CLI ]

1. 使用 INSERT PartiQL 语句在 `Music` 表中创建项目。

   ```
   aws dynamodb execute-statement --statement "INSERT INTO Music  \
   					    VALUE  \
   					    {'Artist':'Acme Band','SongTitle':'PartiQL Rocks'}"
   ```

1. 使用 SELECT PartiQL 语句从 Music 表中检索项目。

   ```
   aws dynamodb execute-statement --statement "SELECT * FROM Music   \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

1. 使用 INSERT PartiQL 语句在 `Music` 表中更新项目。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               SET AwardsWon=1  \
                                               SET AwardDetail={'Grammys':[2020, 2018]}  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

   为 `Music` 表中的项目添加列表值。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               SET AwardDetail.Grammys =list_append(AwardDetail.Grammys,[2016])  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

   为 `Music` 表中的项目移除列表值。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               REMOVE AwardDetail.Grammys[2]  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

   为 `Music` 表中的项目添加映射成员。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               SET AwardDetail.BillBoard=[2020]  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

   为 `Music` 表中的项目添加新字符串集属性。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               SET BandMembers =<<'member1', 'member2'>>  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

   为 `Music` 表中的项目更新字符串集属性。

   ```
   aws dynamodb execute-statement --statement "UPDATE Music  \
                                               SET BandMembers =set_add(BandMembers, <<'newmember'>>)  \
                                               WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

1. 使用 DELETE PartiQL 语句从 `Music` 表删除项目。

   ```
   aws dynamodb execute-statement --statement "DELETE  FROM Music  \
       WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
   ```

------
#### [ Java ]

```
import java.util.ArrayList;
import java.util.List;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import software.amazon.dynamodb.AmazonDynamoDB;
import software.amazon.dynamodb.AmazonDynamoDBClientBuilder;
import software.amazon.dynamodb.model.AttributeValue;
import software.amazon.dynamodb.model.ConditionalCheckFailedException;
import software.amazon.dynamodb.model.ExecuteStatementRequest;
import software.amazon.dynamodb.model.ExecuteStatementResult;
import software.amazon.dynamodb.model.InternalServerErrorException;
import software.amazon.dynamodb.model.ItemCollectionSizeLimitExceededException;
import software.amazon.dynamodb.model.ProvisionedThroughputExceededException;
import software.amazon.dynamodb.model.RequestLimitExceededException;
import software.amazon.dynamodb.model.ResourceNotFoundException;
import software.amazon.dynamodb.model.TransactionConflictException;

public class DynamoDBPartiQGettingStarted {

    public static void main(String[] args) {
        // Create the DynamoDB Client with the region you want
        AmazonDynamoDB dynamoDB = createDynamoDbClient("us-west-1");

        try {
            // Create ExecuteStatementRequest
            ExecuteStatementRequest executeStatementRequest = new ExecuteStatementRequest();
            List<AttributeValue> parameters= getPartiQLParameters();

            //Create an item in the Music table using the INSERT PartiQL statement
            processResults(executeStatementRequest(dynamoDB, "INSERT INTO Music value {'Artist':?,'SongTitle':?}", parameters));

            //Retrieve an item from the Music table using the SELECT PartiQL statement.
            processResults(executeStatementRequest(dynamoDB, "SELECT * FROM Music  where Artist=? and SongTitle=?", parameters));

            //Update an item in the Music table using the UPDATE PartiQL statement.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music SET AwardsWon=1 SET AwardDetail={'Grammys':[2020, 2018]}  where Artist=? and SongTitle=?", parameters));

            //Add a list value for an item in the Music table.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music SET AwardDetail.Grammys =list_append(AwardDetail.Grammys,[2016])  where Artist=? and SongTitle=?", parameters));

            //Remove a list value for an item in the Music table.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music REMOVE AwardDetail.Grammys[2]   where Artist=? and SongTitle=?", parameters));

            //Add a new map member for an item in the Music table.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music set AwardDetail.BillBoard=[2020] where Artist=? and SongTitle=?", parameters));

            //Add a new string set attribute for an item in the Music table.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music SET BandMembers =<<'member1', 'member2'>> where Artist=? and SongTitle=?", parameters));

            //update a string set attribute for an item in the Music table.
            processResults(executeStatementRequest(dynamoDB, "UPDATE Music SET BandMembers =set_add(BandMembers, <<'newmember'>>) where Artist=? and SongTitle=?", parameters));

            //Retrieve an item from the Music table using the SELECT PartiQL statement.
            processResults(executeStatementRequest(dynamoDB, "SELECT * FROM Music  where Artist=? and SongTitle=?", parameters));

            //delete an item from the Music Table
            processResults(executeStatementRequest(dynamoDB, "DELETE  FROM Music  where Artist=? and SongTitle=?", parameters));
        } catch (Exception e) {
            handleExecuteStatementErrors(e);
        }
    }

    private static AmazonDynamoDB createDynamoDbClient(String region) {
        return AmazonDynamoDBClientBuilder.standard().withRegion(region).build();
    }

    private static List<AttributeValue> getPartiQLParameters() {
        List<AttributeValue> parameters = new ArrayList<AttributeValue>();
        parameters.add(new AttributeValue("Acme Band"));
        parameters.add(new AttributeValue("PartiQL Rocks"));
        return parameters;
    }

    private static ExecuteStatementResult executeStatementRequest(AmazonDynamoDB client, String statement, List<AttributeValue> parameters ) {
        ExecuteStatementRequest request = new ExecuteStatementRequest();
        request.setStatement(statement);
        request.setParameters(parameters);
        return client.executeStatement(request);
    }

    private static void processResults(ExecuteStatementResult executeStatementResult) {
        System.out.println("ExecuteStatement successful: "+ executeStatementResult.toString());

    }

    // Handles errors during ExecuteStatement execution. Use recommendations in error messages below to add error handling specific to
    // your application use-case.
    private static void handleExecuteStatementErrors(Exception exception) {
        try {
            throw exception;
        } catch (ConditionalCheckFailedException ccfe) {
            System.out.println("Condition check specified in the operation failed, review and update the condition " +
                                       "check before retrying. Error: " + ccfe.getErrorMessage());
        } catch (TransactionConflictException tce) {
            System.out.println("Operation was rejected because there is an ongoing transaction for the item, generally " +
                                       "safe to retry with exponential back-off. Error: " + tce.getErrorMessage());
        } catch (ItemCollectionSizeLimitExceededException icslee) {
            System.out.println("An item collection is too large, you\'re using Local Secondary Index and exceeded " +
                                       "size limit of items per partition key. Consider using Global Secondary Index instead. Error: " + icslee.getErrorMessage());
        } catch (Exception e) {
            handleCommonErrors(e);
        }
    }

    private static void handleCommonErrors(Exception exception) {
        try {
            throw exception;
        } catch (InternalServerErrorException isee) {
            System.out.println("Internal Server Error, generally safe to retry with exponential back-off. Error: " + isee.getErrorMessage());
        } catch (RequestLimitExceededException rlee) {
            System.out.println("Throughput exceeds the current throughput limit for your account, increase account level throughput before " +
                                       "retrying. Error: " + rlee.getErrorMessage());
        } catch (ProvisionedThroughputExceededException ptee) {
            System.out.println("Request rate is too high. If you're using a custom retry strategy make sure to retry with exponential back-off. " +
                                       "Otherwise consider reducing frequency of requests or increasing provisioned capacity for your table or secondary index. Error: " +
                                       ptee.getErrorMessage());
        } catch (ResourceNotFoundException rnfe) {
            System.out.println("One of the tables was not found, verify table exists before retrying. Error: " + rnfe.getErrorMessage());
        } catch (AmazonServiceException ase) {
            System.out.println("An AmazonServiceException occurred, indicates that the request was correctly transmitted to the DynamoDB " +
                                       "service, but for some reason, the service was not able to process it, and returned an error response instead. Investigate and " +
                                       "configure retry strategy. Error type: " + ase.getErrorType() + ". Error message: " + ase.getErrorMessage());
        } catch (AmazonClientException ace) {
            System.out.println("An AmazonClientException occurred, indicates that the client was unable to get a response from DynamoDB " +
                                       "service, or the client was unable to parse the response from the service. Investigate and configure retry strategy. "+
                                       "Error: " + ace.getMessage());
        } catch (Exception e) {
            System.out.println("An exception occurred, investigate and configure retry strategy. Error: " + e.getMessage());
        }
    }

}
```

------

## 使用参数化语句
<a name="ql-gettingstarted.parameterized"></a>

您可以使用问号（`?`）占位符并在 `Parameters` 字段中单独提供值，而不是将值直接嵌入到 PartiQL 语句字符串中。每个 `?` 值都按提供的顺序替换为相应的参数值。

使用参数化语句是一种最佳实践，因为这可以将语句结构与数据值分开，从而使语句更易于读懂和重复使用。这还可以避免在语句字符串中手动格式化和转义属性值的需求。

`ExecuteStatement`、`BatchExecuteStatement` 和 `ExecuteTransaction` 操作支持参数化语句。

以下示例使用分区键和排序键的参数化值从 `Music` 表中检索项目。

------
#### [ AWS CLI parameterized ]

```
aws dynamodb execute-statement \
    --statement "SELECT * FROM \"Music\" WHERE Artist=? AND SongTitle=?" \
    --parameters '[{"S": "Acme Band"}, {"S": "PartiQL Rocks"}]'
```

------
#### [ Java parameterized ]

```
List<AttributeValue> parameters = new ArrayList<>();
parameters.add(new AttributeValue("Acme Band"));
parameters.add(new AttributeValue("PartiQL Rocks"));

ExecuteStatementRequest request = new ExecuteStatementRequest()
    .withStatement("SELECT * FROM Music WHERE Artist=? AND SongTitle=?")
    .withParameters(parameters);

ExecuteStatementResult result = dynamoDB.executeStatement(request);
```

------
#### [ Python parameterized ]

```
response = dynamodb_client.execute_statement(
    Statement="SELECT * FROM Music WHERE Artist=? AND SongTitle=?",
    Parameters=[
        {'S': 'Acme Band'},
        {'S': 'PartiQL Rocks'}
    ]
)
```

------

**注意**  
前面入门部分中的 Java 示例自始至终都在使用参数化语句。`getPartiQLParameters()` 方法构建参数列表，每条语句都使用 `?` 占位符而不是内联值。

# DynamoDB 的 PartiQL 数据类型
<a name="ql-reference.data-types"></a>

下表列出可用于 PartiQL for DynamoDB 的数据类型。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/ql-reference.data-types.html)

## 示例
<a name="ql-reference.data-types"></a>

以下语句演示如何插入以下数据类型：`String`、`Number`、`Map`、`List`、`Number Set` 和 `String Set`。

```
INSERT INTO TypesTable value {'primarykey':'1', 
'NumberType':1,
'MapType' : {'entryname1': 'value', 'entryname2': 4}, 
'ListType': [1,'stringval'], 
'NumberSetType':<<1,34,32,4.5>>, 
'StringSetType':<<'stringval','stringval2'>>
}
```

以下语句演示了如何将新元素插入到 `Map`、`List`、`Number Set` 和 `String Set`类型并更改 `Number` 类型的值。

```
UPDATE TypesTable 
SET NumberType=NumberType + 100 
SET MapType.NewMapEntry=[2020, 'stringvalue', 2.4]
SET ListType = LIST_APPEND(ListType, [4, <<'string1', 'string2'>>])
SET NumberSetType= SET_ADD(NumberSetType, <<345, 48.4>>)
SET StringSetType = SET_ADD(StringSetType, <<'stringsetvalue1', 'stringsetvalue2'>>)
WHERE primarykey='1'
```

以下语句演示如何从 `Map`、`List`、`Number Set` 和 `String Set` 类型移除元素，并更改 `Number` 类型的值。

```
UPDATE TypesTable 
SET NumberType=NumberType - 1
REMOVE ListType[1]
REMOVE MapType.NewMapEntry
SET NumberSetType = SET_DELETE( NumberSetType, <<345>>)
SET StringSetType = SET_DELETE( StringSetType, <<'stringsetvalue1'>>)
WHERE primarykey='1'
```

有关更多信息，请参阅 [DynamoDB 数据类型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)。

# PartiQL for DynamoDB 语句
<a name="ql-reference.statements"></a>

Amazon DynamoDB 支持以下 PartiQL 语句。

**注意**  
DynamoDB 不支持所有 PartiQL 语句。  
此参考提供可以使用 AWS CLI 或 API 手动运行的 PartiQL 语句的基本语法和用法示例。

*数据操作语言* (DML) 是一组用于管理 DynamoDB 表中的数据的 PartiQL 语句。可以使用 DML 语句在表中添加、修改或删除数据。

支持以下 DML 和查询语言语句：
+ [PartiQL for DynamoDB 的 Select 语句](ql-reference.select.md)
+ [PartiQL for DynamoDB Update 语句](ql-reference.update.md)
+ [PartiQL for DynamoDB Insert 语句](ql-reference.insert.md)
+ [PartiQL for DynamoDB Delete 语句](ql-reference.delete.md)

PartiQL for DynamoDB 还支持 [使用 PartiQL for DynamoDB 执行事务](ql-reference.multiplestatements.transactions.md) 和 [对 PartiQL for DynamoDB 运行批处理操作](ql-reference.multiplestatements.batching.md)。

# PartiQL for DynamoDB 的 Select 语句
<a name="ql-reference.select"></a>

使用 `SELECT` 语句从 Amazon DynamoDB 的表检索数据。

如果 WHERE 子句中未提供带有分区键的相等或 IN 条件，使用 `SELECT` 语句会导致全表扫描。扫描操作会检查每个项目的请求值，并且可以在单个操作中使用大型表或索引的预置吞吐量。

如果您想避免在 PartiQL 中进行全表扫描，您可以：
+ 创作您的 `SELECT` 语句不会导致全表扫描，方法是确保您的 [WHERE 子句条件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.select.html#ql-reference.select.parameters)相应地配置。
+ 使用《DynamoDB 开发人员指南》[示例：允许 Select 语句并拒绝 PartiQL for DynamoDB 的完整表扫描语句](ql-iam.md#access-policy-ql-iam-example6)中指定的 IAM 策略禁用全表扫描。

有关更多信息，请参阅《DynamoDB 开发人员指南》中的[查询和扫描数据的最佳实践](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-query-scan.html)。

**Topics**
+ [语法](#ql-reference.select.syntax)
+ [参数](#ql-reference.select.parameters)
+ [示例](#ql-reference.select.examples)

## 语法
<a name="ql-reference.select.syntax"></a>

```
SELECT expression  [, ...] 
FROM table[.index]
[ WHERE condition ] [ [ORDER BY key [DESC|ASC] , ...]
```

## 参数
<a name="ql-reference.select.parameters"></a>

***expression***  
（必需）从 `*` 通配符形成的投影，或者结果集的一个或多个属性名称或文档路径的投影列表。表达式可以包括对 [将 PartiQL 函数和 DynamoDB 结合使用](ql-functions.md) 或通过 [用于 DynamoDB 的 PartiQL 算术、比较和逻辑运算符](ql-operators.md) 修改的字段的调用。

***table***  
（必需）要查询的表名。

***index***  
（可选）要查询的索引的名称。  
查询索引时，必须在表名和索引名称中添加双引号。  

```
SELECT * 
FROM "TableName"."IndexName"
```

***条件***  
（可选）查询的选择条件。  
为了确保 `SELECT` 语句不会导致全表扫描，`WHERE` 子句条件必须指定分区键。使用相等或 IN 运算符。  
例如，如果 `Orders` 表有 `OrderID` 分区键和其他非键属性，包括 `Address`，则以下语句不会导致完整表扫描：  

```
SELECT * 
FROM "Orders" 
WHERE OrderID = 100

SELECT * 
FROM "Orders" 
WHERE OrderID = 100 and Address='some address'

SELECT * 
FROM "Orders" 
WHERE OrderID = 100 or OrderID = 200

SELECT * 
FROM "Orders" 
WHERE OrderID IN [100, 300, 234]
```
以下 `SELECT` 语句将导致完整表扫描：  

```
SELECT * 
FROM "Orders" 
WHERE OrderID > 1

SELECT * 
FROM "Orders" 
WHERE Address='some address'

SELECT * 
FROM "Orders" 
WHERE OrderID = 100 OR Address='some address'
```

***键***  
（可选）用于对返回结果进行排序的哈希键或排序键。默认顺序为升序 (`ASC`) 指定 `DESC` 如果您希望按降序重新调整结果。

**注意**  
如果省略 `WHERE` 子句，则检索表中的所有项目。

## 示例
<a name="ql-reference.select.examples"></a>

以下查询指定分区键 `OrderID`，并使用相等运算符，返回 `Orders` 表中的一个项目（如果存在）。

```
SELECT OrderID, Total
FROM "Orders"
WHERE OrderID = 1
```

以下查询使用 OR 运算符返回 `Orders` 表中具有特定分区键 `OrderID` 的所有项目。

```
SELECT OrderID, Total
FROM "Orders"
WHERE OrderID = 1 OR OrderID = 2
```

以下查询使用 IN 运算符返回 `Orders` 表中具有特定分区键 `OrderID` 的所有项目。返回的结果基于 `OrderID` 密钥属性值按降序排列。

```
SELECT OrderID, Total
FROM "Orders"
WHERE OrderID IN [1, 2, 3] ORDER BY OrderID DESC
```

以下查询显示一个全表扫描，返回 `Orders` 表中 `Total` 大于 500，`Total` 是非键属性的所有项目。

```
SELECT OrderID, Total 
FROM "Orders"
WHERE Total > 500
```

以下查询显示一个全表扫描，使用 IN 运算符和非键属性 `Total` 返回 `Orders` 表特定 `Total` 订单范围内的所有项目。

```
SELECT OrderID, Total 
FROM "Orders"
WHERE Total IN [500, 600]
```

以下查询显示一个全表扫描，使用 BETWEEN 运算符和非键属性 `Total` 返回 `Orders` 表特定 `Total` 订单范围内的所有项目。

```
SELECT OrderID, Total 
FROM "Orders" 
WHERE Total BETWEEN 500 AND 600
```

下面的查询在 WHERE 子句条件中指定分区键 `CustomerID` 和排序键 `MovieID`，在 SELECT 子句中使用完整文档路径，返回使用 firestick 设备观察的首个日期。

```
SELECT Devices.FireStick.DateWatched[0] 
FROM WatchList 
WHERE CustomerID= 'C1' AND MovieID= 'M1'
```

以下查询显示了一个完整表扫描，此扫描在 WHERE 子句条件中使用文档路径，返回 12/24/19 之后首次使用 firestick 设备的项目列表。

```
SELECT Devices 
FROM WatchList 
WHERE Devices.FireStick.DateWatched[0] >= '12/24/19'
```

# PartiQL for DynamoDB Update 语句
<a name="ql-reference.update"></a>

使用 `UPDATE` 语句来修改 Amazon DynamoDB 表中某个项目中一个或多个属性的值。

**注意**  
一次只能更新一个项目；不能发出单个 DynamoDB PartiQL 语句更新多个项目。有关更新多个项目的信息，请参阅 [使用 PartiQL for DynamoDB 执行事务](ql-reference.multiplestatements.transactions.md) 或 [对 PartiQL for DynamoDB 运行批处理操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [语法](#ql-reference.update.syntax)
+ [参数](#ql-reference.update.parameters)
+ [返回值](#ql-reference.update.return)
+ [示例](#ql-reference.update.examples)

## 语法
<a name="ql-reference.update.syntax"></a>

```
UPDATE  table  
[SET | REMOVE]  path  [=  data] […]
WHERE condition [RETURNING returnvalues]
<returnvalues>  ::= [ALL OLD | MODIFIED OLD | ALL NEW | MODIFIED NEW] *
```

## 参数
<a name="ql-reference.update.parameters"></a>

***表*\$1**  
（必需）包含要修改的数据的表。

***path***  
（必需）要创建或修改的属性名称或文档路径。

***data***  
（必需）属性值或操作的结果。  
要与 SET 一起使用的支持操作：  
+ LIST\$1APPEND：向列表类型添加一个值。
+ SET\$1ADD：将值添加到数字或字符串集。
+ SET\$1DELETE：从数字或字符串集中删除值。

***条件***  
（必需）要修改的项目的选择条件。此条件必须解析为单个主键值。

***returnvalues***  
（可选）如果希望获取更新之前或之后显示的项目属性，使用 `returnvalues`。有效值为：  
+ `ALL OLD *` - 返回更新操作前项目的所有属性。
+ `MODIFIED OLD *` - 仅返回更新操作前已更新的属性。
+ `ALL NEW *` - 返回更新操作后显示的项目的所有属性。
+ `MODIFIED NEW *` - 仅返回 `UpdateItem` 操作后已更新的属性。

## 返回值
<a name="ql-reference.update.return"></a>

此语句不返回值，除非指定 `returnvalues` 参数。

**注意**  
如果对于 DynamoDB 表中的任何项目，UPDATE 语句的 WHERE 子句计算结果不为 true，则返回 `ConditionalCheckFailedException`。

## 示例
<a name="ql-reference.update.examples"></a>

更新现有项目的属性值。如果属性不存在，则创建该属性。

下面的查询添加一个 number 类型参数 (`AwardsWon`) 和一个 map 类型参数 (`AwardDetail`)，更新 `"Music"` 表的项目。

```
UPDATE "Music" 
SET AwardsWon=1 
SET AwardDetail={'Grammys':[2020, 2018]}  
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

您可以添加 `RETURNING ALL OLD *` 以返回在 `Update` 操作之前显示的属性。

```
UPDATE "Music" 
SET AwardsWon=1 
SET AwardDetail={'Grammys':[2020, 2018]}  
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
RETURNING ALL OLD *
```

这将返回以下内容：

```
{
    "Items": [
        {
            "Artist": {
                "S": "Acme Band"
            },
            "SongTitle": {
                "S": "PartiQL Rocks"
            }
        }
    ]
}
```

您可以添加 `RETURNING ALL NEW *` 以返回在 `Update` 操作之后显示的属性。

```
UPDATE "Music" 
SET AwardsWon=1 
SET AwardDetail={'Grammys':[2020, 2018]}  
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
RETURNING ALL NEW *
```

这将返回以下内容：

```
{
    "Items": [
        {
            "AwardDetail": {
                "M": {
                    "Grammys": {
                        "L": [
                            {
                                "N": "2020"
                            },
                            {
                                "N": "2018"
                            }
                        ]
                    }
                }
            },
            "AwardsWon": {
                "N": "1"
            }
        }
    ]
}
```

以下查询通过附加到列表 `AwardDetail.Grammys`，更新 `"Music"` 表中的项目。

```
UPDATE "Music" 
SET AwardDetail.Grammys =list_append(AwardDetail.Grammys,[2016])  
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

以下查询通过从列表 `AwardDetail.Grammys` 移除，更新 `"Music"` 表中的项目。

```
UPDATE "Music" 
REMOVE AwardDetail.Grammys[2]   
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

以下查询通过将 `BillBoard` 添加到映射 `AwardDetail`，更新 `"Music"` 表中的项目。

```
UPDATE "Music" 
SET AwardDetail.BillBoard=[2020] 
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

以下查询添加字符串集属性 `BandMembers`，更新 `"Music"` 表中的项目。

```
UPDATE "Music" 
SET BandMembers =<<'member1', 'member2'>> 
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

以下查询将 `newbandmember` 添加到字符串集 `BandMembers`，更新 `"Music"` 表中的项目。

```
UPDATE "Music" 
SET BandMembers =set_add(BandMembers, <<'newbandmember'>>) 
WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'
```

# PartiQL for DynamoDB Delete 语句
<a name="ql-reference.delete"></a>

使用 `DELETE` 语句从 Amazon DynamoDB 表中删除现有项目。

**注意**  
一次只能删除一个项目。不能发出单个 DynamoDB PartiQL 语句，删除多个项目。有关删除多个项目的信息，请参阅 [使用 PartiQL for DynamoDB 执行事务](ql-reference.multiplestatements.transactions.md) 或 [对 PartiQL for DynamoDB 运行批处理操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [语法](#ql-reference.delete.syntax)
+ [参数](#ql-reference.delete.parameters)
+ [返回值](#ql-reference.delete.return)
+ [示例](#ql-reference.delete.examples)

## 语法
<a name="ql-reference.delete.syntax"></a>

```
DELETE FROM table 
 WHERE condition [RETURNING returnvalues]
 <returnvalues>  ::= ALL OLD *
```

## 参数
<a name="ql-reference.delete.parameters"></a>

***表*\$1**  
（必需）包含要删除的项目的 DynamoDB 表。

***条件***  
（必需）要删除的项目的选择条件；此条件必须解析为单个主键值。

***returnvalues***  
（可选）如果要获得删除前的项目属性，请使用 `returnvalues`。有效值为：  
+ `ALL OLD *` - 返回旧项目的内容。

## 返回值
<a name="ql-reference.delete.return"></a>

此语句不返回值，除非指定 `returnvalues` 参数。

**注意**  
如果 DynamoDB 表中没有任何与发出 DELETE 的项目的主键相同的项目，则返回 SUCCESS 并删除 0 个项目。如果表具有具有相同主键的项目，但 DELETE 语句的 WHERE 子句中的条件计算结果为 false，则返回 `ConditionalCheckFailedException`。

## 示例
<a name="ql-reference.delete.examples"></a>

以下查询删除 `"Music"` 表中的一个项目。

```
DELETE FROM "Music" WHERE "Artist" = 'Acme Band' AND "SongTitle" = 'PartiQL Rocks'
```

您可以添加参数 `RETURNING ALL OLD *` 以返回已删除的数据。

```
DELETE FROM "Music" WHERE "Artist" = 'Acme Band' AND "SongTitle" = 'PartiQL Rocks' RETURNING ALL OLD *
```

`Delete` 语句现在返回以下内容：

```
{
    "Items": [
        {
            "Artist": {
                "S": "Acme Band"
            },
            "SongTitle": {
                "S": "PartiQL Rocks"
            }
        }
    ]
}
```

# PartiQL for DynamoDB Insert 语句
<a name="ql-reference.insert"></a>

使用 `INSERT` 语句向 Amazon DynamoDB 的表添加项目。

**注意**  
一次只能插入一个项目；不能发出单个 DynamoDB PartiQL 语句插入多个项目。有关插入多个项目的信息，请参阅 [使用 PartiQL for DynamoDB 执行事务](ql-reference.multiplestatements.transactions.md) 或 [对 PartiQL for DynamoDB 运行批处理操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [语法](#ql-reference.insert.syntax)
+ [参数](#ql-reference.insert.parameters)
+ [返回值](#ql-reference.insert.return)
+ [示例](#ql-reference.insert.examples)

## 语法
<a name="ql-reference.insert.syntax"></a>

插入单个项目。

```
INSERT INTO table VALUE item
```

## 参数
<a name="ql-reference.insert.parameters"></a>

***表*\$1**  
（必需）要在其中插入数据的表。表必须已经存在。

***item***  
（必需）表示为 [PartiQL tuple](https://partiql.org/docs.html) 的有效 DynamoDB 项目。您必须仅指定*一个*项目，项目中的每个属性名称都区分大小写，并且可以用*单*引号 (`'...'`) 在 PartiQL 中表示。  
字符串值也用*单*引号 (`'...'`) 在 PartiQL 中表示。

## 返回值
<a name="ql-reference.insert.return"></a>

此语句不返回任何值。

**注意**  
如果 DynamoDB 表中已具有与要插入项目的主键相同的项目，则返回 `DuplicateItemException`。

## 示例
<a name="ql-reference.insert.examples"></a>

```
INSERT INTO "Music" value {'Artist' : 'Acme Band','SongTitle' : 'PartiQL Rocks'}
```

# 将 PartiQL 函数和 DynamoDB 结合使用
<a name="ql-functions"></a>

Amazon DynamoDB 中的 PartiQL 支持以下 SQL 标准函数的内置版本。

**注意**  
DynamoDB 当前不支持任何未包含在此列表中的 SQL 函数。

## 聚合函数
<a name="ql-functions.aggregate"></a>
+ [将 SIZE 函数与 PartiQL for Amazon DynamoDB 结合使用](ql-functions.size.md)

## 条件函数
<a name="ql-functions.conditional"></a>
+ [将 EXISTS 函数与 PartiQL for DynamoDB 结合使用](ql-functions.exists.md)
+ [将 ATTRIBUTE\$1TYPE 函数与 PartiQL for DynamoDB 结合使用](ql-functions.attribute_type.md)
+ [将 BEGINS\$1WITH 函数与 PartiQL for DynamoDB 结合使用](ql-functions.beginswith.md)
+ [将 CONTAINS 函数与 PartiQL for DynamoDB 结合使用](ql-functions.contains.md)
+ [将 MISSING 函数与 PartiQL for DynamoDB 结合使用](ql-functions.missing.md)

# 将 EXISTS 函数与 PartiQL for DynamoDB 结合使用
<a name="ql-functions.exists"></a>

您可以使用 EXISTS 来执行与 [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems) API 的 `ConditionCheck` 相同的功能。EXISTS 函数只能在事务中使用。

给定一个值，如果该值是非空集合则返回 `TRUE`。否则返回 `FALSE`。

**注意**  
此函数只能用于事务操作。

## 语法
<a name="ql-functions.exists.syntax"></a>

```
EXISTS ( statement )
```

## Arguments
<a name="ql-functions.exists.arguments"></a>

*语句*  
（必需）函数计算的 SELECT 语句。  
SELECT 语句必须指定完整主键和另一个条件。

## 返回类型
<a name="ql-functions.exists.return-type"></a>

`bool`

## 示例
<a name="ql-functions.exists.examples"></a>

```
EXISTS(
    SELECT * FROM "Music" 
    WHERE "Artist" = 'Acme Band' AND "SongTitle" = 'PartiQL Rocks')
```

# 将 BEGINS\$1WITH 函数与 PartiQL for DynamoDB 结合使用
<a name="ql-functions.beginswith"></a>

如果指定的属性以特定子字符串开头，则返回 `TRUE`。

## 语法
<a name="ql-functions.beginswith.syntax"></a>

```
begins_with(path, value )
```

## Arguments
<a name="ql-functions.beginswith.arguments"></a>

*path*  
（必需）要使用的属性名称或文档路径。

*值*  
（必需）要搜索的字符串。

## 返回类型
<a name="ql-functions.beginswith.return-type"></a>

`bool`

## 示例
<a name="ql-functions.beginswith.examples"></a>

```
SELECT * FROM "Orders" WHERE "OrderID"=1 AND begins_with("Address", '7834 24th')
```

# 将 MISSING 函数与 PartiQL for DynamoDB 结合使用
<a name="ql-functions.missing"></a>

如果项目不包含指定的属性，则返回 `TRUE`。此函数只能使用相等和不等运算符。

## 语法
<a name="ql-functions.missing.syntax"></a>

```
 attributename IS | IS NOT  MISSING 
```

## Arguments
<a name="ql-functions.missing.arguments"></a>

*attributename*  
（必需）要查找的属性名称。

## 返回类型
<a name="ql-functions.missing.return-type"></a>

`bool`

## 示例
<a name="ql-functions.missing.examples"></a>

```
SELECT * FROM Music WHERE "Awards" is MISSING
```

# 将 ATTRIBUTE\$1TYPE 函数与 PartiQL for DynamoDB 结合使用
<a name="ql-functions.attribute_type"></a>

如果指定路径中的属性为特定数据类型，则返回 `TRUE`。

## 语法
<a name="ql-functions.attribute_type.syntax"></a>

```
attribute_type( attributename, type )
```

## Arguments
<a name="ql-functions.attribute_type.arguments"></a>

*attributename*  
（必需）要使用的属性名称。

*类型*  
（必需）要检查的属性类型。有关有效值的列表，请参阅 DynamoDB [attribute\$1type](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)。

## 返回类型
<a name="ql-functions.attribute_type.return-type"></a>

`bool`

## 示例
<a name="ql-functions.attribute_type.examples"></a>

```
SELECT * FROM "Music" WHERE attribute_type("Artist", 'S')
```

# 将 CONTAINS 函数与 PartiQL for DynamoDB 结合使用
<a name="ql-functions.contains"></a>

如果路径指定的属性为以下之一，则返回 `TRUE`：
+ 一个包含特定子字符串的字符串。
+ 一个包含集中某个特定元素的集合。

有关更多信息，请参阅 DynamoDB [contains](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions) 函数。

## 语法
<a name="ql-functions.contains.syntax"></a>

```
contains( path, substring )
```

## Arguments
<a name="ql-functions.contains.arguments"></a>

*path*  
（必需）要使用的属性名称或文档路径。

*substring*  
（必需）要检查的属性子字符串或集合成员。有关更多信息，请参阅 DynamoDB [contains](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions) 函数。

## 返回类型
<a name="ql-functions.contains.return-type"></a>

`bool`

## 示例
<a name="ql-functions.contains.examples"></a>

```
SELECT * FROM "Orders" WHERE "OrderID"=1 AND contains("Address", 'Kirkland')
```

# 将 SIZE 函数与 PartiQL for Amazon DynamoDB 结合使用
<a name="ql-functions.size"></a>

返回一个代表属性字节大小的数字。以下是与 size 结合使用的有效数据类型。有关更多信息，请参阅 DynamoDB [size](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions) 函数。

## 语法
<a name="ql-functions.size.syntax"></a>

```
size( path)
```

## Arguments
<a name="ql-functions.size.arguments"></a>

*path*  
（必需）属性名称或文档路径。  
有关受支持的类型，请参阅 DynamoDB [size](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions) 函数。

## 返回类型
<a name="ql-functions.size.return-type"></a>

`int`

## 示例
<a name="ql-functions.size.examples"></a>

```
 SELECT * FROM "Orders" WHERE "OrderID"=1 AND size("Image") >300
```

# 用于 DynamoDB 的 PartiQL 算术、比较和逻辑运算符
<a name="ql-operators"></a>

Amazon DynamoDB 中的 PartiQL 支持以下 [SQL 标准运算符](https://www.w3schools.com/sql/sql_operators.asp)。

**注意**  
DynamoDB 当前不支持任何未包含在此列表中的 SQL 运算符。

## 算术运算符
<a name="ql-operators.arithmetic"></a>


****  

| 运算符 | 说明 | 
| --- | --- | 
| \$1 | 添加 | 
| - | Subtract | 

## 比较运算符
<a name="ql-operators.comparison"></a>


****  

| 运算符 | 说明 | 
| --- | --- | 
| = | 等于 | 
| <> | 不等于 | 
| \$1= | 不等于 | 
| > | Greater than | 
| < | Less than | 
| >= | 大于或等于 | 
| <= | 小于或等于 | 

## 逻辑运算符
<a name="ql-operators.logical"></a>


****  

| 运算符 | 说明 | 
| --- | --- | 
| AND | 如果 AND 分隔的所有条件都为 TRUE，则为 TRUE | 
| BETWEEN |  如果操作数在比较范围内，则为 `TRUE`。 此运算符包括您对其应用的操作数的下限和上限。  | 
| IN | 如果操作数等于表达式列表的其中之一（最大 50 个哈希属性值或最大 100 个非键属性值），则为 `TRUE`。 结果分页返回，每页最多 10 个项目。如果 `IN` 列表包含更多值，则必须使用响应中返回的 `NextToken` 来检索后续页面。 | 
| IS | 如果运算数是给定 PartiQL 数据类型，包括 NULL 或 MISSING，则为 TRUE | 
| NOT | 反转给定布尔表达式的值 | 
| OR | 如果 OR 分隔的任意条件为 TRUE，则为 TRUE | 

有关使用逻辑运算符的更多信息，请参阅[进行比较](Expressions.OperatorsAndFunctions.md#Expressions.OperatorsAndFunctions.Comparators)和[逻辑评估](Expressions.OperatorsAndFunctions.md#Expressions.OperatorsAndFunctions.LogicalEvaluations)。

# 使用 PartiQL for DynamoDB 执行事务
<a name="ql-reference.multiplestatements.transactions"></a>

本部分介绍如何使用事务和 PartiQL for DynamoDB。PartiQL 事务限制为总共 100 条语句（操作）。

有关 DynamoDB 事务的更多信息，请参阅[使用 DynamoDB 事务管理复杂工作流](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transactions.html)。

**注意**  
整个事务必须由读取语句或写语句组成。您不能在一个事务中混合使用这两个语句。EXISTS 函数是一个例外。可用于检查项目的特定属性的条件，类似于 [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems) API 操作中 `ConditionCheck` 的方式。

**Topics**
+ [语法](#ql-reference.multiplestatements.transactions.syntax)
+ [参数](#ql-reference.multiplestatements.transactions.parameters)
+ [返回值](#ql-reference.multiplestatements.transactions.return)
+ [示例](#ql-reference.multiplestatements.transactions.examples)

## 语法
<a name="ql-reference.multiplestatements.transactions.syntax"></a>

```
[
   {
      "Statement":" statement ",
      "Parameters":[
         {
            " parametertype " : " parametervalue "
         }, ...]
   } , ...
]
```

## 参数
<a name="ql-reference.multiplestatements.transactions.parameters"></a>

***语句***  
（必需）PartiQL for DynamoDB 支持的语句。  
整个事务必须由读取语句或写语句组成。您不能在一个事务中混合使用这两个语句。

***parametertype***  
（可选）DynamoDB 类型，如果在指定 PartiQL 语句时使用了参数。

***parametervalue***  
（可选）如果在指定 PartiQL 语句时使用了参数，则为参数值。

## 返回值
<a name="ql-reference.multiplestatements.transactions.return"></a>

此语句不会返回写入操作（INSERT、UPDATE 或 DELETE）的任何值。但是，根据 WHERE 子句中指定的条件，它会为读取操作 (SELECT) 返回不同的值。

**注意**  
如果任何单例 INSERT、UPDATE 或 DELETE 操作返回错误，则取消事务并抛出 `TransactionCanceledException` 异常，取消原因代码包括来自各个单例操作的错误。

## 示例
<a name="ql-reference.multiplestatements.transactions.examples"></a>

以下示例运行作为事务的多条语句。

------
#### [ AWS CLI ]

1. 将以下 JSON 代码保存到名为 partiql.json 的文件 

   ```
   [
       {
           "Statement": "EXISTS(SELECT * FROM \"Music\" where Artist='No One You Know' and SongTitle='Call Me Today' and Awards is  MISSING)"
       },
       {
           "Statement": "INSERT INTO Music value {'Artist':?,'SongTitle':'?'}",
           "Parameters": [{\"S\": \"Acme Band\"}, {\"S\": \"Best Song\"}]
       },
       {
           "Statement": "UPDATE \"Music\" SET AwardsWon=1 SET AwardDetail={'Grammys':[2020, 2018]}  where Artist='Acme Band' and SongTitle='PartiQL Rocks'"
       }
   ]
   ```

1. 在命令提示符中运行以下命令。

   ```
   aws dynamodb execute-transaction --transact-statements  file://partiql.json
   ```

------
#### [ Java ]

```
public class DynamoDBPartiqlTransaction {

    public static void main(String[] args) {
        // Create the DynamoDB Client with the region you want
        AmazonDynamoDB dynamoDB = createDynamoDbClient("us-west-2");
        
        try {
            // Create ExecuteTransactionRequest
            ExecuteTransactionRequest executeTransactionRequest = createExecuteTransactionRequest();
            ExecuteTransactionResult executeTransactionResult = dynamoDB.executeTransaction(executeTransactionRequest);
            System.out.println("ExecuteTransaction successful.");
            // Handle executeTransactionResult

        } catch (Exception e) {
            handleExecuteTransactionErrors(e);
        }
    }

    private static AmazonDynamoDB createDynamoDbClient(String region) {
        return AmazonDynamoDBClientBuilder.standard().withRegion(region).build();
    }

    private static ExecuteTransactionRequest createExecuteTransactionRequest() {
        ExecuteTransactionRequest request = new ExecuteTransactionRequest();
        
        // Create statements
        List<ParameterizedStatement> statements = getPartiQLTransactionStatements();

        request.setTransactStatements(statements);
        return request;
    }

    private static List<ParameterizedStatement> getPartiQLTransactionStatements() {
        List<ParameterizedStatement> statements = new ArrayList<ParameterizedStatement>();

        statements.add(new ParameterizedStatement()
                               .withStatement("EXISTS(SELECT * FROM "Music" where Artist='No One You Know' and SongTitle='Call Me Today' and Awards is  MISSING)"));

        statements.add(new ParameterizedStatement()
                               .withStatement("INSERT INTO "Music" value {'Artist':'?','SongTitle':'?'}")
                               .withParameters(new AttributeValue("Acme Band"),new AttributeValue("Best Song")));

        statements.add(new ParameterizedStatement()
                               .withStatement("UPDATE "Music" SET AwardsWon=1 SET AwardDetail={'Grammys':[2020, 2018]}  where Artist='Acme Band' and SongTitle='PartiQL Rocks'"));

        return statements;
    }

    // Handles errors during ExecuteTransaction execution. Use recommendations in error messages below to add error handling specific to 
    // your application use-case.
    private static void handleExecuteTransactionErrors(Exception exception) {
        try {
            throw exception;
        } catch (TransactionCanceledException tce) {
            System.out.println("Transaction Cancelled, implies a client issue, fix before retrying. Error: " + tce.getErrorMessage());
        } catch (TransactionInProgressException tipe) {
            System.out.println("The transaction with the given request token is already in progress, consider changing " +
                "retry strategy for this type of error. Error: " + tipe.getErrorMessage());
        } catch (IdempotentParameterMismatchException ipme) {
            System.out.println("Request rejected because it was retried with a different payload but with a request token that was already used, " +
                "change request token for this payload to be accepted. Error: " + ipme.getErrorMessage());
        } catch (Exception e) {
            handleCommonErrors(e);
        }
    }

    private static void handleCommonErrors(Exception exception) {
        try {
            throw exception;
        } catch (InternalServerErrorException isee) {
            System.out.println("Internal Server Error, generally safe to retry with exponential back-off. Error: " + isee.getErrorMessage());
        } catch (RequestLimitExceededException rlee) {
            System.out.println("Throughput exceeds the current throughput limit for your account, increase account level throughput before " + 
                "retrying. Error: " + rlee.getErrorMessage());
        } catch (ProvisionedThroughputExceededException ptee) {
            System.out.println("Request rate is too high. If you're using a custom retry strategy make sure to retry with exponential back-off. " +
                "Otherwise consider reducing frequency of requests or increasing provisioned capacity for your table or secondary index. Error: " + 
                ptee.getErrorMessage());
        } catch (ResourceNotFoundException rnfe) {
            System.out.println("One of the tables was not found, verify table exists before retrying. Error: " + rnfe.getErrorMessage());
        } catch (AmazonServiceException ase) {
            System.out.println("An AmazonServiceException occurred, indicates that the request was correctly transmitted to the DynamoDB " + 
                "service, but for some reason, the service was not able to process it, and returned an error response instead. Investigate and " +
                "configure retry strategy. Error type: " + ase.getErrorType() + ". Error message: " + ase.getErrorMessage());
        } catch (AmazonClientException ace) {
            System.out.println("An AmazonClientException occurred, indicates that the client was unable to get a response from DynamoDB " +
                "service, or the client was unable to parse the response from the service. Investigate and configure retry strategy. "+
                "Error: " + ace.getMessage());
        } catch (Exception e) {
            System.out.println("An exception occurred, investigate and configure retry strategy. Error: " + e.getMessage());
        }
    }

}
```

------

以下示例显示了 DynamoDB 读取具有 WHERE 子句中所指定不同条件的项目时的不同返回值。

------
#### [ AWS CLI ]

1. 将以下 JSON 代码保存到名为 partiql.json 的文件

   ```
   [
       // Item exists and projected attribute exists
       {
           "Statement": "SELECT * FROM "Music" WHERE Artist='No One You Know' and SongTitle='Call Me Today'"
       },
       // Item exists but projected attributes do not exist
       {
           "Statement": "SELECT non_existent_projected_attribute FROM "Music" WHERE Artist='No One You Know' and SongTitle='Call Me Today'"
       },
       // Item does not exist
       {
           "Statement": "SELECT * FROM "Music" WHERE Artist='No One I Know' and SongTitle='Call You Today'"
       }
   ]
   ```

1.  命令提示符中的以下命令。

   ```
   aws dynamodb execute-transaction --transact-statements  file://partiql.json
   ```

1. 如果成功，将返回以下响应。

   ```
   {
       "Responses": [
           // Item exists and projected attribute exists
           {
               "Item": {
                   "Artist":{
                       "S": "No One You Know"
                   },
                   "SongTitle":{
                       "S": "Call Me Today"
                   }    
               }
           },
           // Item exists but projected attributes do not exist
           {
               "Item": {}
           },
           // Item does not exist
           {}
       ]
   }
   ```

------

# 对 PartiQL for DynamoDB 运行批处理操作
<a name="ql-reference.multiplestatements.batching"></a>

本部分介绍如何使用处理器操作和 PartiQL for DynamoDB。

**注意**  
整个批处理必须由读取语句或写入语句组成；不能在一个批处理中混合使用这两种语句。
`BatchExecuteStatement` 和 `BatchWriteItem` 每批可执行的语句不超过 25 个。
`BatchExecuteStatement` 利用 `BatchGetItem`，后者在单独的语句中获取主键的列表。

**Topics**
+ [语法](#ql-reference.multiplestatements.batching.syntax)
+ [参数](#ql-reference.multiplestatements.batching.parameters)
+ [示例](#ql-reference.multiplestatements.batching.examples)

## 语法
<a name="ql-reference.multiplestatements.batching.syntax"></a>

```
[
  {
    "Statement": "SELECT pk FROM ProblemSet WHERE pk = 'p#9StkWHYTxm7x2AqSXcrfu7' AND sk = 'info'"
  },
  {
    "Statement": "SELECT pk FROM ProblemSet WHERE pk = 'p#isC2ChceGbxHgESc4szoTE' AND sk = 'info'"
  }
]
```

```
[
   {
      "Statement":" statement ",
      "Parameters":[
         {
            " parametertype " : " parametervalue "
         }, ...]
   } , ...
]
```

## 参数
<a name="ql-reference.multiplestatements.batching.parameters"></a>

***语句***  
（必需）PartiQL for DynamoDB 支持的语句。  
+ 整个批处理必须由读取语句或写入语句组成；不能在一个批处理中混合使用这两种语句。
+ `BatchExecuteStatement` 和 `BatchWriteItem` 每批可执行的语句不超过 25 个。

***parametertype***  
（可选）DynamoDB 类型，如果在指定 PartiQL 语句时使用了参数。

***parametervalue***  
（可选）如果在指定 PartiQL 语句时使用了参数，则为参数值。

## 示例
<a name="ql-reference.multiplestatements.batching.examples"></a>

------
#### [ AWS CLI ]

1. 将以下 json 保存到一个名为 partiql.json 的文件

   ```
   [
      {
   	 "Statement": "INSERT INTO Music VALUE {'Artist':?,'SongTitle':?}",
   	  "Parameters": [{"S": "Acme Band"}, {"S": "Best Song"}]
   	},
   	{
   	 "Statement": "UPDATE Music SET AwardsWon=1, AwardDetail={'Grammys':[2020, 2018]} WHERE Artist='Acme Band' AND SongTitle='PartiQL Rocks'"
       }
   ]
   ```

1. 在命令提示符中运行以下命令。

   ```
   aws dynamodb batch-execute-statement  --statements  file://partiql.json
   ```

------
#### [ Java ]

```
public class DynamoDBPartiqlBatch {

    public static void main(String[] args) {
        // Create the DynamoDB Client with the region you want
        AmazonDynamoDB dynamoDB = createDynamoDbClient("us-west-2");
        
        try {
            // Create BatchExecuteStatementRequest
            BatchExecuteStatementRequest batchExecuteStatementRequest = createBatchExecuteStatementRequest();
            BatchExecuteStatementResult batchExecuteStatementResult = dynamoDB.batchExecuteStatement(batchExecuteStatementRequest);
            System.out.println("BatchExecuteStatement successful.");
            // Handle batchExecuteStatementResult

        } catch (Exception e) {
            handleBatchExecuteStatementErrors(e);
        }
    }

    private static AmazonDynamoDB createDynamoDbClient(String region) {

        return AmazonDynamoDBClientBuilder.standard().withRegion(region).build();
    }

    private static BatchExecuteStatementRequest createBatchExecuteStatementRequest() {
        BatchExecuteStatementRequest request = new BatchExecuteStatementRequest();

        // Create statements
        List<BatchStatementRequest> statements = getPartiQLBatchStatements();

        request.setStatements(statements);
        return request;
    }

    private static List<BatchStatementRequest> getPartiQLBatchStatements() {
        List<BatchStatementRequest> statements = new ArrayList<BatchStatementRequest>();

        statements.add(new BatchStatementRequest()
                               .withStatement("INSERT INTO Music value {'Artist':'Acme Band','SongTitle':'PartiQL Rocks'}"));

        statements.add(new BatchStatementRequest()
                               .withStatement("UPDATE Music set AwardDetail.BillBoard=[2020] where Artist='Acme Band' and SongTitle='PartiQL Rocks'"));

        return statements;
    }

    // Handles errors during BatchExecuteStatement execution. Use recommendations in error messages below to add error handling specific to 
    // your application use-case.
    private static void handleBatchExecuteStatementErrors(Exception exception) {
        try {
            throw exception;
        } catch (Exception e) {
            // There are no API specific errors to handle for BatchExecuteStatement, common DynamoDB API errors are handled below
            handleCommonErrors(e);
        }
    }

    private static void handleCommonErrors(Exception exception) {
        try {
            throw exception;
        } catch (InternalServerErrorException isee) {
            System.out.println("Internal Server Error, generally safe to retry with exponential back-off. Error: " + isee.getErrorMessage());
        } catch (RequestLimitExceededException rlee) {
            System.out.println("Throughput exceeds the current throughput limit for your account, increase account level throughput before " + 
                "retrying. Error: " + rlee.getErrorMessage());
        } catch (ProvisionedThroughputExceededException ptee) {
            System.out.println("Request rate is too high. If you're using a custom retry strategy make sure to retry with exponential back-off. " +
                "Otherwise consider reducing frequency of requests or increasing provisioned capacity for your table or secondary index. Error: " + 
                ptee.getErrorMessage());
        } catch (ResourceNotFoundException rnfe) {
            System.out.println("One of the tables was not found, verify table exists before retrying. Error: " + rnfe.getErrorMessage());
        } catch (AmazonServiceException ase) {
            System.out.println("An AmazonServiceException occurred, indicates that the request was correctly transmitted to the DynamoDB " + 
                "service, but for some reason, the service was not able to process it, and returned an error response instead. Investigate and " +
                "configure retry strategy. Error type: " + ase.getErrorType() + ". Error message: " + ase.getErrorMessage());
        } catch (AmazonClientException ace) {
            System.out.println("An AmazonClientException occurred, indicates that the client was unable to get a response from DynamoDB " +
                "service, or the client was unable to parse the response from the service. Investigate and configure retry strategy. "+
                "Error: " + ace.getMessage());
        } catch (Exception e) {
            System.out.println("An exception occurred, investigate and configure retry strategy. Error: " + e.getMessage());
        }
    }

}
```

------

# 将 IAM 安全策略与 PartiQL for DynamoDB 结合使用
<a name="ql-iam"></a>

需要以下权限：
+ 若要使用 PartiQL for DynamoDB 读取项目，您必须具有表或索引的 `dynamodb:PartiQLSelect` 权限。
+ 若要使用 PartiQL for DynamoDB 插入项目，您必须具有表或索引的 `dynamodb:PartiQLInsert` 权限。
+ 若要使用 PartiQL for DynamoDB 更新项目，您必须具有表或索引的 `dynamodb:PartiQLUpdate` 权限。
+ 若要使用 PartiQL for DynamoDB 删除项目，您必须具有表或索引的 `dynamodb:PartiQLDelete` 权限。

## 示例：允许表上所有 PartiQL for DynamoDB 语句 (Select/Insert/Update/Delete)
<a name="access-policy-ql-iam-example1"></a>

下面的 IAM policy 授予对表运行所有 PartiQL for DynamoDB 语句的权限。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLInsert",
            "dynamodb:PartiQLUpdate",
            "dynamodb:PartiQLDelete",
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ]
      }
   ]
}
```

------

## 示例：允许表上的 PartiQL for DynamoDB select 语句
<a name="access-policy-ql-iam-example2"></a>

下面的 IAM policy 授予在特定表运行 `select` 语句的权限。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ]
      }
   ]
}
```

------

## 示例：允许在索引上运行 PartiQL for DynamoDB insert 语句
<a name="access-policy-ql-iam-example3"></a>

下面的 IAM policy 授予在特定索引运行 `insert` 语句的权限。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLInsert"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music/index/index1"
         ]
      }
   ]
}
```

------

## 示例：仅允许表上运行 PartiQL for DynamoDB 语句
<a name="access-policy-ql-iam-example4"></a>

下面的 IAM policy 授予在特定表运行事务语句的权限。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLInsert",
            "dynamodb:PartiQLUpdate",
            "dynamodb:PartiQLDelete",
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ],
         "Condition":{
            "StringEquals":{
               "dynamodb:EnclosingOperation":[
                  "ExecuteTransaction"
               ]
            }
         }
      }
   ]
}
```

------

## 示例：允许运行 PartiQL for DynamoDB 非事务性读取和写入，阻止表上的 PartiQL 事务性读取和写入事务性语句。
<a name="access-policy-ql-iam-example5"></a>

 下面的 IAM policy 授予运行 PartiQL for DynamoDB 非事务性读取和写入的权限，同时阻止 PartiQL for DynamoDB 事务性读取和写入。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Deny",
         "Action":[
            "dynamodb:PartiQLInsert",
            "dynamodb:PartiQLUpdate",
            "dynamodb:PartiQLDelete",
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ],
         "Condition":{
            "StringEquals":{
               "dynamodb:EnclosingOperation":[
                  "ExecuteTransaction"
               ]
            }
         }
      },
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLInsert",
            "dynamodb:PartiQLUpdate",
            "dynamodb:PartiQLDelete",
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ]
      }
   ]
}
```

------

## 示例：允许 Select 语句并拒绝 PartiQL for DynamoDB 的完整表扫描语句
<a name="access-policy-ql-iam-example6"></a>

下面的 IAM policy 授予在特定表运行 `select` 语句的权限，同时阻止会导致完整表扫描的 `select` 语句。

------
#### [ JSON ]

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Deny",
         "Action":[
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/WatchList"
         ],
         "Condition":{
            "Bool":{
               "dynamodb:FullTableScan":[
                  "true"
               ]
            }
         }
      },
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/WatchList"
         ]
      }
   ]
}
```

------

# 处理项目：Java
<a name="JavaDocumentAPIItemCRUD"></a>

您可以使用 适用于 Java 的 AWS SDK 文档 API 对某个表中的 Amazon DynamoDB 项目执行典型的创建、读取、更新和删除 (CRUD) 操作。

**注意**  
还提供一个对象持久化模型，可用来将客户端类映射到 DynamoDB 表。该方法可以减少需要编写的代码数量。有关更多信息，请参阅 [Java 1.x：DynamoDBMapper](DynamoDBMapper.md)。

此部分包含执行多个 Java 文档 API 项目操作的 Java 示例和多个可完全工作的示例。

**Topics**
+ [放置项目](#PutDocumentAPIJava)
+ [获取项目](#JavaDocumentAPIGetItem)
+ [批处理写入：放置和删除多个项目](#BatchWriteDocumentAPIJava)
+ [批处理获取：获取多个项目](#JavaDocumentAPIBatchGetItem)
+ [更新项目](#JavaDocumentAPIItemUpdate)
+ [删除项目](#DeleteMidLevelJava)
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 的 CRUD 操作](JavaDocumentAPICRUDExample.md)
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理操作](batch-operation-document-api-java.md)
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 处理二进制类型属性](JavaDocumentAPIBinaryTypeExample.md)

## 放置项目
<a name="PutDocumentAPIJava"></a>

`putItem` 方法能将项目存储在表中。如果项目已存在，则会替换整个项目。如果您不想替换整个项目，而只希望更新特定属性，那么您可以使用 `updateItem` 方法。有关更多信息，请参阅 [更新项目](#JavaDocumentAPIItemUpdate)。

------
#### [ Java v2 ]

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.PutItemResponse;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import java.util.HashMap;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 *
 * To place items into an Amazon DynamoDB table using the AWS SDK for Java V2,
 * its better practice to use the
 * Enhanced Client. See the EnhancedPutItem example.
 */
public class PutItem {
    public static void main(String[] args) {
        final String usage = """

                Usage:
                    <tableName> <key> <keyVal> <albumtitle> <albumtitleval> <awards> <awardsval> <Songtitle> <songtitleval>

                Where:
                    tableName - The Amazon DynamoDB table in which an item is placed (for example, Music3).
                    key - The key used in the Amazon DynamoDB table (for example, Artist).
                    keyval - The key value that represents the item to get (for example, Famous Band).
                    albumTitle - The Album title (for example, AlbumTitle).
                    AlbumTitleValue - The name of the album (for example, Songs About Life ).
                    Awards - The awards column (for example, Awards).
                    AwardVal - The value of the awards (for example, 10).
                    SongTitle - The song title (for example, SongTitle).
                    SongTitleVal - The value of the song title (for example, Happy Day).
                **Warning** This program will  place an item that you specify into a table!
                """;

        if (args.length != 9) {
            System.out.println(usage);
            System.exit(1);
        }

        String tableName = args[0];
        String key = args[1];
        String keyVal = args[2];
        String albumTitle = args[3];
        String albumTitleValue = args[4];
        String awards = args[5];
        String awardVal = args[6];
        String songTitle = args[7];
        String songTitleVal = args[8];

        Region region = Region.US_EAST_1;
        DynamoDbClient ddb = DynamoDbClient.builder()
                .region(region)
                .build();

        putItemInTable(ddb, tableName, key, keyVal, albumTitle, albumTitleValue, awards, awardVal, songTitle,
                songTitleVal);
        System.out.println("Done!");
        ddb.close();
    }

    public static void putItemInTable(DynamoDbClient ddb,
            String tableName,
            String key,
            String keyVal,
            String albumTitle,
            String albumTitleValue,
            String awards,
            String awardVal,
            String songTitle,
            String songTitleVal) {

        HashMap<String, AttributeValue> itemValues = new HashMap<>();
        itemValues.put(key, AttributeValue.builder().s(keyVal).build());
        itemValues.put(songTitle, AttributeValue.builder().s(songTitleVal).build());
        itemValues.put(albumTitle, AttributeValue.builder().s(albumTitleValue).build());
        itemValues.put(awards, AttributeValue.builder().s(awardVal).build());

        PutItemRequest request = PutItemRequest.builder()
                .tableName(tableName)
                .item(itemValues)
                .build();

        try {
            PutItemResponse response = ddb.putItem(request);
            System.out.println(tableName + " was successfully updated. The request id is "
                    + response.responseMetadata().requestId());

        } catch (ResourceNotFoundException e) {
            System.err.format("Error: The Amazon DynamoDB table \"%s\" can't be found.\n", tableName);
            System.err.println("Be sure that it exists and that you've typed its name correctly!");
            System.exit(1);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
}
```

------
#### [ Java v1 ]

按照以下步骤进行操作：

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例来代表要处理的表。

1. 创建 `Item` 类的实例来代表新项目。必须指定新项目的主键及其属性。

1. 使用您在之前步骤中创建的 `putItem`，调用 `Table` 对象的 `Item` 方法。

以下 Java 代码示例演示了上述任务。代码将新项目写入 `ProductCatalog` 表。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("ProductCatalog");

// Build a list of related items
List<Number> relatedItems = new ArrayList<Number>();
relatedItems.add(341);
relatedItems.add(472);
relatedItems.add(649);

//Build a map of product pictures
Map<String, String> pictures = new HashMap<String, String>();
pictures.put("FrontView", "http://example.com/products/123_front.jpg");
pictures.put("RearView", "http://example.com/products/123_rear.jpg");
pictures.put("SideView", "http://example.com/products/123_left_side.jpg");

//Build a map of product reviews
Map<String, List<String>> reviews = new HashMap<String, List<String>>();

List<String> fiveStarReviews = new ArrayList<String>();
fiveStarReviews.add("Excellent! Can't recommend it highly enough!  Buy it!");
fiveStarReviews.add("Do yourself a favor and buy this");
reviews.put("FiveStar", fiveStarReviews);

List<String> oneStarReviews = new ArrayList<String>();
oneStarReviews.add("Terrible product!  Do not buy this.");
reviews.put("OneStar", oneStarReviews);

// Build the item
Item item = new Item()
    .withPrimaryKey("Id", 123)
    .withString("Title", "Bicycle 123")
    .withString("Description", "123 description")
    .withString("BicycleType", "Hybrid")
    .withString("Brand", "Brand-Company C")
    .withNumber("Price", 500)
    .withStringSet("Color",  new HashSet<String>(Arrays.asList("Red", "Black")))
    .withString("ProductCategory", "Bicycle")
    .withBoolean("InStock", true)
    .withNull("QuantityOnHand")
    .withList("RelatedItems", relatedItems)
    .withMap("Pictures", pictures)
    .withMap("Reviews", reviews);

// Write the item to the table
PutItemOutcome outcome = table.putItem(item);
```

在上述示例中，项目具有标量（`String`、`Number`、`Boolean`、`Null`）、集 (`String Set`) 和文档类型（`List`、`Map`）的属性。

------

### 指定可选参数
<a name="PutItemJavaDocumentAPIOptions"></a>

除了必需的参数之外，您还可以为 `putItem` 方法指定可选参数。例如，以下 Java 代码示例使用可选参数指定上传项目的条件。如果未满足指定条件，那么 适用于 Java 的 AWS SDK 会抛出 `ConditionalCheckFailedException`。该代码示例在 `putItem` 方法中指定了以下可选参数：
+ 定义请求条件的 `ConditionExpression`。该代码定义了一个条件，仅在具有同一主键的现有项目包含与特定值相等的 ISBN 属性时，才替换该项目。
+ 将在条件中使用的 `ExpressionAttributeValues` 的映射。在这种情况下，只需要一个替换：条件表达式中的占位符 `:val` 在运行时被替换为要检查的实际 ISBN 值。

以下示例使用这些可选参数添加一个新的图书项目。

**Example**  

```
Item item = new Item()
    .withPrimaryKey("Id", 104)
    .withString("Title", "Book 104 Title")
    .withString("ISBN", "444-4444444444")
    .withNumber("Price", 20)
    .withStringSet("Authors",
        new HashSet<String>(Arrays.asList("Author1", "Author2")));

Map<String, Object> expressionAttributeValues = new HashMap<String, Object>();
expressionAttributeValues.put(":val", "444-4444444444");

PutItemOutcome outcome = table.putItem(
    item,
    "ISBN = :val", // ConditionExpression parameter
    null,          // ExpressionAttributeNames parameter - we're not using it for this example
    expressionAttributeValues);
```

### PutItem 和 JSON 文档
<a name="PutItemJavaDocumentAPI.JSON"></a>

您可以将 JSON 文档作为属性存储在 DynamoDB 表中。为此，请使用 `withJSON` 的 `Item` 方法。此方法会解析 JSON 文档并将每个元素映射到本地 DynamoDB 数据类型。

假设您想要存储以下 JSON 文档，其中包含可履行特定产品订单的供应商。

**Example**  

```
{
    "V01": {
        "Name": "Acme Books",
        "Offices": [ "Seattle" ]
    },
    "V02": {
        "Name": "New Publishers, Inc.",
        "Offices": ["London", "New York"
        ]
    },
    "V03": {
        "Name": "Better Buy Books",
        "Offices": [ "Tokyo", "Los Angeles", "Sydney"
        ]
    }
}
```

您可以使用 `withJSON` 方法，将此项目存储在 `ProductCatalog` 表的名为 `Map` 的 `VendorInfo` 属性中。以下 Java 代码示例演示了如何执行此操作。

```
// Convert the document into a String.  Must escape all double-quotes.
String vendorDocument = "{"
    + "    \"V01\": {"
    + "        \"Name\": \"Acme Books\","
    + "        \"Offices\": [ \"Seattle\" ]"
    + "    },"
    + "    \"V02\": {"
    + "        \"Name\": \"New Publishers, Inc.\","
    + "        \"Offices\": [ \"London\", \"New York\"" + "]" + "},"
    + "    \"V03\": {"
    + "        \"Name\": \"Better Buy Books\","
    +          "\"Offices\": [ \"Tokyo\", \"Los Angeles\", \"Sydney\""
    + "            ]"
    + "        }"
    + "    }";

Item item = new Item()
    .withPrimaryKey("Id", 210)
    .withString("Title", "Book 210 Title")
    .withString("ISBN", "210-2102102102")
    .withNumber("Price", 30)
    .withJSON("VendorInfo", vendorDocument);

PutItemOutcome outcome = table.putItem(item);
```

## 获取项目
<a name="JavaDocumentAPIGetItem"></a>

要检索单个项目，可以使用 `getItem` 对象的 `Table` 方法。按照以下步骤进行操作：

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例来代表要处理的表。

1. 调用 `getItem` 实例的 `Table` 方法。您必须指定要检索的项目的主键。

以下 Java 代码示例演示了上述步骤。该代码获取具有指定分区键的项目。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("ProductCatalog");

Item item = table.getItem("Id", 210);
```

### 指定可选参数
<a name="GetItemJavaDocumentAPIOptions"></a>

除了必需的参数之外，您还可以为 `getItem` 方法指定可选参数。例如，以下 Java 代码示例使用可选方法仅检索属性的特定列表，并指定强一致性读取。(要了解有关读取一致性的更多信息，请参阅 [DynamoDB 读取一致性](HowItWorks.ReadConsistency.md)。)

您可以使用 `ProjectionExpression` 只检索特定属性或元素，而不是整个项目。`ProjectionExpression` 可以使用文档路径指定顶级或嵌套属性。有关更多信息，请参阅 [在 DynamoDB 中使用投影表达式](Expressions.ProjectionExpressions.md)。

`getItem` 方法的参数不让您指定读取一致性。但是，您可以创建 `GetItemSpec`，这会将对所有输入的完整访问权限提供给低级 `GetItem` 操作。下面的代码示例创建 `GetItemSpec`，并且使用该规范作为 `getItem` 方法的输入。

**Example**  

```
GetItemSpec spec = new GetItemSpec()
    .withPrimaryKey("Id", 206)
    .withProjectionExpression("Id, Title, RelatedItems[0], Reviews.FiveStar")
    .withConsistentRead(true);

Item item = table.getItem(spec);

System.out.println(item.toJSONPretty());
```

 要以人类可读格式输出 `Item`，请使用 `toJSONPretty` 方法。上一例中的输出类似于下文所示。

```
{
  "RelatedItems" : [ 341 ],
  "Reviews" : {
    "FiveStar" : [ "Excellent! Can't recommend it highly enough! Buy it!", "Do yourself a favor and buy this" ]
  },
  "Id" : 123,
  "Title" : "20-Bicycle 123"
}
```

### GetItem 和 JSON 文档
<a name="GetItemJavaDocumentAPI.JSON"></a>

在 [PutItem 和 JSON 文档](#PutItemJavaDocumentAPI.JSON) 部分中，您在名为 `Map` 的 `VendorInfo` 属性中存储了一个 JSON 文档。您可以使用 `getItem` 方法检索 JSON 格式的整个文档。或者，您可以使用文档路径表示来仅检索文档中的部分元素。以下 Java 代码示例演示了这些技术。

```
GetItemSpec spec = new GetItemSpec()
    .withPrimaryKey("Id", 210);

System.out.println("All vendor info:");
spec.withProjectionExpression("VendorInfo");
System.out.println(table.getItem(spec).toJSON());

System.out.println("A single vendor:");
spec.withProjectionExpression("VendorInfo.V03");
System.out.println(table.getItem(spec).toJSON());

System.out.println("First office location for this vendor:");
spec.withProjectionExpression("VendorInfo.V03.Offices[0]");
System.out.println(table.getItem(spec).toJSON());
```

上一例中的输出类似于下文所示。

```
All vendor info:
{"VendorInfo":{"V03":{"Name":"Better Buy Books","Offices":["Tokyo","Los Angeles","Sydney"]},"V02":{"Name":"New Publishers, Inc.","Offices":["London","New York"]},"V01":{"Name":"Acme Books","Offices":["Seattle"]}}}
A single vendor:
{"VendorInfo":{"V03":{"Name":"Better Buy Books","Offices":["Tokyo","Los Angeles","Sydney"]}}}
First office location for a single vendor:
{"VendorInfo":{"V03":{"Offices":["Tokyo"]}}}
```

**注意**  
您可以使用 `toJSON` 方法将任意项目 (或其属性) 转换成 JSON 格式的字符串。以下代码检索多个顶级和嵌套属性，并以 JSON 格式输出结果。  

```
GetItemSpec spec = new GetItemSpec()
    .withPrimaryKey("Id", 210)
    .withProjectionExpression("VendorInfo.V01, Title, Price");

Item item = table.getItem(spec);
System.out.println(item.toJSON());
```
输出如下所示。  

```
{"VendorInfo":{"V01":{"Name":"Acme Books","Offices":["Seattle"]}},"Price":30,"Title":"Book 210 Title"}
```

## 批处理写入：放置和删除多个项目
<a name="BatchWriteDocumentAPIJava"></a>

*批量写入* 是指批量放置和删除多个项目。`batchWriteItem` 方法可让您通过一次 调用即可向一个或多个表中放置或从中删除多个项目。下面是使用 适用于 Java 的 AWS SDK 文档 API 放置或删除多个项目的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建描述表的所有放置和删除操作的 `TableWriteItems` 类的实例。如果要在单个批量写入操作中写入到多个表，您必须为每个表创建一个 `TableWriteItems` 实例。

1. 通过提供您在之前步骤中创建的 `batchWriteItem` 对象，调用 `TableWriteItems` 方法。

1. 处理响应。您应该检查一下响应是否返回未处理的请求项目。如果达到预置吞吐量配额或发生其他临时错误，就可能会出现这种情况。此外，DynamoDB 还对可在请求中指定的请求大小和操作数进行限制。如果超出这些限制，DynamoDB 会拒绝请求。有关更多信息，请参阅 [Amazon DynamoDB 中的配额](ServiceQuotas.md)。

以下 Java 代码示例演示了上述步骤。本示例对两个表执行 `batchWriteItem` 操作：`Forum` 和 `Thread`。相应的 `TableWriteItems` 对象定义以下操作：
+ 在 `Forum` 表中放置一个项目。
+ 对 `Thread` 表放置和删除项目。

然后，代码调用 `batchWriteItem` 来执行操作。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

TableWriteItems forumTableWriteItems = new TableWriteItems("Forum")
    .withItemsToPut(
        new Item()
            .withPrimaryKey("Name", "Amazon RDS")
            .withNumber("Threads", 0));

TableWriteItems threadTableWriteItems = new TableWriteItems("Thread")
    .withItemsToPut(
        new Item()
            .withPrimaryKey("ForumName","Amazon RDS","Subject","Amazon RDS Thread 1")
    .withHashAndRangeKeysToDelete("ForumName","Some partition key value", "Amazon S3", "Some sort key value");

BatchWriteItemOutcome outcome = dynamoDB.batchWriteItem(forumTableWriteItems, threadTableWriteItems);

// Code for checking unprocessed items is omitted in this example
```

要了解可工作的示例，请参阅 [示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理写入操作](batch-operation-document-api-java.md#JavaDocumentAPIBatchWrite)。

## 批处理获取：获取多个项目
<a name="JavaDocumentAPIBatchGetItem"></a>

`batchGetItem` 方法可让您检索一个或多个表中的多个项目。要检索单个项目，您可以使用 `getItem` 方法。

按照以下步骤进行操作：

1. 创建 `DynamoDB` 类的实例。

1. 创建描述要从表中检索的主键值列表的 `TableKeysAndAttributes` 类的实例。如果需要在单个批量获取操作中读取多个表，需要为每个表创建一个 `TableKeysAndAttributes` 实例。

1. 通过提供您在之前步骤中创建的 `batchGetItem` 对象，调用 `TableKeysAndAttributes` 方法。

以下 Java 代码示例演示了上述步骤。示例检索 `Forum` 表中的两个项目和 `Thread` 表中的三个项目。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

    TableKeysAndAttributes forumTableKeysAndAttributes = new TableKeysAndAttributes(forumTableName);
    forumTableKeysAndAttributes.addHashOnlyPrimaryKeys("Name",
    "Amazon S3",
    "Amazon DynamoDB");

TableKeysAndAttributes threadTableKeysAndAttributes = new TableKeysAndAttributes(threadTableName);
threadTableKeysAndAttributes.addHashAndRangePrimaryKeys("ForumName", "Subject",
    "Amazon DynamoDB","DynamoDB Thread 1",
    "Amazon DynamoDB","DynamoDB Thread 2",
    "Amazon S3","S3 Thread 1");

BatchGetItemOutcome outcome = dynamoDB.batchGetItem(
    forumTableKeysAndAttributes, threadTableKeysAndAttributes);

for (String tableName : outcome.getTableItems().keySet()) {
    System.out.println("Items in table " + tableName);
    List<Item> items = outcome.getTableItems().get(tableName);
    for (Item item : items) {
        System.out.println(item);
    }
}
```

### 指定可选参数
<a name="BatchGetItemJavaDocumentAPIOptions"></a>

在使用 `batchGetItem` 时，除了必需的参数之外，您还可以指定可选参数。例如，可以随您定义的每个 `ProjectionExpression` 提供一个 `TableKeysAndAttributes`。这样您可以指定要从表中检索的属性。

以下 C\$1 代码示例从 `Forum` 表检索两个项目。`withProjectionExpression` 参数指定仅检索 `Threads` 属性。

**Example**  

```
TableKeysAndAttributes forumTableKeysAndAttributes = new TableKeysAndAttributes("Forum")
    .withProjectionExpression("Threads");

forumTableKeysAndAttributes.addHashOnlyPrimaryKeys("Name",
    "Amazon S3",
    "Amazon DynamoDB");

BatchGetItemOutcome outcome = dynamoDB.batchGetItem(forumTableKeysAndAttributes);
```

## 更新项目
<a name="JavaDocumentAPIItemUpdate"></a>

`updateItem` 对象的 `Table` 方法可以更新现有属性值，添加新属性，或者从现有项目中删除属性。

`updateItem` 方法的运行机制如下：
+ 如果项目不存在（表中不存在具有指定主键的项目），`updateItem` 会将新项目添加到表中
+ 如果项目存在，`updateItem` 会按照 `UpdateExpression` 参数指定的方式执行更新。

**注意**  
还可以使用 `putItem` 来“更新”项目。例如，如果您调用 `putItem` 向表中添加项目，但是已存在具有指定主键的项目，那么 `putItem` 将替换整个项目。如果现有项目中有属性，并且这些属性未在输入中指定，`putItem` 从项目中删除这些属性。  
通常，在您希望修改任何项目属性时，我们建议您使用 `updateItem`。`updateItem` 方法仅修改您在输入中指定的项目属性，项目中的其他属性将保持不变。

按照以下步骤进行操作：

1. 创建 `Table` 类的实例来代表要处理的表。

1. 调用 `updateTable` 实例的 `Table` 方法。必须指定要检索的项目的主键，同时指定描述要修改的属性以及如何修改这些属性的 `UpdateExpression`。

以下 Java 代码示例演示了上述任务。该代码更新 `ProductCatalog` 表中的书本项目。将新作者添加到 `Authors` 集中，并删除现有 `ISBN` 属性。另外还降低了价格 (-1)。

在 `ExpressionAttributeValues` 中使用 `UpdateExpression` 映射。在运行时，占位符 `:val1` 和 `:val2` 将被替换为 `Authors` 和 `Price` 的实际值。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("ProductCatalog");

Map<String, String> expressionAttributeNames = new HashMap<String, String>();
expressionAttributeNames.put("#A", "Authors");
expressionAttributeNames.put("#P", "Price");
expressionAttributeNames.put("#I", "ISBN");

Map<String, Object> expressionAttributeValues = new HashMap<String, Object>();
expressionAttributeValues.put(":val1",
    new HashSet<String>(Arrays.asList("Author YY","Author ZZ")));
expressionAttributeValues.put(":val2", 1);   //Price

UpdateItemOutcome outcome =  table.updateItem(
    "Id",          // key attribute name
    101,           // key attribute value
    "add #A :val1 set #P = #P - :val2 remove #I", // UpdateExpression
    expressionAttributeNames,
    expressionAttributeValues);
```

### 指定可选参数
<a name="UpdateItemJavaDocumentAPIOptions"></a>

除了必需的参数之外，您还可以为 `updateItem` 方法指定可选参数，其中包括为了执行更新而必须满足的条件。如果未满足指定条件，那么 适用于 Java 的 AWS SDK 会抛出 `ConditionalCheckFailedException`。例如，以下 Java 代码示例有条件地将书本物品价格更新为 25。它指定 `ConditionExpression`，后者声明仅当现有价格为 20 时才应更新价格。

**Example**  

```
Table table = dynamoDB.getTable("ProductCatalog");

Map<String, String> expressionAttributeNames = new HashMap<String, String>();
expressionAttributeNames.put("#P", "Price");

Map<String, Object> expressionAttributeValues = new HashMap<String, Object>();
expressionAttributeValues.put(":val1", 25);  // update Price to 25...
expressionAttributeValues.put(":val2", 20);  //...but only if existing Price is 20

UpdateItemOutcome outcome = table.updateItem(
    new PrimaryKey("Id",101),
    "set #P = :val1", // UpdateExpression
    "#P = :val2",     // ConditionExpression
    expressionAttributeNames,
    expressionAttributeValues);
```

### 原子计数器
<a name="AtomicCounterJavaDocumentAPI"></a>

您可以使用 `updateItem` 实现原子计数器，并使用该计数器来递增或递减现有属性的值而不会干扰其他写入请求。要递增原子计数器，请将 `UpdateExpression` 和 `set` 操作结合使用，以便将数值添加到类型为 `Number` 的现有属性。

以下示例演示了这一用法，将 `Quantity` 属性递增 1。它还演示在 `ExpressionAttributeNames` 中使用的 `UpdateExpression` 参数。

```
Table table = dynamoDB.getTable("ProductCatalog");

Map<String,String> expressionAttributeNames = new HashMap<String,String>();
expressionAttributeNames.put("#p", "PageCount");

Map<String,Object> expressionAttributeValues = new HashMap<String,Object>();
expressionAttributeValues.put(":val", 1);

UpdateItemOutcome outcome = table.updateItem(
    "Id", 121,
    "set #p = #p + :val",
    expressionAttributeNames,
    expressionAttributeValues);
```

## 删除项目
<a name="DeleteMidLevelJava"></a>

`deleteItem` 方法能删除表中的项目。您必须提供要删除的项目的主键。

按照以下步骤进行操作：

1. 创建 `DynamoDB` 客户端的实例。

1. 通过提供要删除的项目的键来调用 `deleteItem` 方法。

以下 Java 示例演示了这些任务。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("ProductCatalog");

DeleteItemOutcome outcome = table.deleteItem("Id", 101);
```

### 指定可选参数
<a name="DeleteItemJavaDocumentAPIOptions"></a>

您可以为 `deleteItem` 指定可选参数。例如，以下 Java 代码示例指定 `ConditionExpression`，声明只有当图书不再位于出版物中（`ProductCatalog` 属性为 false）时，才能删除 `InPublication` 中的图书项目。

**Example**  

```
Map<String,Object> expressionAttributeValues = new HashMap<String,Object>();
expressionAttributeValues.put(":val", false);

DeleteItemOutcome outcome = table.deleteItem("Id",103,
    "InPublication = :val",
    null, // ExpressionAttributeNames - not used in this example
    expressionAttributeValues);
```

# 示例：使用 适用于 Java 的 AWS SDK 文档 API 的 CRUD 操作
<a name="JavaDocumentAPICRUDExample"></a>

以下代码示例介绍对 Amazon DynamoDB 项目的 CRUD 操作。该示例创建一个项目、对其进行检索、执行多种更新，最终删除项目。

**注意**  
SDK for Java 还提供一个对象持久化模型，可用来将客户端类映射到 DynamoDB 表。该方法可以减少需要编写的代码数量。有关更多信息，请参阅 [Java 1.x：DynamoDBMapper](DynamoDBMapper.md)。

**注意**  
此代码示例假定您已按照 [为 DynamoDB 中的代码示例创建表和加载数据](SampleData.md) 部分的说明，将数据加载到您的帐户的 DynamoDB 中。  
有关运行以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

```
package com.amazonaws.codesamples.document;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DeleteItemOutcome;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.UpdateItemOutcome;
import com.amazonaws.services.dynamodbv2.document.spec.DeleteItemSpec;
import com.amazonaws.services.dynamodbv2.document.spec.UpdateItemSpec;
import com.amazonaws.services.dynamodbv2.document.utils.NameMap;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
import com.amazonaws.services.dynamodbv2.model.ReturnValue;

public class DocumentAPIItemCRUDExample {

    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    static String tableName = "ProductCatalog";

    public static void main(String[] args) throws IOException {

        createItems();

        retrieveItem();

        // Perform various updates.
        updateMultipleAttributes();
        updateAddNewAttribute();
        updateExistingAttributeConditionally();

        // Delete the item.
        deleteItem();

    }

    private static void createItems() {

        Table table = dynamoDB.getTable(tableName);
        try {

            Item item = new Item().withPrimaryKey("Id", 120).withString("Title", "Book 120 Title")
                    .withString("ISBN", "120-1111111111")
                    .withStringSet("Authors", new HashSet<String>(Arrays.asList("Author12", "Author22")))
                    .withNumber("Price", 20).withString("Dimensions", "8.5x11.0x.75").withNumber("PageCount", 500)
                    .withBoolean("InPublication", false).withString("ProductCategory", "Book");
            table.putItem(item);

            item = new Item().withPrimaryKey("Id", 121).withString("Title", "Book 121 Title")
                    .withString("ISBN", "121-1111111111")
                    .withStringSet("Authors", new HashSet<String>(Arrays.asList("Author21", "Author 22")))
                    .withNumber("Price", 20).withString("Dimensions", "8.5x11.0x.75").withNumber("PageCount", 500)
                    .withBoolean("InPublication", true).withString("ProductCategory", "Book");
            table.putItem(item);

        } catch (Exception e) {
            System.err.println("Create items failed.");
            System.err.println(e.getMessage());

        }
    }

    private static void retrieveItem() {
        Table table = dynamoDB.getTable(tableName);

        try {

            Item item = table.getItem("Id", 120, "Id, ISBN, Title, Authors", null);

            System.out.println("Printing item after retrieving it....");
            System.out.println(item.toJSONPretty());

        } catch (Exception e) {
            System.err.println("GetItem failed.");
            System.err.println(e.getMessage());
        }

    }

    private static void updateAddNewAttribute() {
        Table table = dynamoDB.getTable(tableName);

        try {

            UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey("Id", 121)
                    .withUpdateExpression("set #na = :val1").withNameMap(new NameMap().with("#na", "NewAttribute"))
                    .withValueMap(new ValueMap().withString(":val1", "Some value"))
                    .withReturnValues(ReturnValue.ALL_NEW);

            UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

            // Check the response.
            System.out.println("Printing item after adding new attribute...");
            System.out.println(outcome.getItem().toJSONPretty());

        } catch (Exception e) {
            System.err.println("Failed to add new attribute in " + tableName);
            System.err.println(e.getMessage());
        }
    }

    private static void updateMultipleAttributes() {

        Table table = dynamoDB.getTable(tableName);

        try {

            UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey("Id", 120)
                    .withUpdateExpression("add #a :val1 set #na=:val2")
                    .withNameMap(new NameMap().with("#a", "Authors").with("#na", "NewAttribute"))
                    .withValueMap(
                            new ValueMap().withStringSet(":val1", "Author YY", "Author ZZ").withString(":val2",
                                    "someValue"))
                    .withReturnValues(ReturnValue.ALL_NEW);

            UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

            // Check the response.
            System.out.println("Printing item after multiple attribute update...");
            System.out.println(outcome.getItem().toJSONPretty());

        } catch (Exception e) {
            System.err.println("Failed to update multiple attributes in " + tableName);
            System.err.println(e.getMessage());

        }
    }

    private static void updateExistingAttributeConditionally() {

        Table table = dynamoDB.getTable(tableName);

        try {

            // Specify the desired price (25.00) and also the condition (price =
            // 20.00)

            UpdateItemSpec updateItemSpec = new UpdateItemSpec().withPrimaryKey("Id", 120)
                    .withReturnValues(ReturnValue.ALL_NEW).withUpdateExpression("set #p = :val1")
                    .withConditionExpression("#p = :val2").withNameMap(new NameMap().with("#p", "Price"))
                    .withValueMap(new ValueMap().withNumber(":val1", 25).withNumber(":val2", 20));

            UpdateItemOutcome outcome = table.updateItem(updateItemSpec);

            // Check the response.
            System.out.println("Printing item after conditional update to new attribute...");
            System.out.println(outcome.getItem().toJSONPretty());

        } catch (Exception e) {
            System.err.println("Error updating item in " + tableName);
            System.err.println(e.getMessage());
        }
    }

    private static void deleteItem() {

        Table table = dynamoDB.getTable(tableName);

        try {

            DeleteItemSpec deleteItemSpec = new DeleteItemSpec().withPrimaryKey("Id", 120)
                    .withConditionExpression("#ip = :val").withNameMap(new NameMap().with("#ip", "InPublication"))
                    .withValueMap(new ValueMap().withBoolean(":val", false)).withReturnValues(ReturnValue.ALL_OLD);

            DeleteItemOutcome outcome = table.deleteItem(deleteItemSpec);

            // Check the response.
            System.out.println("Printing item that was deleted...");
            System.out.println(outcome.getItem().toJSONPretty());

        } catch (Exception e) {
            System.err.println("Error deleting item in " + tableName);
            System.err.println(e.getMessage());
        }
    }
}
```

# 示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理操作
<a name="batch-operation-document-api-java"></a>

本部分提供在 Amazon DynamoDB 中使用 适用于 Java 的 AWS SDK 文档 API 执行批量写入和批量获取操作的示例。

**注意**  
SDK for Java 还提供一个对象持久化模型，可用来将客户端类映射到 DynamoDB 表。该方法可以减少需要编写的代码数量。有关更多信息，请参阅 [Java 1.x：DynamoDBMapper](DynamoDBMapper.md)。

**Topics**
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理写入操作](#JavaDocumentAPIBatchWrite)
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理获取操作](#JavaDocumentAPIBatchGet)

## 示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理写入操作
<a name="JavaDocumentAPIBatchWrite"></a>

以下 Java 代码示例使用 `batchWriteItem` 方法执行以下放置和删除操作：
+ 在 `Forum` 表中放置一个项目。
+ 在 `Thread` 表中放置一个项目并删除一个项目。

在创建批量写入请求时，您可以就一个或多个表指定任意数量的放置和删除请求。但是，`batchWriteItem` 对批量写入请求的大小，以及单个批量写入操作中的放置和删除操作数量有限制。如果您的请求超出这些限制，请求会遭到拒绝。如果您的表的预置吞吐量不足，无法处理此请求，那么响应将返回未处理的请求项目。

以下示例查看响应，了解响应是否包含任何未处理的请求项目。如果存在，则循环返回，并重新发送包含请求中的未处理项目的 `batchWriteItem` 请求。如果您遵循该指南中的示例，则应已创建 `Forum` 和 `Thread` 表。您还能够以编程方式创建这些表和上传示例数据。有关更多信息，请参阅 [使用 适用于 Java 的 AWS SDK 创建表示例并上传数据](AppendixSampleDataCodeJava.md)。

有关测试以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

**Example**  

```
package com.amazonaws.codesamples.document;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.BatchWriteItemOutcome;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.TableWriteItems;
import com.amazonaws.services.dynamodbv2.model.WriteRequest;

public class DocumentAPIBatchWrite {

    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    static String forumTableName = "Forum";
    static String threadTableName = "Thread";

    public static void main(String[] args) throws IOException {

        writeMultipleItemsBatchWrite();

    }

    private static void writeMultipleItemsBatchWrite() {
        try {

            // Add a new item to Forum
            TableWriteItems forumTableWriteItems = new TableWriteItems(forumTableName) // Forum
                    .withItemsToPut(new Item().withPrimaryKey("Name", "Amazon RDS").withNumber("Threads", 0));

            // Add a new item, and delete an existing item, from Thread
            // This table has a partition key and range key, so need to specify
            // both of them
            TableWriteItems threadTableWriteItems = new TableWriteItems(threadTableName)
                    .withItemsToPut(
                            new Item().withPrimaryKey("ForumName", "Amazon RDS", "Subject", "Amazon RDS Thread 1")
                                    .withString("Message", "ElastiCache Thread 1 message")
                                    .withStringSet("Tags", new HashSet<String>(Arrays.asList("cache", "in-memory"))))
                    .withHashAndRangeKeysToDelete("ForumName", "Subject", "Amazon S3", "S3 Thread 100");

            System.out.println("Making the request.");
            BatchWriteItemOutcome outcome = dynamoDB.batchWriteItem(forumTableWriteItems, threadTableWriteItems);

            do {

                // Check for unprocessed keys which could happen if you exceed
                // provisioned throughput

                Map<String, List<WriteRequest>> unprocessedItems = outcome.getUnprocessedItems();

                if (outcome.getUnprocessedItems().size() == 0) {
                    System.out.println("No unprocessed items found");
                } else {
                    System.out.println("Retrieving the unprocessed items");
                    outcome = dynamoDB.batchWriteItemUnprocessed(unprocessedItems);
                }

            } while (outcome.getUnprocessedItems().size() > 0);

        } catch (Exception e) {
            System.err.println("Failed to retrieve items: ");
            e.printStackTrace(System.err);
        }

    }

}
```

## 示例：使用 适用于 Java 的 AWS SDK 文档 API 的批处理获取操作
<a name="JavaDocumentAPIBatchGet"></a>

以下 Java 代码示例使用 `batchGetItem` 方法检索 `Forum` 和 `Thread` 表中的多个项目。`BatchGetItemRequest` 为每个要获取的项目指定表名称和键列表。示例介绍通过打印检索到的项目来处理响应。

**注意**  
此代码示例假定您已将数据加载到您的帐户的 DynamoDB 中，方法是按照 [为 DynamoDB 中的代码示例创建表和加载数据](SampleData.md) 部分。  
有关运行以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

**Example**  

```
package com.amazonaws.codesamples.document;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.BatchGetItemOutcome;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.TableKeysAndAttributes;
import com.amazonaws.services.dynamodbv2.model.KeysAndAttributes;

public class DocumentAPIBatchGet {
    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    static String forumTableName = "Forum";
    static String threadTableName = "Thread";

    public static void main(String[] args) throws IOException {
        retrieveMultipleItemsBatchGet();
    }

    private static void retrieveMultipleItemsBatchGet() {

        try {

            TableKeysAndAttributes forumTableKeysAndAttributes = new TableKeysAndAttributes(forumTableName);
            // Add a partition key
            forumTableKeysAndAttributes.addHashOnlyPrimaryKeys("Name", "Amazon S3", "Amazon DynamoDB");

            TableKeysAndAttributes threadTableKeysAndAttributes = new TableKeysAndAttributes(threadTableName);
            // Add a partition key and a sort key
            threadTableKeysAndAttributes.addHashAndRangePrimaryKeys("ForumName", "Subject", "Amazon DynamoDB",
                    "DynamoDB Thread 1", "Amazon DynamoDB", "DynamoDB Thread 2", "Amazon S3", "S3 Thread 1");

            System.out.println("Making the request.");

            BatchGetItemOutcome outcome = dynamoDB.batchGetItem(forumTableKeysAndAttributes,
                    threadTableKeysAndAttributes);

            Map<String, KeysAndAttributes> unprocessed = null;

            do {
                for (String tableName : outcome.getTableItems().keySet()) {
                    System.out.println("Items in table " + tableName);
                    List<Item> items = outcome.getTableItems().get(tableName);
                    for (Item item : items) {
                        System.out.println(item.toJSONPretty());
                    }
                }

                // Check for unprocessed keys which could happen if you exceed
                // provisioned
                // throughput or reach the limit on response size.
                unprocessed = outcome.getUnprocessedKeys();

                if (unprocessed.isEmpty()) {
                    System.out.println("No unprocessed keys found");
                } else {
                    System.out.println("Retrieving the unprocessed keys");
                    outcome = dynamoDB.batchGetItemUnprocessed(unprocessed);
                }

            } while (!unprocessed.isEmpty());

        } catch (Exception e) {
            System.err.println("Failed to retrieve items.");
            System.err.println(e.getMessage());
        }

    }

}
```

# 示例：使用 适用于 Java 的 AWS SDK 文档 API 处理二进制类型属性
<a name="JavaDocumentAPIBinaryTypeExample"></a>

以下 Java 代码示例介绍如何处理二进制类型属性。示例介绍将项目添加到 `Reply` 表。项目包含存储压缩数据的二进制类型属性 (`ExtendedMessage`)。然后，示例检索该项目，并打印所有属性值。为方便说明，该示例使用 `GZIPOutputStream` 类压缩示例数据流，并将其分配至 `ExtendedMessage` 属性。检索到二进制属性后，使用 `GZIPInputStream` 类对其解压。

**注意**  
SDK for Java 还提供一个对象持久化模型，可用来将客户端类映射到 DynamoDB 表。该方法可以减少需要编写的代码数量。有关更多信息，请参阅 [Java 1.x：DynamoDBMapper](DynamoDBMapper.md)。

如果您已按照[为 DynamoDB 中的代码示例创建表和加载数据](SampleData.md)部分进行操作，您应当已经创建了 `Reply` 表。您还能够以编程方式创建此表。有关更多信息，请参阅 [使用 适用于 Java 的 AWS SDK 创建表示例并上传数据](AppendixSampleDataCodeJava.md)。

有关测试以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

**Example**  

```
package com.amazonaws.codesamples.document;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec;

public class DocumentAPIItemBinaryExample {

    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    static String tableName = "Reply";
    static SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

    public static void main(String[] args) throws IOException {
        try {

            // Format the primary key values
            String threadId = "Amazon DynamoDB#DynamoDB Thread 2";

            dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
            String replyDateTime = dateFormatter.format(new Date());

            // Add a new reply with a binary attribute type
            createItem(threadId, replyDateTime);

            // Retrieve the reply with a binary attribute type
            retrieveItem(threadId, replyDateTime);

            // clean up by deleting the item
            deleteItem(threadId, replyDateTime);
        } catch (Exception e) {
            System.err.println("Error running the binary attribute type example: " + e);
            e.printStackTrace(System.err);
        }
    }

    public static void createItem(String threadId, String replyDateTime) throws IOException {

        Table table = dynamoDB.getTable(tableName);

        // Craft a long message
        String messageInput = "Long message to be compressed in a lengthy forum reply";

        // Compress the long message
        ByteBuffer compressedMessage = compressString(messageInput.toString());

        table.putItem(new Item().withPrimaryKey("Id", threadId).withString("ReplyDateTime", replyDateTime)
                .withString("Message", "Long message follows").withBinary("ExtendedMessage", compressedMessage)
                .withString("PostedBy", "User A"));
    }

    public static void retrieveItem(String threadId, String replyDateTime) throws IOException {

        Table table = dynamoDB.getTable(tableName);

        GetItemSpec spec = new GetItemSpec().withPrimaryKey("Id", threadId, "ReplyDateTime", replyDateTime)
                .withConsistentRead(true);

        Item item = table.getItem(spec);

        // Uncompress the reply message and print
        String uncompressed = uncompressString(ByteBuffer.wrap(item.getBinary("ExtendedMessage")));

        System.out.println("Reply message:\n" + " Id: " + item.getString("Id") + "\n" + " ReplyDateTime: "
                + item.getString("ReplyDateTime") + "\n" + " PostedBy: " + item.getString("PostedBy") + "\n"
                + " Message: "
                + item.getString("Message") + "\n" + " ExtendedMessage (uncompressed): " + uncompressed + "\n");
    }

    public static void deleteItem(String threadId, String replyDateTime) {

        Table table = dynamoDB.getTable(tableName);
        table.deleteItem("Id", threadId, "ReplyDateTime", replyDateTime);
    }

    private static ByteBuffer compressString(String input) throws IOException {
        // Compress the UTF-8 encoded String into a byte[]
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream os = new GZIPOutputStream(baos);
        os.write(input.getBytes("UTF-8"));
        os.close();
        baos.close();
        byte[] compressedBytes = baos.toByteArray();

        // The following code writes the compressed bytes to a ByteBuffer.
        // A simpler way to do this is by simply calling
        // ByteBuffer.wrap(compressedBytes);
        // However, the longer form below shows the importance of resetting the
        // position of the buffer
        // back to the beginning of the buffer if you are writing bytes directly
        // to it, since the SDK
        // will consider only the bytes after the current position when sending
        // data to DynamoDB.
        // Using the "wrap" method automatically resets the position to zero.
        ByteBuffer buffer = ByteBuffer.allocate(compressedBytes.length);
        buffer.put(compressedBytes, 0, compressedBytes.length);
        buffer.position(0); // Important: reset the position of the ByteBuffer
                            // to the beginning
        return buffer;
    }

    private static String uncompressString(ByteBuffer input) throws IOException {
        byte[] bytes = input.array();
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPInputStream is = new GZIPInputStream(bais);

        int chunkSize = 1024;
        byte[] buffer = new byte[chunkSize];
        int length = 0;
        while ((length = is.read(buffer, 0, chunkSize)) != -1) {
            baos.write(buffer, 0, length);
        }

        String result = new String(baos.toByteArray(), "UTF-8");

        is.close();
        baos.close();
        bais.close();

        return result;
    }
}
```

# 使用项目：.NET
<a name="LowLevelDotNetItemCRUD"></a>

您可以使用 适用于 .NET 的 AWS SDK 低级别 API 对表中的项目执行典型的创建、读取、更新和删除 (CRUD) 操作。以下是使用 .NET 低级 API 执行数据 CRUD 操作的常见步骤。

1. 创建 `AmazonDynamoDBClient` 类（客户端）的实例。

1. 在相应的请求对象中提供特定于操作的必需参数。

   例如，将 `PutItemRequest` 请求对象，并使用 `GetItemRequest` 检索现有项目时请求对象。

   您可以使用请求对象同时提供所需参数和可选参数。

1. 传入之前步骤创建的请求对象，运行客户端提供的适当方法。

   这些区域有：`AmazonDynamoDBClient` 客户端提供 `PutItem`、`GetItem`、`UpdateItem` 和 `DeleteItem` 方法进行 CRUD 操作。

**Topics**
+ [放置项目](#PutItemLowLevelAPIDotNet)
+ [获取项目](#GetItemLowLevelDotNET)
+ [更新项目](#UpdateItemLowLevelDotNet)
+ [原子计数器](#AtomicCounterLowLevelDotNet)
+ [删除项目](#DeleteMidLevelDotNet)
+ [批处理写入：放置和删除多个项目](#BatchWriteLowLevelDotNet)
+ [批处理获取：获取多个项目](#BatchGetLowLevelDotNet)
+ [示例：使用低级别 适用于 .NET 的 AWS SDK API 进行 CRUD 操作](LowLevelDotNetItemsExample.md)
+ [示例：使用低级 适用于 .NET 的 AWS SDK API 进行批处理操作](batch-operation-lowlevel-dotnet.md)
+ [示例：使用 适用于 .NET 的 AWS SDK 低级 API 处理二进制类型属性](LowLevelDotNetBinaryTypeExample.md)

## 放置项目
<a name="PutItemLowLevelAPIDotNet"></a>

`PutItem` 方法将项目上传到表。如果项目已存在，则会替换整个项目。

**注意**  
如果您不想替换整个项目，而只希望更新特定属性，那么您可以使用 `UpdateItem` 方法。有关更多信息，请参阅 [更新项目](#UpdateItemLowLevelDotNet)。

以下是使用低级别 .NET SDK API 上传项目的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 提供所需的参数，方法是创建 `PutItemRequest` 类。

   要放置项目，您必须提供表名称和项目。

1. 通过提供您在之前步骤中创建的 `PutItemRequest` 对象，运行 `PutItem` 方法。

以下 C\$1 示例演示了上述步骤。该示例将一个项目上传到 `ProductCatalog` 表。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new PutItemRequest
{
   TableName = tableName,
   Item = new Dictionary<string, AttributeValue>()
      {
          { "Id", new AttributeValue { N = "201" }},
          { "Title", new AttributeValue { S = "Book 201 Title" }},
          { "ISBN", new AttributeValue { S = "11-11-11-11" }},
          { "Price", new AttributeValue { S = "20.00" }},
          {
            "Authors",
            new AttributeValue
            { SS = new List<string>{"Author1", "Author2"}   }
          }
      }
};
client.PutItem(request);
```

在上一示例中，您上传的图书项目包含 `Id`、`Title`、`ISBN` 和 `Authors` 属性。请注意，`Id` 是数字类型属性，所有其他属性都是字符串类型。作者是 `String` 设置。

### 指定可选参数
<a name="PutItemLowLevelAPIDotNetOptions"></a>

您也可以使用 `PutItemRequest` 对象，如以下 C\$1 示例所示。该示例指定了以下可选参数：
+ `ExpressionAttributeNames`、`ExpressionAttributeValues` 和 `ConditionExpression` 指定只有当现有项目具有特定值 ISBN 属性时，才可替换该项目。
+ `ReturnValues` 参数，用于请求响应中的旧项目。

**Example**  

```
var request = new PutItemRequest
 {
   TableName = tableName,
   Item = new Dictionary<string, AttributeValue>()
               {
                   { "Id", new AttributeValue { N = "104" }},
                   { "Title", new AttributeValue { S = "Book 104  Title" }},
                   { "ISBN", new AttributeValue { S = "444-4444444444" }},
                   { "Authors",
                     new AttributeValue { SS = new List<string>{"Author3"}}}
               },
    // Optional parameters.
    ExpressionAttributeNames = new Dictionary<string,string>()
    {
        {"#I", "ISBN"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":isbn",new AttributeValue {S = "444-4444444444"}}
    },
    ConditionExpression = "#I = :isbn"

};
var response = client.PutItem(request);
```

有关更多信息，请参见 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html)。

## 获取项目
<a name="GetItemLowLevelDotNET"></a>

`GetItem` 方法检索项目。

**注意**  
要检索多个项目，您可以使用 `BatchGetItem` 方法。有关更多信息，请参阅 [批处理获取：获取多个项目](#BatchGetLowLevelDotNet)。

下面是使用低级 适用于 .NET 的 AWS SDK API 检索现有项目的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `GetItemRequest` 类实例，提供所需的参数。

   要获取项目，您必须提供项目的表名称和主键。

1. 通过提供您在之前步骤中创建的 `GetItemRequest` 对象，运行 `GetItem` 方法。

以下 C\$1 示例演示了上述步骤。示例从 `ProductCatalog` 表检索项目。

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new GetItemRequest
 {
   TableName = tableName,
   Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "202" } } },
 };
 var response = client.GetItem(request);

// Check the response.
var result = response.GetItemResult;
var attributeMap = result.Item; // Attribute list in the response.
```

### 指定可选参数
<a name="GetItemLowLevelDotNETOptions"></a>

您也可以使用 `GetItemRequest` 对象提供可选参数，如以下 C\$1 示例所示。该示例指定了以下可选参数：
+ `ProjectionExpression` 参数，指定要检索的属性。
+ `ConsistentRead` 参数，执行强一致性读取。要了解有关读取一致性的更多信息，请参阅 [DynamoDB 读取一致性](HowItWorks.ReadConsistency.md)。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new GetItemRequest
 {
   TableName = tableName,
   Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "202" } } },
   // Optional parameters.
   ProjectionExpression = "Id, ISBN, Title, Authors",
   ConsistentRead = true
 };

 var response = client.GetItem(request);

// Check the response.
var result = response.GetItemResult;
var attributeMap = result.Item;
```

有关更多信息，请参阅 [GetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html)。

## 更新项目
<a name="UpdateItemLowLevelDotNet"></a>

`UpdateItem` 方法更新现有的项目（如果存在）。您可以使用 `UpdateItem` 操作更新现有属性值，添加新属性，或者从现有集合中删除属性。如果未找到具有指定主键的项目，将添加新项目。

`UpdateItem` 操作遵循以下指导原则：
+ 如果项目不存在，`UpdateItem` 会添加一个新项目 (使用输入中指定的主键)。
+ 如果项目存在，则 `UpdateItem` 按照以下方式应用更新：
  + 使用更新中的值替换现有属性值。
  + 如果您在输入中提供的属性不存在，系统就会为项目添加新属性。
  + 如果输入属性为 Null，系统会删除属性（如果存在）。
  + 如果您对 `Action` 使用 `ADD`，您可以将值添加到现有集合（字符串或数字集），或以数学方式从现有数字属性值中添加（使用正数）或减去（使用负数）。

**注意**  
`PutItem` 操作还可以执行更新。有关更多信息，请参阅 [放置项目](#PutItemLowLevelAPIDotNet)。例如，如果调用 `PutItem` 上传项目，并且主键存在，则 `PutItem` 操作会替换整个项目。如果现有项目中有属性，并且这些属性未在输入中指定，那么 `PutItem` 操作就会删除这些属性。但是，`UpdateItem` 仅更新指定的输入属性。该项目的任何其他现有属性都不会更改。

以下是使用低级 .NET SDK API 更新现有项目的步骤：

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `UpdateItemRequest` 类实例，提供所需的参数。

   这是描述所有更新（如添加属性、更新现有属性或删除属性）的请求对象。要删除现有属性，请将属性名称指定为 Null 值。

1. 通过提供您在之前步骤中创建的 `UpdateItemRequest` 对象，运行 `UpdateItem` 方法。

以下 C\$1 代码示例演示了上述步骤。示例更新 `ProductCatalog` 表中的书本项目。将新作者添加到 `Authors` 集合，删除现有 `ISBN` 属性。另外还降低了价格 (-1)。



```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new UpdateItemRequest
{
    TableName = tableName,
    Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "202" } } },
    ExpressionAttributeNames = new Dictionary<string,string>()
    {
        {"#A", "Authors"},
        {"#P", "Price"},
        {"#NA", "NewAttribute"},
        {"#I", "ISBN"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":auth",new AttributeValue { SS = {"Author YY","Author ZZ"}}},
        {":p",new AttributeValue {N = "1"}},
        {":newattr",new AttributeValue {S = "someValue"}},
    },

    // This expression does the following:
    // 1) Adds two new authors to the list
    // 2) Reduces the price
    // 3) Adds a new attribute to the item
    // 4) Removes the ISBN attribute from the item
    UpdateExpression = "ADD #A :auth SET #P = #P - :p, #NA = :newattr REMOVE #I"
};
var response = client.UpdateItem(request);
```

### 指定可选参数
<a name="UpdateItemLowLevelDotNETOptions"></a>

您也可以使用 `UpdateItemRequest` 对象提供可选参数，如以下 C\$1 示例所示。它指定以下两个可选参数：
+ `ExpressionAttributeValues` 和 `ConditionExpression`，指定仅当现有价格为 20.00 时才能更新价格。
+ `ReturnValues` 参数，用于请求响应中的更新项目。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new UpdateItemRequest
{
    Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "202" } } },

    // Update price only if the current price is 20.00.
    ExpressionAttributeNames = new Dictionary<string,string>()
    {
        {"#P", "Price"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":newprice",new AttributeValue {N = "22"}},
        {":currprice",new AttributeValue {N = "20"}}
    },
    UpdateExpression = "SET #P = :newprice",
    ConditionExpression = "#P = :currprice",
    TableName = tableName,
    ReturnValues = "ALL_NEW" // Return all the attributes of the updated item.
};

var response = client.UpdateItem(request);
```

有关更多信息，请参见 [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html)。

## 原子计数器
<a name="AtomicCounterLowLevelDotNet"></a>

您可以使用 `updateItem` 实现原子计数器，并使用该计数器来递增或递减现有属性的值而不会干扰其他写入请求。要更新原子计数器，请使用 `updateItem`，`UpdateExpression` 参数为 `Number` 类型属性，`ADD` 为 `Action`。

以下示例演示了这一用法，将 `Quantity` 属性递增 1。

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new UpdateItemRequest
{
    Key = new Dictionary<string, AttributeValue>() { { "Id", new AttributeValue { N = "121" } } },
    ExpressionAttributeNames = new Dictionary<string, string>()
    {
        {"#Q", "Quantity"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":incr",new AttributeValue {N = "1"}}
    },
    UpdateExpression = "SET #Q = #Q + :incr",
    TableName = tableName
};

var response = client.UpdateItem(request);
```

## 删除项目
<a name="DeleteMidLevelDotNet"></a>

`DeleteItem` 方法能删除表中的项目。

以下是使用低级 .NET SDK API 删除项目的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `DeleteItemRequest` 类实例，提供所需的参数。

    要删除项目，需要表名和项目的主键。

1. 通过提供您在之前步骤中创建的 `DeleteItemRequest` 对象，运行 `DeleteItem` 方法。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "ProductCatalog";

var request = new DeleteItemRequest
{
    TableName = tableName,
    Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "201" } } },
};

var response = client.DeleteItem(request);
```

### 指定可选参数
<a name="DeleteItemLowLevelDotNETOptions"></a>

您也可以使用 `DeleteItemRequest` 对象提供可选参数，如以下 C\$1 代码示例所示。它指定以下两个可选参数：
+ `ExpressionAttributeValues` 和 `ConditionExpression`，指定仅当书籍项目不再在出版中时才可以删除（InPublisted 属性值为 false）。
+ `ReturnValues` 参数，请求响应中的删除项目。

**Example**  

```
var request = new DeleteItemRequest
{
    TableName = tableName,
    Key = new Dictionary<string,AttributeValue>() { { "Id", new AttributeValue { N = "201" } } },

    // Optional parameters.
    ReturnValues = "ALL_OLD",
    ExpressionAttributeNames = new Dictionary<string, string>()
    {
        {"#IP", "InPublication"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":inpub",new AttributeValue {BOOL = false}}
    },
    ConditionExpression = "#IP = :inpub"
};

var response = client.DeleteItem(request);
```

有关更多信息，请参阅 [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html)。

## 批处理写入：放置和删除多个项目
<a name="BatchWriteLowLevelDotNet"></a>

*批量写入* 是指批量放置和删除多个项目。`BatchWriteItem` 方法可让您通过一次 调用即可向一个或多个表中放置或从中删除多个项目。以下是使用低级 .NET SDK API 检索多个项目的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `BatchWriteItemRequest` 类实例，描述所有放入和删除操作。

1. 通过提供您在之前步骤中创建的 `BatchWriteItemRequest` 对象，运行 `BatchWriteItem` 方法。

1. 处理响应。您应该检查一下响应是否返回未处理的请求项目。如果达到预置吞吐量配额或发生其他临时错误，就可能会出现这种情况。此外，DynamoDB 还对可在请求中指定的请求大小和操作数进行限制。如果超出这些限制，DynamoDB 会拒绝请求。有关更多信息，请参阅 [BatchWriteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html)。

以下 C\$1 代码示例演示了上述步骤。该示例创建了一个 `BatchWriteItemRequest` 执行以下写入操作：
+ 在 `Forum` 表中放置一个项目。
+ 对 `Thread` 表放置和删除项目。

代码运行 `BatchWriteItem` 执行批处理操作。

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();

string table1Name = "Forum";
string table2Name = "Thread";

var request = new BatchWriteItemRequest
 {
   RequestItems = new Dictionary<string, List<WriteRequest>>
    {
      {
        table1Name, new List<WriteRequest>
        {
          new WriteRequest
          {
             PutRequest = new PutRequest
             {
                Item = new Dictionary<string,AttributeValue>
                {
                  { "Name", new AttributeValue { S = "Amazon S3 forum" } },
                  { "Threads", new AttributeValue { N = "0" }}
                }
             }
          }
        }
      } ,
      {
        table2Name, new List<WriteRequest>
        {
          new WriteRequest
          {
            PutRequest = new PutRequest
            {
               Item = new Dictionary<string,AttributeValue>
               {
                 { "ForumName", new AttributeValue { S = "Amazon S3 forum" } },
                 { "Subject", new AttributeValue { S = "My sample question" } },
                 { "Message", new AttributeValue { S = "Message Text." } },
                 { "KeywordTags", new AttributeValue { SS = new List<string> { "Amazon S3", "Bucket" }  } }
               }
            }
          },
          new WriteRequest
          {
             DeleteRequest = new DeleteRequest
             {
                Key = new Dictionary<string,AttributeValue>()
                {
                   { "ForumName", new AttributeValue { S = "Some forum name" } },
                   { "Subject", new AttributeValue { S = "Some subject" } }
                }
             }
          }
        }
      }
    }
 };
response = client.BatchWriteItem(request);
```

要了解可工作的示例，请参阅 [示例：使用低级 适用于 .NET 的 AWS SDK API 进行批处理操作](batch-operation-lowlevel-dotnet.md)。

## 批处理获取：获取多个项目
<a name="BatchGetLowLevelDotNet"></a>

`BatchGetItem` 方法可让您检索一个或多个表中的多个项目。

**注意**  
要检索单个项目，您可以使用 `GetItem` 方法。

以下是使用低级 适用于 .NET 的 AWS SDK API 检索多个项目的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `BatchGetItemRequest` 类实例，提供所需的参数。

   要检索多个项目，需要表名和主键值列表。

1. 通过提供您在之前步骤中创建的 `BatchGetItemRequest` 对象，运行 `BatchGetItem` 方法。

1. 处理响应。您应检查一下是否存在任何未处理的键，如果达到了预置吞吐量配额或发生某些其他临时错误，就可能会出现这种情况。

以下 C\$1 代码示例演示了上述步骤。该示例从两个表 `Forum` 和 `Thread` 检索项目。请求指定 `Forum` 表中两个项目和 `Thread` 表中三个项目。响应包括两个表中的项目。代码显示了如何处理响应。



```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();

string table1Name = "Forum";
string table2Name = "Thread";

var request = new BatchGetItemRequest
{
  RequestItems = new Dictionary<string, KeysAndAttributes>()
  {
    { table1Name,
      new KeysAndAttributes
      {
        Keys = new List<Dictionary<string, AttributeValue>>()
        {
          new Dictionary<string, AttributeValue>()
          {
            { "Name", new AttributeValue { S = "DynamoDB" } }
          },
          new Dictionary<string, AttributeValue>()
          {
            { "Name", new AttributeValue { S = "Amazon S3" } }
          }
        }
      }
    },
    {
      table2Name,
      new KeysAndAttributes
      {
        Keys = new List<Dictionary<string, AttributeValue>>()
        {
          new Dictionary<string, AttributeValue>()
          {
            { "ForumName", new AttributeValue { S = "DynamoDB" } },
            { "Subject", new AttributeValue { S = "DynamoDB Thread 1" } }
          },
          new Dictionary<string, AttributeValue>()
          {
            { "ForumName", new AttributeValue { S = "DynamoDB" } },
            { "Subject", new AttributeValue { S = "DynamoDB Thread 2" } }
          },
          new Dictionary<string, AttributeValue>()
          {
            { "ForumName", new AttributeValue { S = "Amazon S3" } },
            { "Subject", new AttributeValue { S = "Amazon S3 Thread 1" } }
          }
        }
      }
    }
  }
};

var response = client.BatchGetItem(request);

// Check the response.
var result = response.BatchGetItemResult;
var responses = result.Responses; // The attribute list in the response.

var table1Results = responses[table1Name];
Console.WriteLine("Items in table {0}" + table1Name);
foreach (var item1 in table1Results.Items)
{
  PrintItem(item1);
}

var table2Results = responses[table2Name];
Console.WriteLine("Items in table {1}" + table2Name);
foreach (var item2 in table2Results.Items)
{
  PrintItem(item2);
}
// Any unprocessed keys? could happen if you exceed ProvisionedThroughput or some other error.
Dictionary<string, KeysAndAttributes> unprocessedKeys = result.UnprocessedKeys;
foreach (KeyValuePair<string, KeysAndAttributes> pair in unprocessedKeys)
{
    Console.WriteLine(pair.Key, pair.Value);
}
```



### 指定可选参数
<a name="BatchGetItemLowLevelDotNETOptions"></a>

您也可以使用 `BatchGetItemRequest` 对象提供可选参数，如以下 C\$1 代码示例所示。示例从 `Forum` 表检索两个项目。其中指定了以下可选参数：
+  `ProjectionExpression` 参数，指定要检索的属性。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();

string table1Name = "Forum";

var request = new BatchGetItemRequest
{
  RequestItems = new Dictionary<string, KeysAndAttributes>()
  {
    { table1Name,
      new KeysAndAttributes
      {
        Keys = new List<Dictionary<string, AttributeValue>>()
        {
          new Dictionary<string, AttributeValue>()
          {
            { "Name", new AttributeValue { S = "DynamoDB" } }
          },
          new Dictionary<string, AttributeValue>()
          {
            { "Name", new AttributeValue { S = "Amazon S3" } }
          }
        }
      },
      // Optional - name of an attribute to retrieve.
      ProjectionExpression = "Title"
    }
  }
};

var response = client.BatchGetItem(request);
```

有关更多信息，请参阅 [BatchGetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)。

# 示例：使用低级别 适用于 .NET 的 AWS SDK API 进行 CRUD 操作
<a name="LowLevelDotNetItemsExample"></a>

以下 C\$1 代码示例介绍对 Amazon DynamoDB 项目的 CRUD 操作。该示例将项目添加到 `ProductCatalog` 表、对其进行检索、执行多种更新，最终删除项目。如果您尚未创建此表，也可以编程方式进行创建。有关更多信息，请参阅 [创建示例表并使用 适用于 .NET 的 AWS SDK 上传数据](AppendixSampleDataCodeDotNET.md)。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelItemCRUDExample
    {
        private static string tableName = "ProductCatalog";
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();

        static void Main(string[] args)
        {
            try
            {
                CreateItem();
                RetrieveItem();

                // Perform various updates.
                UpdateMultipleAttributes();
                UpdateExistingAttributeConditionally();

                // Delete item.
                DeleteItem();
                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
        }

        private static void CreateItem()
        {
            var request = new PutItemRequest
            {
                TableName = tableName,
                Item = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      N = "1000"
                  }},
                { "Title", new AttributeValue {
                      S = "Book 201 Title"
                  }},
                { "ISBN", new AttributeValue {
                      S = "11-11-11-11"
                  }},
                { "Authors", new AttributeValue {
                      SS = new List<string>{"Author1", "Author2" }
                  }},
                { "Price", new AttributeValue {
                      N = "20.00"
                  }},
                { "Dimensions", new AttributeValue {
                      S = "8.5x11.0x.75"
                  }},
                { "InPublication", new AttributeValue {
                      BOOL = false
                  } }
            }
            };
            client.PutItem(request);
        }

        private static void RetrieveItem()
        {
            var request = new GetItemRequest
            {
                TableName = tableName,
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      N = "1000"
                  } }
            },
                ProjectionExpression = "Id, ISBN, Title, Authors",
                ConsistentRead = true
            };
            var response = client.GetItem(request);

            // Check the response.
            var attributeList = response.Item; // attribute list in the response.
            Console.WriteLine("\nPrinting item after retrieving it ............");
            PrintItem(attributeList);
        }

        private static void UpdateMultipleAttributes()
        {
            var request = new UpdateItemRequest
            {
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      N = "1000"
                  } }
            },
                // Perform the following updates:
                // 1) Add two new authors to the list
                // 1) Set a new attribute
                // 2) Remove the ISBN attribute
                ExpressionAttributeNames = new Dictionary<string, string>()
            {
                {"#A","Authors"},
                {"#NA","NewAttribute"},
                {"#I","ISBN"}
            },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
            {
                {":auth",new AttributeValue {
                     SS = {"Author YY", "Author ZZ"}
                 }},
                {":new",new AttributeValue {
                     S = "New Value"
                 }}
            },

                UpdateExpression = "ADD #A :auth SET #NA = :new REMOVE #I",

                TableName = tableName,
                ReturnValues = "ALL_NEW" // Give me all attributes of the updated item.
            };
            var response = client.UpdateItem(request);

            // Check the response.
            var attributeList = response.Attributes; // attribute list in the response.
                                                     // print attributeList.
            Console.WriteLine("\nPrinting item after multiple attribute update ............");
            PrintItem(attributeList);
        }

        private static void UpdateExistingAttributeConditionally()
        {
            var request = new UpdateItemRequest
            {
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      N = "1000"
                  } }
            },
                ExpressionAttributeNames = new Dictionary<string, string>()
            {
                {"#P", "Price"}
            },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
            {
                {":newprice",new AttributeValue {
                     N = "22.00"
                 }},
                {":currprice",new AttributeValue {
                     N = "20.00"
                 }}
            },
                // This updates price only if current price is 20.00.
                UpdateExpression = "SET #P = :newprice",
                ConditionExpression = "#P = :currprice",

                TableName = tableName,
                ReturnValues = "ALL_NEW" // Give me all attributes of the updated item.
            };
            var response = client.UpdateItem(request);

            // Check the response.
            var attributeList = response.Attributes; // attribute list in the response.
            Console.WriteLine("\nPrinting item after updating price value conditionally ............");
            PrintItem(attributeList);
        }

        private static void DeleteItem()
        {
            var request = new DeleteItemRequest
            {
                TableName = tableName,
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      N = "1000"
                  } }
            },

                // Return the entire item as it appeared before the update.
                ReturnValues = "ALL_OLD",
                ExpressionAttributeNames = new Dictionary<string, string>()
            {
                {"#IP", "InPublication"}
            },
                ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
            {
                {":inpub",new AttributeValue {
                     BOOL = false
                 }}
            },
                ConditionExpression = "#IP = :inpub"
            };

            var response = client.DeleteItem(request);

            // Check the response.
            var attributeList = response.Attributes; // Attribute list in the response.
                                                     // Print item.
            Console.WriteLine("\nPrinting item that was just deleted ............");
            PrintItem(attributeList);
        }

        private static void PrintItem(Dictionary<string, AttributeValue> attributeList)
        {
            foreach (KeyValuePair<string, AttributeValue> kvp in attributeList)
            {
                string attributeName = kvp.Key;
                AttributeValue value = kvp.Value;

                Console.WriteLine(
                    attributeName + " " +
                    (value.S == null ? "" : "S=[" + value.S + "]") +
                    (value.N == null ? "" : "N=[" + value.N + "]") +
                    (value.SS == null ? "" : "SS=[" + string.Join(",", value.SS.ToArray()) + "]") +
                    (value.NS == null ? "" : "NS=[" + string.Join(",", value.NS.ToArray()) + "]")
                    );
            }
            Console.WriteLine("************************************************");
        }
    }
}
```

# 示例：使用低级 适用于 .NET 的 AWS SDK API 进行批处理操作
<a name="batch-operation-lowlevel-dotnet"></a>

**Topics**
+ [示例：使用 适用于 .NET 的 AWS SDK 低级 API 的批处理写入操作](#batch-write-low-level-dotnet)
+ [示例：使用 适用于 .NET 的 AWS SDK 低级 API 的批处理获取操作](#LowLevelDotNetBatchGet)

本节提供 Amazon DynamoDB 支持的批量操作示例，*批处理写入*和*批处理获取*。

## 示例：使用 适用于 .NET 的 AWS SDK 低级 API 的批处理写入操作
<a name="batch-write-low-level-dotnet"></a>

以下 C\$1 代码示例使用 `BatchWriteItem` 方法执行以下放置和删除操作：
+ 在 `Forum` 表中放置一个项目。
+ 在 `Thread` 表中放置一个项目并删除一个项目。

在创建批量写入请求时，您可以就一个或多个表指定任意数量的放置和删除请求。但是，DynamoDB `BatchWriteItem` 对批量写入请求的大小，以及单个批量写入操作中的放置和删除操作数量有限制。有关更多信息，请参阅 [BatchWriteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html)。如果您的请求超出这些限制，请求会遭到拒绝。如果您的表的预置吞吐量不足，无法处理此请求，那么响应将返回未处理的请求项目。

以下示例查看响应，了解响应是否包含任何未处理的请求项目。如果存在，则循环返回，并重新发送包含请求中的未处理项目的 `BatchWriteItem` 请求。您还能够以编程方式创建这些表和上传示例数据。有关更多信息，请参阅 [创建示例表并使用 适用于 .NET 的 AWS SDK 上传数据](AppendixSampleDataCodeDotNET.md)。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;

namespace com.amazonaws.codesamples
{
    class LowLevelBatchWrite
    {
        private static string table1Name = "Forum";
        private static string table2Name = "Thread";
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();

        static void Main(string[] args)
        {
            try
            {
                TestBatchWrite();
            }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }

            Console.WriteLine("To continue, press Enter");
            Console.ReadLine();
        }

        private static void TestBatchWrite()
        {
            var request = new BatchWriteItemRequest
            {
                ReturnConsumedCapacity = "TOTAL",
                RequestItems = new Dictionary<string, List<WriteRequest>>
            {
                {
                    table1Name, new List<WriteRequest>
                    {
                        new WriteRequest
                        {
                            PutRequest = new PutRequest
                            {
                                Item = new Dictionary<string, AttributeValue>
                                {
                                    { "Name", new AttributeValue {
                                          S = "S3 forum"
                                      } },
                                    { "Threads", new AttributeValue {
                                          N = "0"
                                      }}
                                }
                            }
                        }
                    }
                },
                {
                    table2Name, new List<WriteRequest>
                    {
                        new WriteRequest
                        {
                            PutRequest = new PutRequest
                            {
                                Item = new Dictionary<string, AttributeValue>
                                {
                                    { "ForumName", new AttributeValue {
                                          S = "S3 forum"
                                      } },
                                    { "Subject", new AttributeValue {
                                          S = "My sample question"
                                      } },
                                    { "Message", new AttributeValue {
                                          S = "Message Text."
                                      } },
                                    { "KeywordTags", new AttributeValue {
                                          SS = new List<string> { "S3", "Bucket" }
                                      } }
                                }
                            }
                        },
                        new WriteRequest
                        {
                            // For the operation to delete an item, if you provide a primary key value
                            // that does not exist in the table, there is no error, it is just a no-op.
                            DeleteRequest = new DeleteRequest
                            {
                                Key = new Dictionary<string, AttributeValue>()
                                {
                                    { "ForumName",  new AttributeValue {
                                          S = "Some partition key value"
                                      } },
                                    { "Subject", new AttributeValue {
                                          S = "Some sort key value"
                                      } }
                                }
                            }
                        }
                    }
                }
            }
            };

            CallBatchWriteTillCompletion(request);
        }

        private static void CallBatchWriteTillCompletion(BatchWriteItemRequest request)
        {
            BatchWriteItemResponse response;

            int callCount = 0;
            do
            {
                Console.WriteLine("Making request");
                response = client.BatchWriteItem(request);
                callCount++;

                // Check the response.

                var tableConsumedCapacities = response.ConsumedCapacity;
                var unprocessed = response.UnprocessedItems;

                Console.WriteLine("Per-table consumed capacity");
                foreach (var tableConsumedCapacity in tableConsumedCapacities)
                {
                    Console.WriteLine("{0} - {1}", tableConsumedCapacity.TableName, tableConsumedCapacity.CapacityUnits);
                }

                Console.WriteLine("Unprocessed");
                foreach (var unp in unprocessed)
                {
                    Console.WriteLine("{0} - {1}", unp.Key, unp.Value.Count);
                }
                Console.WriteLine();

                // For the next iteration, the request will have unprocessed items.
                request.RequestItems = unprocessed;
            } while (response.UnprocessedItems.Count > 0);

            Console.WriteLine("Total # of batch write API calls made: {0}", callCount);
        }
    }
}
```

## 示例：使用 适用于 .NET 的 AWS SDK 低级 API 的批处理获取操作
<a name="LowLevelDotNetBatchGet"></a>

以下 Java 代码示例使用 `BatchGetItem` 方法检索 Amazon DynamoDB 中的 `Forum` 和 `Thread` 表中的多个项目。`BatchGetItemRequest` 指定表名称和每个表的主键列表。示例介绍通过打印检索到的项目来处理响应。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;

namespace com.amazonaws.codesamples
{
    class LowLevelBatchGet
    {
        private static string table1Name = "Forum";
        private static string table2Name = "Thread";
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();

        static void Main(string[] args)
        {
            try
            {
                RetrieveMultipleItemsBatchGet();

                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }

        private static void RetrieveMultipleItemsBatchGet()
        {
            var request = new BatchGetItemRequest
            {
                RequestItems = new Dictionary<string, KeysAndAttributes>()
            {
                { table1Name,
                  new KeysAndAttributes
                  {
                      Keys = new List<Dictionary<string, AttributeValue> >()
                      {
                          new Dictionary<string, AttributeValue>()
                          {
                              { "Name", new AttributeValue {
                            S = "Amazon DynamoDB"
                        } }
                          },
                          new Dictionary<string, AttributeValue>()
                          {
                              { "Name", new AttributeValue {
                            S = "Amazon S3"
                        } }
                          }
                      }
                  }},
                {
                    table2Name,
                    new KeysAndAttributes
                    {
                        Keys = new List<Dictionary<string, AttributeValue> >()
                        {
                            new Dictionary<string, AttributeValue>()
                            {
                                { "ForumName", new AttributeValue {
                                      S = "Amazon DynamoDB"
                                  } },
                                { "Subject", new AttributeValue {
                                      S = "DynamoDB Thread 1"
                                  } }
                            },
                            new Dictionary<string, AttributeValue>()
                            {
                                { "ForumName", new AttributeValue {
                                      S = "Amazon DynamoDB"
                                  } },
                                { "Subject", new AttributeValue {
                                      S = "DynamoDB Thread 2"
                                  } }
                            },
                            new Dictionary<string, AttributeValue>()
                            {
                                { "ForumName", new AttributeValue {
                                      S = "Amazon S3"
                                  } },
                                { "Subject", new AttributeValue {
                                      S = "S3 Thread 1"
                                  } }
                            }
                        }
                    }
                }
            }
            };

            BatchGetItemResponse response;
            do
            {
                Console.WriteLine("Making request");
                response = client.BatchGetItem(request);

                // Check the response.
                var responses = response.Responses; // Attribute list in the response.

                foreach (var tableResponse in responses)
                {
                    var tableResults = tableResponse.Value;
                    Console.WriteLine("Items retrieved from table {0}", tableResponse.Key);
                    foreach (var item1 in tableResults)
                    {
                        PrintItem(item1);
                    }
                }

                // Any unprocessed keys? could happen if you exceed ProvisionedThroughput or some other error.
                Dictionary<string, KeysAndAttributes> unprocessedKeys = response.UnprocessedKeys;
                foreach (var unprocessedTableKeys in unprocessedKeys)
                {
                    // Print table name.
                    Console.WriteLine(unprocessedTableKeys.Key);
                    // Print unprocessed primary keys.
                    foreach (var key in unprocessedTableKeys.Value.Keys)
                    {
                        PrintItem(key);
                    }
                }

                request.RequestItems = unprocessedKeys;
            } while (response.UnprocessedKeys.Count > 0);
        }

        private static void PrintItem(Dictionary<string, AttributeValue> attributeList)
        {
            foreach (KeyValuePair<string, AttributeValue> kvp in attributeList)
            {
                string attributeName = kvp.Key;
                AttributeValue value = kvp.Value;

                Console.WriteLine(
                    attributeName + " " +
                    (value.S == null ? "" : "S=[" + value.S + "]") +
                    (value.N == null ? "" : "N=[" + value.N + "]") +
                    (value.SS == null ? "" : "SS=[" + string.Join(",", value.SS.ToArray()) + "]") +
                    (value.NS == null ? "" : "NS=[" + string.Join(",", value.NS.ToArray()) + "]")
                    );
            }
            Console.WriteLine("************************************************");
        }
    }
}
```

# 示例：使用 适用于 .NET 的 AWS SDK 低级 API 处理二进制类型属性
<a name="LowLevelDotNetBinaryTypeExample"></a>

以下 C\$1 代码示例介绍如何处理二进制类型属性。示例介绍将项目添加到 `Reply` 表。项目包含存储压缩数据的二进制类型属性 (`ExtendedMessage`)。然后，示例检索该项目，并打印所有属性值。为方便说明，该示例使用 `GZipStream` 类压缩示例数据流，分配至 `ExtendedMessage` 属性，输出属性值时解压缩。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;

namespace com.amazonaws.codesamples
{
    class LowLevelItemBinaryExample
    {
        private static string tableName = "Reply";
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();

        static void Main(string[] args)
        {
            // Reply table primary key.
            string replyIdPartitionKey = "Amazon DynamoDB#DynamoDB Thread 1";
            string replyDateTimeSortKey = Convert.ToString(DateTime.UtcNow);

            try
            {
                CreateItem(replyIdPartitionKey, replyDateTimeSortKey);
                RetrieveItem(replyIdPartitionKey, replyDateTimeSortKey);
                // Delete item.
                DeleteItem(replyIdPartitionKey, replyDateTimeSortKey);
                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (AmazonDynamoDBException e) { Console.WriteLine(e.Message); }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }

        private static void CreateItem(string partitionKey, string sortKey)
        {
            MemoryStream compressedMessage = ToGzipMemoryStream("Some long extended message to compress.");
            var request = new PutItemRequest
            {
                TableName = tableName,
                Item = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      S = partitionKey
                  }},
                { "ReplyDateTime", new AttributeValue {
                      S = sortKey
                  }},
                { "Subject", new AttributeValue {
                      S = "Binary type "
                  }},
                { "Message", new AttributeValue {
                      S = "Some message about the binary type"
                  }},
                { "ExtendedMessage", new AttributeValue {
                      B = compressedMessage
                  }}
            }
            };
            client.PutItem(request);
        }

        private static void RetrieveItem(string partitionKey, string sortKey)
        {
            var request = new GetItemRequest
            {
                TableName = tableName,
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      S = partitionKey
                  } },
                { "ReplyDateTime", new AttributeValue {
                      S = sortKey
                  } }
            },
                ConsistentRead = true
            };
            var response = client.GetItem(request);

            // Check the response.
            var attributeList = response.Item; // attribute list in the response.
            Console.WriteLine("\nPrinting item after retrieving it ............");

            PrintItem(attributeList);
        }

        private static void DeleteItem(string partitionKey, string sortKey)
        {
            var request = new DeleteItemRequest
            {
                TableName = tableName,
                Key = new Dictionary<string, AttributeValue>()
            {
                { "Id", new AttributeValue {
                      S = partitionKey
                  } },
                { "ReplyDateTime", new AttributeValue {
                      S = sortKey
                  } }
            }
            };
            var response = client.DeleteItem(request);
        }

        private static void PrintItem(Dictionary<string, AttributeValue> attributeList)
        {
            foreach (KeyValuePair<string, AttributeValue> kvp in attributeList)
            {
                string attributeName = kvp.Key;
                AttributeValue value = kvp.Value;

                Console.WriteLine(
                    attributeName + " " +
                    (value.S == null ? "" : "S=[" + value.S + "]") +
                    (value.N == null ? "" : "N=[" + value.N + "]") +
                    (value.SS == null ? "" : "SS=[" + string.Join(",", value.SS.ToArray()) + "]") +
                    (value.NS == null ? "" : "NS=[" + string.Join(",", value.NS.ToArray()) + "]") +
                    (value.B == null ? "" : "B=[" + FromGzipMemoryStream(value.B) + "]")
                    );
            }
            Console.WriteLine("************************************************");
        }

        private static MemoryStream ToGzipMemoryStream(string value)
        {
            MemoryStream output = new MemoryStream();
            using (GZipStream zipStream = new GZipStream(output, CompressionMode.Compress, true))
            using (StreamWriter writer = new StreamWriter(zipStream))
            {
                writer.Write(value);
            }
            return output;
        }

        private static string FromGzipMemoryStream(MemoryStream stream)
        {
            using (GZipStream zipStream = new GZipStream(stream, CompressionMode.Decompress))
            using (StreamReader reader = new StreamReader(zipStream))
            {
                return reader.ReadToEnd();
            }
        }
    }
}
```

# 在 DynamoDB 中使用二级索引改进数据访问
<a name="SecondaryIndexes"></a>

Amazon DynamoDB 通过指定主键值来提供对表中项目的快速访问。但是，很多应用程序可能适合有一个或多个二级（或替代）键，以便通过主键以外的属性对数据进行高效访问。要解决此问题，您可以对表创建一个或多个二级索引，然后对这些索引发出 `Query` 或 `Scan` 请求。

*二级索引*是一种数据结构，它包含表中属性的子集以及一个支持 `Query` 操作的替代键。您可以使用 `Query` 从索引中检索数据，其方式与对表使用 `Query` 大致相同。一个表可以有多个二级索引，这样，应用程序可以访问许多不同的查询模式。

**注意**  
也可以对索引使用 `Scan`，其方式与对表使用 `Scan` 大致相同。  
[基于资源的策略](access-control-resource-based.md)目前不支持跨账户访问二级索引扫描操作。

每个二级索引关联且仅关联一个表，并从该表中获取其数据。这称为索引的*基表*。在创建索引时，您为索引定义一个替代键 (分区键和排序键)。您还可以定义要从基表*投影*或复制到索引的属性。DynamoDB 将这些属性以及基表中的主键属性一起复制到索引中。然后，您可以查询或扫描该索引，就像查询或扫描表一样。

每个二级索引都由 DynamoDB 自动维护。在基表中添加、修改或删除项目时，表上的所有索引也会更新，以反映这些更改。

DynamoDB 支持两种类型的二级索引：
+ **[全局二级索引](GSI.html) — **分区键和排序键可与基表中的这些键不同的索引。全局二级索引之所以称为“全局”，是因为索引上的查询可跨过所有分区，覆盖基表的所有数据。全局二级索引存储在其远离基表的分区空间中，并且独立于基表进行扩展。
+ **[本地二级索引](LSI.html) — **分区键与基表相同但排序键不同的索引。本地二级索引之所以称为“本地”，是因为索引的每个分区的范围都限定为具有相同分区键值的基表分区。

有关全局二级索引和本地二级索引的比较，请观看此视频。

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/BkEu7zBWge8/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/BkEu7zBWge8)


**Topics**
+ [在 DynamoDB 中使用全局二级索引](GSI.md)
+ [本地二级索引](LSI.md)

在确定要使用的索引类型时，应考虑应用程序的要求。下表显示了全局二级索引和本地二级索引之间的主要区别。


****  

| 特征 | 全局二级索引 | 本地二级索引 | 
| --- | --- | --- | 
| 键架构 | 全局二级索引的主键可以是简单主键（分区键）或复合主键（分区键和排序键）。 | 本地二级索引的主键必须是复合主键（分区键和排序键）。 | 
| 键属性 | 索引分区键和排序键 (如果有) 可以是字符串、数字或二进制类型的任何基表属性。 | 索引的分区键是与基表的分区键相同的属性。排序键可以是字符串、数字或二进制类型的任何基表属性。 | 
| 每个分区键值的大小限制 | 全局二级索引没有大小限制。 | 对于每个分区键值，所有索引项目的大小总和必须为 10 GB 或更小。 | 
| 在线索引操作 | 全局二级索引可以在您创建表的同时创建。您还可以向现有表中添加新的全局二级索引，或删除现有的全局二级索引。有关更多信息，请参阅 [在 DynamoDB 中管理全局二级索引](GSI.OnlineOps.md)。 | 本地二级索引在您创建表的同时创建。您不能向现有表添加本地二级索引，也不能删除已存在的任何本地二级索引。 | 
| 查询和分区 | 通过全局二级索引，可以跨所有分区查询整个表。 | 借助本地二级索引，您可以对查询中分区键值指定的单个分区进行查询。 | 
| 读取一致性 | 全局二级索引查询仅支持最终一致性。 | 查询本地二级索引时，您可以选择最终一致性或强一致性。 | 
| 预置吞吐量使用 | 每个全局二级索引都有自己的用于读取和写入活动的预置吞吐量设置。对全局二级索引执行的查询或扫描会占用索引（而非基表）的容量单位。全局二级索引更新也是如此，因为会进行表写入。与全局表关联的全局二级索引会占用写入容量单位。 | 对本地二级索引执行的查询或扫描会占用基表的读取容量单位。写入一个表时，其本地二级索引也将更新；这些更新会占用基表的写入容量单位。与全局表关联的本地二级索引会占用复制的写入容量单位。 | 
| 投影属性 | 对于全局二级索引查询或扫描，您只能请求投影到索引的属性。DynamoDB 不会从表中获取任何属性。 | 如果查询或扫描本地二级索引，可以请求未投影到索引的属性。DynamoDB 会自动从表中获取这些属性。 | 

如果要创建多个含有二级索引的表，必须按顺序执行此操作。例如，您可以创建第一个表，等待其状态变为 `ACTIVE`，创建下一个表，等待其状态变为 `ACTIVE`，依此类推。如果您尝试同时创建多个含有二级索引的表，DynamoDB 会返回 `LimitExceededException`。

每个二级索引都使用与其关联的基表相同的[表类](HowItWorks.TableClasses.html)和[容量模式](capacity-mode.md)。对于每个二级索引，必须指定以下内容：
+ 要创建的索引类型 — 全局二级索引或本地二级索引。
+ 索引的名称。索引的命名规则与表的命名规则相同，具体请参阅 [Amazon DynamoDB 中的配额](ServiceQuotas.md)。就相关联的基表而言，索引的名称必须唯一，不过，与不同的基表相关联的索引的名称可以相同。
+ 索引的键架构。索引键架构中的每个属性必须是类型为 `String`、`Number` 或 `Binary` 的顶级属性。其他数据类型，包括文档和集，均不受支持。键架构的其他要求取决于索引的类型：
  + 对于全局二级索引，分区键可以是基表的任何标量属性。排序键是可选的，也可以是基表的任何标量属性。
  + 对于本地二级索引，分区键必须与基表的分区键相同，排序键必须是非键基表属性。
+ 要从基表投影到索引中的其他属性 (如果有)。这些属性是除表键属性之外的属性，表键属性会自动投影到每个索引。您可以投影任何数据类型的属性，包括标量、文档和集。
+ 索引的预置吞吐量设置（如有必要）：
  + 对于全局二级索引，您必须指定读取和写入容量单位设置。这些预置吞吐量设置独立于基表的设置。
  + 对于本地二级索引，您无需指定读取和写入容量单位设置。对本地二级索引进行的读取和写入操作会占用其基表的预置吞吐量设置。

为获得最高的查询灵活性，您可以为每个表创建最多 20 个全局二级索引（默认配额）和最多 5 个本地二级索引。

要获取表的二级索引的详细列表，请使用 `DescribeTable` 操作。`DescribeTable` 将返回表上每个的名称、存储大小和项目计数。系统并不会实时更新这些值，但会大约每隔六个小时刷新一次。

您可以使用 `Query` 或 `Scan` 操作来访问二级索引中的数据。您必须指定您要使用的基表的名称和索引的名称、要在结果中返回的属性以及要应用的任何条件表达式或筛选条件。DynamoDB 可以按升序或降序返回结果。

删除表时，会同时删除与该表关联的全部索引。

有关最佳实践，请参阅[在 DynamoDB 中使用二级索引的最佳实践](bp-indexes.md)。

# 在 DynamoDB 中使用全局二级索引
<a name="GSI"></a>

一些应用程序可能需要使用很多不同的属性作为查询条件，来执行许多类型的查询。要支持这些要求，您可以创建一个或多个*全局二级索引*，在 Amazon DynamoDB 中针对这些索引发出 `Query` 请求。

**Topics**
+ [场景：使用全局二级索引](#GSI.scenario)
+ [属性投影](#GSI.Projections)
+ [多属性键架构](#GSI.MultiAttributeKeys)
+ [从全局二级索引读取数据](#GSI.Reading)
+ [表与全局二级索引之间的数据同步](#GSI.Writes)
+ [具有全局二级索引的表类别](#GSI.tableclasses)
+ [全局二级索引的预调配吞吐量注意事项](#GSI.ThroughputConsiderations)
+ [全局二级索引的存储注意事项](#GSI.StorageConsiderations)
+ [设计模式](GSI.DesignPatterns.md)
+ [在 DynamoDB 中管理全局二级索引](GSI.OnlineOps.md)
+ [在 DynamoDB 中检测和纠正索引键违规](GSI.OnlineOps.ViolationDetection.md)
+ [处理全局二级索引：Java](GSIJavaDocumentAPI.md)
+ [处理全局二级索引：.NET](GSILowLevelDotNet.md)
+ [借助 AWS CLI 在 DynamoDB 中使用全局二级索引](GCICli.md)

## 场景：使用全局二级索引
<a name="GSI.scenario"></a>

为进行说明，考虑使用一个名为 `GameScores` 的表跟踪一个移动游戏应用程序的用户和分数。`GameScores` 中的每一项使用一个分区键 (`UserId`) 和一个排序键 (`GameTitle`) 标识。下表显示了此表中项目的组织方式，（并未显示所有属性。）

![\[包含用户 ID、头衔、得分、日期和输赢次数的 GameScores 表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_01.png)


现在假设您要编写一个排行榜应用程序以显示每个游戏的最高分数。指定键属性 (`UserId` 和 `GameTitle`) 的查询将会非常高效。但是，如果应用程序仅需要基于 `GameScores` 从 `GameTitle` 检索数据，则需要使用 `Scan` 操作。随着更多项目添加到表中，所有数据的扫描会变得缓慢且低效。这会使得难于回答以下问题：
+ 对游戏 Meteor Blasters 记录的最高分数是多少？
+ 哪个用户拥有 Galaxy Invaders 的最高分数？
+ 最高赢输比是多少？

要加快对非键属性的查询，您可以创建一个全局二级索引。全局二级索引包含从基表中选择的一组属性，但是这些属性按与表主键不同的主键进行排列。索引键不必具有来自表的任何键属性。它甚至不必具有与表相同的键架构。

例如，您可以创建名为 `GameTitleIndex` 的全局二级索引，其分区键为 `GameTitle`，排序键为 `TopScore`。基表的主键属性始终投影到某个索引，因此 `UserId` 属性也存在。`GameTitleIndex` 索引如下图所示。

![\[包含头衔、得分和用户 ID 的 GameTitleIndex 表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_02.png)


现在，您可以查询 `GameTitleIndex` 并方便地获取 Meteor Blasters 的分数。结果按排序键值 `TopScore` 进行排序。如果您将 `ScanIndexForward` 参数设置为 false，则结果按降序返回，因此最高分数最先返回。

每个全局二级索引都必须有分区键，另外可以有可选的排序键。索引键架构可以不同于基表架构。您可以拥有带有简单主键（分区键）的表，然后使用复合主键（分区键和排序键）创建全局二级索引，反之亦然。索引键属性可以包含来自基表的任意顶级 `String`、`Number` 或 `Binary` 属性。不允许使用其他标量类型、文档类型和集合类型。

您可以在需要时将其他基表属性投影到索引。当您查询索引时，DynamoDB 便可高效地检索这些已投影的属性。但是，全局二级索引查询无法从基表提取属性。例如，如果您如上图所示查询 `GameTitleIndex`，则查询无法访问除 `TopScore`（虽然键属性 `GameTitle` 和 `UserId` 将自动投影）之外的任何非键属性。

在 DynamoDB 表中，每个键值都必须唯一。但是，全局二级索引中的键值无需唯一。为进行说明，假设一个名为 Comet Quest 的游戏难度特别高，许多新用户进行尝试，但是无法获得零以上的分数。以下是可以表示这种情况的一些数据。


****  

| UserId | GameTitle | TopScore | 
| --- | --- | --- | 
| 123 | Comet Quest | 0 | 
| 201 | Comet Quest | 0 | 
| 301 | Comet Quest | 0 | 

当此数据添加到 `GameScores` 表中时，DynamoDB 将其传播到 `GameTitleIndex`。如果我们随后以 Comet Quest 作为 `GameTitle` 并以 0 作为 `TopScore` 来查询索引，则将返回以下数据。

![\[包含头衔、最高得分和用户 ID 的表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_05.png)


响应中仅显示具有指定键值的项。在该组数据中，项没有特定顺序。

全局二级索引仅跟踪其键属性实际存在的数据项。例如，假设您向 `GameScores` 表添加了另一个新项目，但是仅提供了必需的主键属性。


****  

| UserId | GameTitle | 
| --- | --- | 
| 400 | Comet Quest | 

因为您未指定 `TopScore` 属性，DynamoDB 不会将此项目传播到 `GameTitleIndex`。因此，如果您针对所有 Comet Quest 项目查询 `GameScores`，则会获得以下四个项目。

![\[包含 4 个头衔列表、最高得分和用户 ID 的表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_04.png)


对 `GameTitleIndex` 执行的相似查询仍会返回三项，而不是四个。这是因为，不存在 `TopScore` 的项不会传播到索引。

![\[包含 3 个头衔列表、最高得分和用户 ID 的表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_05.png)


## 属性投影
<a name="GSI.Projections"></a>

*投影*是从表复制到二级索引的属性集。表的分区键和排序键始终投影到索引中；您可以投影其他属性以支持应用程序的查询要求。当您查询索引时，Amazon DynamoDB 可以访问投影中的任何属性，就像这些属性位于自己的表中一样。

创建二级索引时，需要指定将投影到索引中的属性。DynamoDB 为此提供了三种不同的选项：
+ *KEYS\$1ONLY* – 索引中的每个项目仅包含表的分区键、排序键值以及索引键值。`KEYS_ONLY` 选项会导致最小二级索引。
+ *INCLUDE* – 除 `KEYS_ONLY` 中描述的属性外，二级索引还包括您指定的其他非键属性。
+ *ALL* – 二级索引包括源表中的所有属性。由于所有表数据都在索引中复制，因此 `ALL` 投影会产生最大二级索引。

在上图中，`GameTitleIndex` 只有一个投影属性：`UserId`。因此，尽管应用程序通过在查询中使用 `UserId` 和 `GameTitle` 能够高效确定每个游戏中得分榜选手的 `TopScore`，但不能高效确定得分榜选手的最高输赢比。为此，应用程序必须对基表执行额外查询，以获取每个得分榜选手的输赢数据。要支持对此数据进行查询，更高效的方法是将这些属性从基表投影到全局二级索引，如下图所示。

![\[将非键属性投影到 GSI 中以支持高效查询的描述。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/GSI_06.png)


因为非键属性 `Wins` 和 `Losses` 投影到索引，所以应用程序可以确定任何游戏或是任何游戏和用户 ID 组合的赢输比。

您在选择要投影到全局二级索引的属性时，必须在预置吞吐量成本和存储成本之间做出权衡：
+ 如果只需要访问少量属性，同时尽可能降低延迟，就应考虑仅将键属性投影到全局二级索引。索引越小，存储索引所需的成本越少，并且写入成本也会越少。
+ 如果您的应用程序频繁访问某些非键属性，就应考虑将这些属性投影到全局二级索引。全局二级索引的额外存储成本会抵消频繁执行表扫描的成本。
+ 如果需要频繁访问大多数非键属性，则可以将这些属性（甚至整个基表）投影到全局二级索引中。这为您带来了最大限度的灵活性。但是，您的存储成本将增长，甚至翻倍。
+ 如果您的应用程序并不会频繁查询表，但必须要对表中的数据执行大量写入或更新操作，就应考虑投影 `KEYS_ONLY`。这是最小的全局二级索引，但仍可用于查询活动。

## 多属性键架构
<a name="GSI.MultiAttributeKeys"></a>

全局二级索引支持多属性键，让您可以从多个属性组成分区键和排序键。使用多属性键，您可以从最多四个属性创建分区键，从最多四个属性创建排序键，这样每个键架构最多总共可以有八个属性。

多属性键无需手动将属性连接成合成键，这样可以简化数据模型。您可以直接使用域模型中的自然属性，而不是创建像 `TOURNAMENT#WINTER2024#REGION#NA-EAST` 这样的复合字符串。DynamoDB 自动处理复合键逻辑，对多个分区键属性一起进行哈希处理用于数据分布，并维护多个排序键属性的分层排序顺序。

例如，假设有一个游戏锦标赛系统，您想按锦标赛和地区组织比赛。使用多属性键，您可以将分区键定义为两个单独的属性：`tournamentId` 和 `region`。同样，您可以使用 `round`、`bracket` 和 `matchId` 等多个属性来定义排序键，用于创建自然的层次结构。这种方法可以保持数据类型化和代码整洁，无需对字符串进行操作或解析。

使用多属性键查询全局二级索引时，必须使用相等条件指定所有分区键属性。对于排序键属性，您可以按照它们在键架构中定义的顺序从左到右进行查询。这意味着您可以单独查询第一个排序键属性，同时查询前两个属性，或者同时查询所有属性，但您不能跳过中间的属性。不相等条件（例如 `>`、`<`、`BETWEEN` 或 `begins_with()`）必须是查询中的最后一个条件。

在现有表上创建全局二级索引时，多属性键特别有效。您可以使用表中已经存在的属性，而无需在数据中回填合成键。这样就可以创建索引，使用不同的属性组合来识别数据，从而直接向应用程序添加新的查询模式。

多属性键中的每个属性都可以有自己的数据类型：`String`（S）、`Number`（N）或 `Binary`（B）。选择数据类型时，请考虑 `Number` 属性按数字排序而无需补零，而 `String` 属性则按字典顺序排序。例如，如果您为分数属性使用 `Number` 类型，则值 5、50、500 和 1000 将按自然数字顺序排序。为相同的值使用 `String` 类型时，则排序的结果为“1000”、“5”、“50”、“500”，除非您用前导零填充它们。

在设计多属性键时，请按从最一般到最具体的顺序对属性进行排序。对于分区键，请将始终会一起查询且能够提供良好数据分布的属性组合在一起。对于排序键，请将经常查询的属性放在层次结构的前面，以最大限度地提高查询灵活性。这种排序让您可以按照与访问模式匹配的任何粒度级别进行查询。

有关实施示例，请参阅[多属性键](GSI.DesignPattern.MultiAttributeKeys.md)。

## 从全局二级索引读取数据
<a name="GSI.Reading"></a>

您可以使用 `Query` 和 `Scan` 操作从全局二级索引检索项目。`GetItem` 和 `BatchGetItem` 操作不能用于全局二级索引。

### 查询全局二级索引
<a name="GSI.Querying"></a>

您可以使用 `Query` 操作来访问全局二级索引中的一个或多个项目。查询必须指定要使用的基表名称和索引名称、查询结果中要返回的属性以及要应用的任何查询条件。DynamoDB 可以按升序或降序返回结果。

考虑为排行榜应用程序请求游戏数据的 `Query` 返回的以下数据。

```
{
    "TableName": "GameScores",
    "IndexName": "GameTitleIndex",
    "KeyConditionExpression": "GameTitle = :v_title",
    "ExpressionAttributeValues": {
        ":v_title": {"S": "Meteor Blasters"}
    },
    "ProjectionExpression": "UserId, TopScore",
    "ScanIndexForward": false
}
```

在此查询中：
+ DynamoDB 使用 *GameTitle* 分区键访问 *GameTitleIndex*，查找 Meteor Blasters 的索引项目。具有此键的所有索引项目都彼此相邻存储，以实现快速检索。
+ 在此游戏中，DynamoDB 使用索引访问此游戏的所有用户 ID 和最高分数。
+ 因为 `ScanIndexForward` 参数设置为 false，所以结果按降序返回。

### 扫描全局二级索引
<a name="GSI.Scanning"></a>

您可以使用 `Scan` 操作从全局二级索引检索全部数据。您必须在请求中提供基表名称和索引名称。通过 `Scan`，DynamoDB 可读取索引中的全部数据并将其返回到应用程序。您还可以请求仅返回部分数据并放弃其余数据。为此，请使用 `FilterExpression` 操作的 `Scan` 参数。有关更多信息，请参阅 [扫描的筛选表达式](Scan.md#Scan.FilterExpression)。

## 表与全局二级索引之间的数据同步
<a name="GSI.Writes"></a>

DynamoDB 自动将每个全局二级索引与其基表同步。当应用程序对某个表写入或删除项目时，该表的所有全局二级索引都会使用最终一致性模型异步更新。应用程序绝不会直接向索引中写入内容。但是，您有必要了解 DynamoDB 如何维护这些索引。

 全局二级索引继承基表的读/写入容量模式。有关更多信息，请参阅 [在 DynamoDB 中切换容量模式时的注意事项](bp-switching-capacity-modes.md)。

在创建全局二级索引之后，您可以指定一个或多个索引键属性及其数据类型。这就意味着，无论您何时向基表中写入项目，这些属性的数据类型必须与索引键架构的数据类型匹配。在 `GameTitleIndex` 的情况下，索引中的 `GameTitle` 分区键定义为 `String` 数据类型。索引中的 `TopScore` 排序键为 `Number` 类型。如果您尝试向 `GameScores` 表添加项目并为 `GameTitle` 或 `TopScore` 指定其他数据类型，DynamoDB 会因数据类型不匹配而返回 `ValidationException`。

在表中放置或删除项目时，表的全局二级索引会以最终一致性方式进行更新。在正常情况下，对表数据进行的更改会瞬间传播到全局二级索引。但是，在某些不常发生的故障情况下，可能出现较长时间的传播延迟。因此，应用程序需要预计和处理对全局二级索引进行的查询返回不是最新结果的情况。

如果向表中写入项目，无需指定全局二级索引任何排序键的属性。以 `GameTitleIndex` 为例，您无需指定 `TopScore` 属性的值就可以向 `GameScores` 表写入新项目。在本示例中，DynamoDB 不会向此特定项目的索引写入任何数据。

相较于索引数量较少的表，拥有较多全局二级索引的表会产生较高的写入活动成本。有关更多信息，请参阅 [全局二级索引的预调配吞吐量注意事项](#GSI.ThroughputConsiderations)。

## 具有全局二级索引的表类别
<a name="GSI.tableclasses"></a>

全局二级索引将始终使用与其基表相同的表类别。为表添加新的全局二级索引时，新索引将使用与其基表相同的表类别。更新表的表类别时，所有关联的全局二级索引也会更新。

## 全局二级索引的预调配吞吐量注意事项
<a name="GSI.ThroughputConsiderations"></a>

在预置模式表创建全局二级索引时，必须根据该索引的预期工作负载指定读取和写入容量单位。全局二级索引的预置吞吐量设置独立于其基表的相应设置。对全局二级索引执行的 `Query` 操作占用索引（而非基表）的读取容量单位。在表中放置、更新或删除项目时，还会更新表的全局二级索引。这些索引更新占用索引（而非基表）的写入容量单位。

例如，如果您对全局二级索引执行 `Query` 操作并超过其预配置读取容量，则您的请求会受到阻止。如果您对表执行大量写入活动，但是该表的全局二级索引没有足够写入容量，则对该表进行的写入活动会受到限制。

**重要**  
 为了避免触发可能的限制，全局二级索引的预配置写入容量应等于或大于基表的写入容量，因为新更新将同时写入基表和全局二级索引。

要查看全局二级索引的预配置吞吐量设置，请使用 `DescribeTable` 操作。这将返回表的所有全局二级索引的详细信息。

### 读取容量单位
<a name="GSI.ThroughputConsiderations.Reads"></a>

全局二级索引支持最终一致性读取，每个读取占用一半的读取容量单位。这意味着，单个全局二级索引查询对于每个读取容量单位，可以检索最多 2 × 4 KB = 8 KB。

对于全局二级索引查询，DynamoDB 计算预配置读取活动的方式与对表查询使用的方式相同。唯一不同的是，本次计算基于索引条目的大小，而不是基表中项目的大小。读取容量单位的数量就是返回的所有项目的所有投影属性大小之和。然后，该结果会向上取整到 4 KB 边界。有关 DynamoDB 如何计算预配置吞吐量使用情况的更多信息，请参阅 [DynamoDB 预置容量模式](provisioned-capacity-mode.md)。

`Query` 操作返回的结果大小上限为 1 MB。这包括所有属性名称的大小和所返回的所有项目的值。

例如，请考虑使用每项均包含 2000 字节数据的 全局二级索引。现在假设您对此索引执行 `Query` 操作，并且该查询的 `KeyConditionExpression` 匹配八个项目。匹配项目的总大小为 2000 字节 x 8 个项目 = 16000 字节。然后，该结果会向上取整到最近的 4 KB 边界。由于全局二级索引查询具有最终一致性，因此总成本是 0.5 × (16 KB / 4 KB))，即 2 个读取容量单位。

### 写入容量单位
<a name="GSI.ThroughputConsiderations.Writes"></a>

在添加、更新或删除表中的项目，并且全局二级索引受此影响时，全局二级索引将占用为此操作预配置的写入容量单位。一次写入操作的预配置吞吐量总成本是对基表执行的写入操作以及更新全局二级索引所占用的写入容量单位之和。如果对表执行的写入操作不需要全局二级索引更新，则不会占用索引的写入容量。

要成功写入表，表及其所有全局二级索引的预配置吞吐量设置必须具有足够的写入容量来允许写入。否则，对表的写入将受到限制。

**重要**  
创建全局二级索引（GSI）时，如果写入基表所产生的 GSI 活动超过了 GSI 的预置写入容量，则对基表的写入操作会受限。这种节流会影响所有写入操作，其影响小至干扰索引编制流程，大至可能中断您的生产工作负载。有关更多信息，请参阅 [Amazon DynamoDB 中节流故障排除](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingThrottling.html)。

向全局二级索引写入项目的成本取决于多个因素：
+ 如果您向定义了索引属性的表中写入新项目，或更新现有的项目来定义之前未定义的索引属性，只需一个写入操作即可将项目放置到索引中。
+ 如果对表执行的更新操作更改了索引键属性的值（从 A 更改为 B），就需要执行两次写入操作，一次用于删除索引中之前的项目，另一次用于将新项目放置到索引中。  
+ 如果索引中已有某一项目，而对表执行的写入操作删除了索引属性，就需要执行一次写入操作删除索引中旧的项目投影。
+ 如果更新项目前后索引中没有此项目，此索引就不会额外产生写入成本。
+ 如果对表的更新仅更改了索引键架构中投影属性的值，但不更改任何索引键属性的值，则需要执行一次写入以将投影属性的值更新到索引中。

所有这些因素都假定索引中每个项目的大小小于或等于 1 KB 这一项目大小（用于计算写入容量单位）。如果索引条目大于这一大小，就会占用额外的写入容量单位。您可以考虑查询需要返回的属性类型并仅将这些属性投影到索引中，从而最大程度地减少写入成本。

## 全局二级索引的存储注意事项
<a name="GSI.StorageConsiderations"></a>

当应用程序向表中写入项目时，DynamoDB 会自动将适当的属性子集复制到应包含这些属性的所有全局二级索引。您的 AWS 账户需要支付在基表中存储项目以及在表的任何全局二级索引中存储属性的费用。

索引项目所占用的空间大小就是以下内容之和：
+ 基表的主键 (分区键和排序键) 的大小 (按字节计算)
+ 索引键属性的大小（按字节计算）
+ 投影的属性（如果有）的大小（按字节计算）
+ 每个索引项目 100 字节的开销

要估算全局二级索引的存储要求，您可以估算索引中项目的平均大小，然后乘以基表中具有全局二级索引键属性的项目数。

如果表包含的某个项目未定义特定属性，但是该属性定义为索引分区键或排序键，则 DynamoDB 不会将该项目的任何数据写入到索引中。

# 设计模式
<a name="GSI.DesignPatterns"></a>

设计模式为使用全局二级索引时的常见挑战提供了行之有效的解决方案。这些模式向您展示如何针对特定使用案例设计索引结构，从而帮助您构建高效、可扩展的应用程序。

每种模式都包含完整的实施指南，其中包含代码示例、最佳实践和实际使用案例，帮助您将模式应用于自己的应用程序。

**Topics**
+ [多属性键](GSI.DesignPattern.MultiAttributeKeys.md)

# 多属性键模式
<a name="GSI.DesignPattern.MultiAttributeKeys"></a>

## 概述
<a name="GSI.DesignPattern.MultiAttributeKeys.Overview"></a>

通过多属性键，您可以创建全局二级索引（GSI）分区键和排序键，每个分区键和排序键最多由四个属性组成。这可减少客户端的代码数量，并简化了对数据进行初始建模和以后添加新访问模式的工作。

考虑一个常见的场景：要创建按多个分层属性查询项目的 GSI，传统上需要通过连接值来创建合成键。例如，在游戏应用程序中，如果要按锦标赛、地区和回合查询锦标赛比赛，您可以创建一个合成 GSI 分区键（例如 TOURNAMENT\$1WINTER2024\$1REGION\$1NA-EAST）和一个合成排序键（例如 ROUND\$1SEMIFINALS\$1BRACKET\$1UPPER）。这种方法是可行的，但是在写入数据时需要连接字符串，在读取时需要进行解析，如果要将 GSI 添加到现有表中，还需要在所有现有项目上回填合成键。这使得代码更加混乱，更难维护各个关键组件的类型安全。

多属性键可以解决 GSI 的这个问题。您可以使用多个现有属性（例如 tournamentId 和 region）来定义 GSI 分区键。DynamoDB 会自动处理复合键逻辑，将它们一起进行哈希处理用于数据分布。您使用域模型中的自然属性编写项目，而 GSI 会自动为它们编制索引。无需连接，无需解析，也无需回填。代码保持干净，数据保持类型化，查询保持简单。当您拥有使用自然属性分组的分层数据时（例如锦标赛 → 区域 → 回合，或企业 → 部门 → 团队），这种方法特别有用。

## 应用程序示例
<a name="GSI.DesignPattern.MultiAttributeKeys.ApplicationExample"></a>

本指南介绍如何为电子竞技平台构建锦标赛比赛跟踪系统。该平台需要有效地按多个维度查询比赛：按锦标赛和地区进行分组管理，按玩家查询比赛历史记录，按日期进行排程。

## 数据模型
<a name="GSI.DesignPattern.MultiAttributeKeys.DataModel"></a>

在本演练中，锦标赛比赛跟踪系统支持三种主要访问模式，每种模式都需要不同的键结构：

**访问模式 1：**通过唯一 ID 查找特定比赛
+ **解决方案：**使用 `matchId` 作为分区键的基表

**访问模式 2：**查询特定锦标赛和地区的所有比赛，可以选择按回合、分组或比赛进行筛选
+ **解决方案：**使用多属性分区键（`tournamentId` \$1 `region`）和多属性排序键（`round` \$1`bracket` \$1 `matchId`）的全局二级索引
+ **查询示例：**“北美东部地区的所有 WINTER2024 比赛”或“北美东部地区 WINTER2024 上半区分组的所有半决赛”

**访问模式 3：**查询某个玩家的比赛历史记录，可以选择按日期范围或锦标赛回合进行筛选
+ **解决方案：**使用单分区键（`player1Id`）和多属性排序键（`matchDate` \$1 `round`）的全局二级索引
+ **查询示例：**“玩家 101 的所有比赛”或“玩家 101 在 2024 年 1 月的比赛”

通过查看项目结构，您可以清楚地看到传统方法与多属性方法之间的主要区别：

**传统的全局二级索引方法（连接键）：**

```
// Manual concatenation required for GSI keys
const item = {
    matchId: 'match-001',                                          // Base table PK
    tournamentId: 'WINTER2024',
    region: 'NA-EAST',
    round: 'SEMIFINALS',
    bracket: 'UPPER',
    player1Id: '101',
    // Synthetic keys needed for GSI
    GSI_PK: `TOURNAMENT#${tournamentId}#REGION#${region}`,       // Must concatenate
    GSI_SK: `${round}#${bracket}#${matchId}`,                    // Must concatenate
    // ... other attributes
};
```

**多属性全局二级索引方法（原生键）：**

```
// Use existing attributes directly - no concatenation needed
const item = {
    matchId: 'match-001',                                          // Base table PK
    tournamentId: 'WINTER2024',
    region: 'NA-EAST',
    round: 'SEMIFINALS',
    bracket: 'UPPER',
    player1Id: '101',
    matchDate: '2024-01-18',
    // No synthetic keys needed - GSI uses existing attributes directly
    // ... other attributes
};
```

使用多属性键，您将具有自然领域属性的项目写入一次。DynamoDB 会自动将它们编入多个 GSI 的索引，而无需合成连接键。

**基表架构：**
+ 分区键：`matchId`（1 个属性）

**全局二级索引架构（具有多属性键的 TournamentRegionIndex）：**
+ 分区键：`tournamentId`、`region`（2 个属性）
+ 排序键：`round`、`bracket`、`matchId`（3 个属性）

**全局二级索引架构（具有多属性键的 PlayerMatchHistoryIndex）：**
+ 分区键：`player1Id`（1 个属性）
+ 排序键：`matchDate`、`round`（2 个属性）

### 基表：TournamentMatches
<a name="GSI.DesignPattern.MultiAttributeKeys.BaseTable"></a>


| matchId（PK） | tournamentId | 区域 | round | bracket | player1Id | player2Id | matchDate | winner | 分数 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| match-001 | WINTER2024 | NA-EAST | FINALS | CHAMPIONSHIP | 101 | 103 | 2024-01-20 | 101 | 3-1 | 
| match-002 | WINTER2024 | NA-EAST | SEMIFINALS | UPPER | 101 | 105 | 2024-01-18 | 101 | 3-2 | 
| match-003 | WINTER2024 | NA-EAST | SEMIFINALS | UPPER | 103 | 107 | 2024-01-18 | 103 | 3-0 | 
| match-004 | WINTER2024 | NA-EAST | QUARTERFINALS | UPPER | 101 | 109 | 2024-01-15 | 101 | 3-1 | 
| match-005 | WINTER2024 | NA-WEST | FINALS | CHAMPIONSHIP | 102 | 104 | 2024-01-20 | 102 | 3-2 | 
| match-006 | WINTER2024 | NA-WEST | SEMIFINALS | UPPER | 102 | 106 | 2024-01-18 | 102 | 3-1 | 
| match-007 | SPRING2024 | NA-EAST | QUARTERFINALS | UPPER | 101 | 108 | 2024-03-15 | 101 | 3-0 | 
| match-008 | SPRING2024 | NA-EAST | QUARTERFINALS | LOWER | 103 | 110 | 2024-03-15 | 103 | 3-2 | 

### GSI：TournamentRegionIndex（多属性键）
<a name="GSI.DesignPattern.MultiAttributeKeys.TournamentRegionIndexTable"></a>


| tournamentId（PK） | region（PK） | round（SK） | bracket（SK） | matchId（SK） | player1Id | player2Id | matchDate | winner | 分数 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| WINTER2024 | NA-EAST | FINALS | CHAMPIONSHIP | match-001 | 101 | 103 | 2024-01-20 | 101 | 3-1 | 
| WINTER2024 | NA-EAST | QUARTERFINALS | UPPER | match-004 | 101 | 109 | 2024-01-15 | 101 | 3-1 | 
| WINTER2024 | NA-EAST | SEMIFINALS | UPPER | match-002 | 101 | 105 | 2024-01-18 | 101 | 3-2 | 
| WINTER2024 | NA-EAST | SEMIFINALS | UPPER | match-003 | 103 | 107 | 2024-01-18 | 103 | 3-0 | 
| WINTER2024 | NA-WEST | FINALS | CHAMPIONSHIP | match-005 | 102 | 104 | 2024-01-20 | 102 | 3-2 | 
| WINTER2024 | NA-WEST | SEMIFINALS | UPPER | match-006 | 102 | 106 | 2024-01-18 | 102 | 3-1 | 
| SPRING2024 | NA-EAST | QUARTERFINALS | LOWER | match-008 | 103 | 110 | 2024-03-15 | 103 | 3-2 | 
| SPRING2024 | NA-EAST | QUARTERFINALS | UPPER | match-007 | 101 | 108 | 2024-03-15 | 101 | 3-0 | 

### GSI：PlayerMatchHistoryIndex（多属性键）
<a name="GSI.DesignPattern.MultiAttributeKeys.PlayerMatchHistoryIndexTable"></a>


| player1Id（PK） | matchDate（SK） | round（SK） | tournamentId | 区域 | bracket | matchId | player2Id | winner | 分数 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| 101 | 2024-01-15 | QUARTERFINALS | WINTER2024 | NA-EAST | UPPER | match-004 | 109 | 101 | 3-1 | 
| 101 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-EAST | UPPER | match-002 | 105 | 101 | 3-2 | 
| 101 | 2024-01-20 | FINALS | WINTER2024 | NA-EAST | CHAMPIONSHIP | match-001 | 103 | 101 | 3-1 | 
| 101 | 2024-03-15 | QUARTERFINALS | SPRING2024 | NA-EAST | UPPER | match-007 | 108 | 101 | 3-0 | 
| 102 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-WEST | UPPER | match-006 | 106 | 102 | 3-1 | 
| 102 | 2024-01-20 | FINALS | WINTER2024 | NA-WEST | CHAMPIONSHIP | match-005 | 104 | 102 | 3-2 | 
| 103 | 2024-01-18 | SEMIFINALS | WINTER2024 | NA-EAST | UPPER | match-003 | 107 | 103 | 3-0 | 
| 103 | 2024-03-15 | QUARTERFINALS | SPRING2024 | NA-EAST | LOWER | match-008 | 110 | 103 | 3-2 | 

## 先决条件
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites"></a>

在开始之前，请确保您满足以下条件：

### 账户和权限
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.AWSAccount"></a>
+ 有效 AWS 账户（如有必要，[可在此处创建](https://aws.amazon.com/free/)）
+ DynamoDB 操作的 IAM 权限：
  + `dynamodb:CreateTable`
  + `dynamodb:DeleteTable`
  + `dynamodb:DescribeTable`
  + `dynamodb:PutItem`
  + `dynamodb:Query`
  + `dynamodb:BatchWriteItem`

**注意**  
**安全说明：**对于生产用途，请创建仅包含所需权限的自定义 IAM 策略。在本教程中，您可以使用 AWS 托管式策略 `AmazonDynamoDBFullAccessV2`。

### 开发环境
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.DevEnvironment"></a>
+ 计算机上已安装 Node.js
+ 使用以下方法之一配置的 AWS 凭证：

**选项 1：AWS CLI**

```
aws configure
```

**选项 2：环境变量**

```
export AWS_ACCESS_KEY_ID=your_access_key_here
export AWS_SECRET_ACCESS_KEY=your_secret_key_here
export AWS_DEFAULT_REGION=us-east-1
```

### 安装所需的程序包
<a name="GSI.DesignPattern.MultiAttributeKeys.Prerequisites.InstallPackages"></a>

```
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
```

## 实现
<a name="GSI.DesignPattern.MultiAttributeKeys.Implementation"></a>

### 步骤 1：使用多属性键创建带有 GSI 的表
<a name="GSI.DesignPattern.MultiAttributeKeys.CreateTable"></a>

创建具有简单基本键结构和使用多属性键的 GSI 的表。

#### 代码示例
<a name="w2aac19c13c45c23b9c11b3b5b1"></a>

```
import { DynamoDBClient, CreateTableCommand } from "@aws-sdk/client-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });

const response = await client.send(new CreateTableCommand({
    TableName: 'TournamentMatches',
    
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'matchId', KeyType: 'HASH' }              // Simple PK
    ],
    
    AttributeDefinitions: [
        { AttributeName: 'matchId', AttributeType: 'S' },
        { AttributeName: 'tournamentId', AttributeType: 'S' },
        { AttributeName: 'region', AttributeType: 'S' },
        { AttributeName: 'round', AttributeType: 'S' },
        { AttributeName: 'bracket', AttributeType: 'S' },
        { AttributeName: 'player1Id', AttributeType: 'S' },
        { AttributeName: 'matchDate', AttributeType: 'S' }
    ],
    
    // GSIs with multi-attribute keys
    GlobalSecondaryIndexes: [
        {
            IndexName: 'TournamentRegionIndex',
            KeySchema: [
                { AttributeName: 'tournamentId', KeyType: 'HASH' },    // GSI PK attribute 1
                { AttributeName: 'region', KeyType: 'HASH' },          // GSI PK attribute 2
                { AttributeName: 'round', KeyType: 'RANGE' },          // GSI SK attribute 1
                { AttributeName: 'bracket', KeyType: 'RANGE' },        // GSI SK attribute 2
                { AttributeName: 'matchId', KeyType: 'RANGE' }         // GSI SK attribute 3
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'PlayerMatchHistoryIndex',
            KeySchema: [
                { AttributeName: 'player1Id', KeyType: 'HASH' },       // GSI PK
                { AttributeName: 'matchDate', KeyType: 'RANGE' },      // GSI SK attribute 1
                { AttributeName: 'round', KeyType: 'RANGE' }           // GSI SK attribute 2
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    
    BillingMode: 'PAY_PER_REQUEST'
}));

console.log("Table with multi-attribute GSI keys created successfully");
```

**关键设计决策：**

**基表：**基表使用简单的 `matchId` 分区键直接查询比赛，保持基表结构简单，而 GSI 则提供复杂的查询模式。

**TournamentRegionIndex 全局二级索引：**`TournamentRegionIndex` 全局二级索引使用 `tournamentId` \$1 `region` 作为多属性分区键，创建锦标赛-区域隔离，其中数据按两个属性的组合哈希分布，从而实现在特定的锦标赛-区域上下文中的高效查询。多属性排序键（`round` \$1 `bracket` \$1 `matchId`）提供分层排序，该排序支持层次结构中任何级别的查询，并采用从一般（回合）到特定（比赛 ID）的自然排序。

**PlayerMatchHistoryIndex 全局二级索引：**`PlayerMatchHistoryIndex` 全局二级索引使用 `player1Id` 作为分区键来按玩家重新组织数据，从而实现针对特定玩家的跨锦标赛查询。多属性排序键（`matchDate` \$1 `round`）按时间顺序排序，能够按日期范围或特定锦标赛回合进行筛选。

### 步骤 2：插入具有原生属性的数据
<a name="GSI.DesignPattern.MultiAttributeKeys.InsertData"></a>

使用自然属性添加锦标赛比赛数据。GSI 将自动为这些属性编制索引，无需合成键。

#### 代码示例
<a name="w2aac19c13c45c23b9c11b5b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Tournament match data - no synthetic keys needed for GSIs
const matches = [
    // Winter 2024 Tournament, NA-EAST region
    {
        matchId: 'match-001',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'FINALS',
        bracket: 'CHAMPIONSHIP',
        player1Id: '101',
        player2Id: '103',
        matchDate: '2024-01-20',
        winner: '101',
        score: '3-1'
    },
    {
        matchId: 'match-002',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '105',
        matchDate: '2024-01-18',
        winner: '101',
        score: '3-2'
    },
    {
        matchId: 'match-003',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '103',
        player2Id: '107',
        matchDate: '2024-01-18',
        winner: '103',
        score: '3-0'
    },
    {
        matchId: 'match-004',
        tournamentId: 'WINTER2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '109',
        matchDate: '2024-01-15',
        winner: '101',
        score: '3-1'
    },
    
    // Winter 2024 Tournament, NA-WEST region
    {
        matchId: 'match-005',
        tournamentId: 'WINTER2024',
        region: 'NA-WEST',
        round: 'FINALS',
        bracket: 'CHAMPIONSHIP',
        player1Id: '102',
        player2Id: '104',
        matchDate: '2024-01-20',
        winner: '102',
        score: '3-2'
    },
    {
        matchId: 'match-006',
        tournamentId: 'WINTER2024',
        region: 'NA-WEST',
        round: 'SEMIFINALS',
        bracket: 'UPPER',
        player1Id: '102',
        player2Id: '106',
        matchDate: '2024-01-18',
        winner: '102',
        score: '3-1'
    },
    
    // Spring 2024 Tournament, NA-EAST region
    {
        matchId: 'match-007',
        tournamentId: 'SPRING2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'UPPER',
        player1Id: '101',
        player2Id: '108',
        matchDate: '2024-03-15',
        winner: '101',
        score: '3-0'
    },
    {
        matchId: 'match-008',
        tournamentId: 'SPRING2024',
        region: 'NA-EAST',
        round: 'QUARTERFINALS',
        bracket: 'LOWER',
        player1Id: '103',
        player2Id: '110',
        matchDate: '2024-03-15',
        winner: '103',
        score: '3-2'
    }
];

// Insert all matches
for (const match of matches) {
    await docClient.send(new PutCommand({
        TableName: 'TournamentMatches',
        Item: match
    }));
    
    console.log(`Added: ${match.matchId} - ${match.tournamentId}/${match.region} - ${match.round} ${match.bracket}`);
}

console.log(`\nInserted ${matches.length} tournament matches`);
console.log("No synthetic keys created - GSIs use native attributes automatically");
```

**数据结构解释：**

**自然属性用法：**每个属性都代表一个真实的锦标赛概念，无需字符串连接或解析，可直接映射到领域模型。

**全局二级索引自动编制索引：**GSI 自动使用现有属性（对于 TournamentRegionIndex 为 `tournamentId`、`region`、`round`、`bracket`、`matchId`，对于 PlayerMatchHistoryIndex 为 `player1Id`、`matchDate`、`round`）为项目编制索引，而无需合成连接键。

**无需回填：**向现有表添加带有多属性键的新全局二级索引时，DynamoDB 会使用其自然属性自动为所有现有项目编制索引，无需使用合成键更新项目。

### 第 3 步：使用所有分区键属性查询 TournamentRegionIndex 全局二级索引
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryAllPartitionKeys"></a>

此示例查询具有多属性分区键（`tournamentId` \$1 `region`）的 TournamentRegionIndex 全局二级索引。所有分区键属性都必须在查询中使用相等条件来指定，不能单独使用 `tournamentId` 进行查询，也不能对分区键属性使用不相等运算符。

#### 代码示例
<a name="w2aac19c13c45c23b9c11b7b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query GSI: All matches for WINTER2024 tournament in NA-EAST region
const response = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region',
    ExpressionAttributeNames: {
        '#region': 'region',  // 'region' is a reserved keyword
        '#tournament': 'tournament'
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST'
    }
}));

console.log(`Found ${response.Items.length} matches for WINTER2024/NA-EAST:\n`);
response.Items.forEach(match => {
    console.log(`  ${match.round} | ${match.bracket} | ${match.matchId}`);
    console.log(`    Players: ${match.player1Id} vs ${match.player2Id}`);
    console.log(`    Winner: ${match.winner}, Score: ${match.score}\n`);
});
```

**预期输出。**

```
Found 4 matches for WINTER2024/NA-EAST:

  FINALS | CHAMPIONSHIP | match-001
    Players: 101 vs 103
    Winner: 101, Score: 3-1

  QUARTERFINALS | UPPER | match-004
    Players: 101 vs 109
    Winner: 101, Score: 3-1

  SEMIFINALS | UPPER | match-002
    Players: 101 vs 105
    Winner: 101, Score: 3-2

  SEMIFINALS | UPPER | match-003
    Players: 103 vs 107
    Winner: 103, Score: 3-0
```

**无效的查询：**

```
// Missing region attribute
KeyConditionExpression: 'tournamentId = :tournament'

// Using inequality on partition key attribute
KeyConditionExpression: 'tournamentId = :tournament AND #region > :region'
```

**性能：**多属性分区键一起进行哈希处理，提供与单属性键相同的 O(1) 查找性能。

### 步骤 4：从左到右查询全局二级索引排序键
<a name="GSI.DesignPattern.MultiAttributeKeys.QuerySortKeysLeftToRight"></a>

排序键属性必须按照其在全局二级索引中定义的顺序，从左到右进行查询。此示例演示了在不同的层次结构级别上查询 TournamentRegionIndex：仅按 `round`、按 `round` \$1 `bracket` 或按所有三个排序键属性进行筛选。您不能跳过中间的属性，例如，您不能按照 `round` 和 `matchId` 而跳过 `bracket` 进行查询。

#### 代码示例
<a name="w2aac19c13c45c23b9c11b9b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: Filter by first sort key attribute (round)
console.log("Query 1: All SEMIFINALS matches");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS'
    }
}));
console.log(`  Found ${query1.Items.length} matches\n`);

// Query 2: Filter by first two sort key attributes (round + bracket)
console.log("Query 2: SEMIFINALS UPPER bracket matches");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':bracket': 'UPPER'
    }
}));
console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Filter by all three sort key attributes (round + bracket + matchId)
console.log("Query 3: Specific match in SEMIFINALS UPPER bracket");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND bracket = :bracket AND matchId = :matchId',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':bracket': 'UPPER',
        ':matchId': 'match-002'
    }
}));
console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: INVALID - skipping round
console.log("Query 4: Attempting to skip first sort key attribute (WILL FAIL)");
try {
    const query4 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND bracket = :bracket',
        ExpressionAttributeNames: {
            '#region': 'region'  // 'region' is a reserved keyword
        },
        ExpressionAttributeValues: {
            ':tournament': 'WINTER2024',
            ':region': 'NA-EAST',
            ':bracket': 'UPPER'
        }
    }));
} catch (error) {
    console.log(`  Error: ${error.message}`);
    console.log(`  Cannot skip sort key attributes - must query left-to-right\n`);
}
```

**预期输出。**

```
Query 1: All SEMIFINALS matches
  Found 2 matches

Query 2: SEMIFINALS UPPER bracket matches
  Found 2 matches

Query 3: Specific match in SEMIFINALS UPPER bracket
  Found 1 matches

Query 4: Attempting to skip first sort key attribute (WILL FAIL)
  Error: Query key condition not supported
  Cannot skip sort key attributes - must query left-to-right
```

**从左到右的查询规则：**您必须按从左到右的顺序查询属性，不能跳过任何属性。

**有效模式：**
+ 仅第一个属性：`round = 'SEMIFINALS'`
+ 前两个属性：`round = 'SEMIFINALS' AND bracket = 'UPPER'`
+ 全部三个属性：`round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId = 'match-002'`

**无效的模式：**
+ 跳过第一个属性：`bracket = 'UPPER'`（跳过回合）
+ 乱序查询：`matchId = 'match-002' AND round = 'SEMIFINALS'`
+ 留空白：`round = 'SEMIFINALS' AND matchId = 'match-002'`（跳过分组）

**注意**  
**设计提示：**按从最一般到最具体的顺序对排序键属性进行排序，以充分提高查询灵活性。

### 步骤 5：在全局二级索引排序键上使用不相等条件
<a name="GSI.DesignPattern.MultiAttributeKeys.InequalityConditions"></a>

不相等条件必须是查询中的最后一个条件。此示例演示在排序键属性上使用比较运算符（`>=`、`BETWEEN`）和前缀匹配（`begins_with()`）。如果您使用了不相等运算符，就无法在后面添加任何其他排序键条件，不相等必须是键条件表达式中的最后一个条件。

#### 代码示例
<a name="w2aac19c13c45c23b9c11c11b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: Round comparison (inequality on first sort key attribute)
console.log("Query 1: Matches from QUARTERFINALS onwards");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round >= :round',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'QUARTERFINALS'
    }
}));
console.log(`  Found ${query1.Items.length} matches\n`);

// Query 2: Round range with BETWEEN
console.log("Query 2: Matches between QUARTERFINALS and SEMIFINALS");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round BETWEEN :start AND :end',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':start': 'QUARTERFINALS',
        ':end': 'SEMIFINALS'
    }
}));
console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Prefix matching with begins_with (treated as inequality)
console.log("Query 3: Matches in brackets starting with 'U'");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'TournamentRegionIndex',
    KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round = :round AND begins_with(bracket, :prefix)',
    ExpressionAttributeNames: {
        '#region': 'region'  // 'region' is a reserved keyword
    },
    ExpressionAttributeValues: {
        ':tournament': 'WINTER2024',
        ':region': 'NA-EAST',
        ':round': 'SEMIFINALS',
        ':prefix': 'U'
    }
}));
console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: INVALID - condition after inequality
console.log("Query 4: Attempting condition after inequality (WILL FAIL)");
try {
    const query4 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region AND round > :round AND bracket = :bracket',
        ExpressionAttributeNames: {
            '#region': 'region'  // 'region' is a reserved keyword
        },
        ExpressionAttributeValues: {
            ':tournament': 'WINTER2024',
            ':region': 'NA-EAST',
            ':round': 'QUARTERFINALS',
            ':bracket': 'UPPER'
        }
    }));
} catch (error) {
    console.log(`  Error: ${error.message}`);
    console.log(`  Cannot add conditions after inequality - it must be last\n`);
}
```

**不相等运算符规则：**您可以使用比较运算符（`>`、`>=`、`<`、`<=`），`BETWEEN` 用于范围查询，`begins_with()` 用于前缀匹配。不相等必须是查询中的最后一个条件。

**有效模式：**
+ 相等条件后跟不相等：`round = 'SEMIFINALS' AND bracket = 'UPPER' AND matchId > 'match-001'`
+ 第一个属性上的不相等：`round BETWEEN 'QUARTERFINALS' AND 'SEMIFINALS'`
+ 前缀匹配作为最后一个条件：`round = 'SEMIFINALS' AND begins_with(bracket, 'U')`

**无效的模式：**
+ 在不相等后添加条件：`round > 'QUARTERFINALS' AND bracket = 'UPPER'`
+ 使用多个不相等：`round > 'QUARTERFINALS' AND bracket > 'L'`

**重要**  
`begins_with()` 被视为不相等条件，因此后面不能有其他排序键条件。

### 步骤 6：查询具有多属性排序键的 PlayerMatchHistoryIndex 全局二级索引架构
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryPlayerHistory"></a>

此示例查询 PlayerMatchHistoryIndex，其中有单个分区键（`player1Id`）和多属性排序键（`matchDate` \$1 `round`）。这样就实现了跨锦标赛的分析，可以查询某个特定玩家的所有比赛，而无需知道锦标赛 ID，而基表则需要对每个锦标赛-区域组合进行单独查询。

#### 代码示例
<a name="w2aac19c13c45c23b9c11c13b5b1"></a>

```
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

// Query 1: All matches for Player 101 across all tournaments
console.log("Query 1: All matches for Player 101");
const query1 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player',
    ExpressionAttributeValues: {
        ':player': '101'
    }
}));

console.log(`  Found ${query1.Items.length} matches for Player 101:`);
query1.Items.forEach(match => {
    console.log(`    ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`);
});
console.log();

// Query 2: Player 101 matches on specific date
console.log("Query 2: Player 101 matches on 2024-01-18");
const query2 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate = :date',
    ExpressionAttributeValues: {
        ':player': '101',
        ':date': '2024-01-18'
    }
}));

console.log(`  Found ${query2.Items.length} matches\n`);

// Query 3: Player 101 SEMIFINALS matches on specific date
console.log("Query 3: Player 101 SEMIFINALS matches on 2024-01-18");
const query3 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate = :date AND round = :round',
    ExpressionAttributeValues: {
        ':player': '101',
        ':date': '2024-01-18',
        ':round': 'SEMIFINALS'
    }
}));

console.log(`  Found ${query3.Items.length} matches\n`);

// Query 4: Player 101 matches in date range
console.log("Query 4: Player 101 matches in January 2024");
const query4 = await docClient.send(new QueryCommand({
    TableName: 'TournamentMatches',
    IndexName: 'PlayerMatchHistoryIndex',
    KeyConditionExpression: 'player1Id = :player AND matchDate BETWEEN :start AND :end',
    ExpressionAttributeValues: {
        ':player': '101',
        ':start': '2024-01-01',
        ':end': '2024-01-31'
    }
}));

console.log(`  Found ${query4.Items.length} matches\n`);
```

## 模式变化
<a name="GSI.DesignPattern.MultiAttributeKeys.PatternVariations"></a>

### 具有多属性键的时间序列数据
<a name="GSI.DesignPattern.MultiAttributeKeys.TimeSeries"></a>

针对具有分层时间属性的时间序列查询进行优化

#### 代码示例
<a name="w2aac19c13c45c23b9c13b3b5b1"></a>

```
{
    TableName: 'IoTReadings',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'readingId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'readingId', AttributeType: 'S' },
        { AttributeName: 'deviceId', AttributeType: 'S' },
        { AttributeName: 'locationId', AttributeType: 'S' },
        { AttributeName: 'year', AttributeType: 'S' },
        { AttributeName: 'month', AttributeType: 'S' },
        { AttributeName: 'day', AttributeType: 'S' },
        { AttributeName: 'timestamp', AttributeType: 'S' }
    ],
    // GSI with multi-attribute keys for time-series queries
    GlobalSecondaryIndexes: [{
        IndexName: 'DeviceLocationTimeIndex',
        KeySchema: [
            { AttributeName: 'deviceId', KeyType: 'HASH' },
            { AttributeName: 'locationId', KeyType: 'HASH' },
            { AttributeName: 'year', KeyType: 'RANGE' },
            { AttributeName: 'month', KeyType: 'RANGE' },
            { AttributeName: 'day', KeyType: 'RANGE' },
            { AttributeName: 'timestamp', KeyType: 'RANGE' }
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query patterns enabled via GSI:
// - All readings for device in location
// - Readings for specific year
// - Readings for specific month in year
// - Readings for specific day
// - Readings in time range
```

**优点：**自然时间层次结构（年 → 月 → 日 → 时间戳），允许在任何时间粒度进行高效查询，无需解析或操作日期。全局二级索引使用其自然时间属性，自动为所有读数编制索引。

### 带有多属性键的电子商务订单
<a name="GSI.DesignPattern.MultiAttributeKeys.ECommerce"></a>

按多个维度追踪订单

#### 代码示例
<a name="w2aac19c13c45c23b9c13b5b5b1"></a>

```
{
    TableName: 'Orders',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'orderId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'orderId', AttributeType: 'S' },
        { AttributeName: 'sellerId', AttributeType: 'S' },
        { AttributeName: 'region', AttributeType: 'S' },
        { AttributeName: 'orderDate', AttributeType: 'S' },
        { AttributeName: 'category', AttributeType: 'S' },
        { AttributeName: 'customerId', AttributeType: 'S' },
        { AttributeName: 'orderStatus', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'SellerRegionIndex',
            KeySchema: [
                { AttributeName: 'sellerId', KeyType: 'HASH' },
                { AttributeName: 'region', KeyType: 'HASH' },
                { AttributeName: 'orderDate', KeyType: 'RANGE' },
                { AttributeName: 'category', KeyType: 'RANGE' },
                { AttributeName: 'orderId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'CustomerOrdersIndex',
            KeySchema: [
                { AttributeName: 'customerId', KeyType: 'HASH' },
                { AttributeName: 'orderDate', KeyType: 'RANGE' },
                { AttributeName: 'orderStatus', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// SellerRegionIndex GSI queries:
// - Orders by seller and region
// - Orders by seller, region, and date
// - Orders by seller, region, date, and category

// CustomerOrdersIndex GSI queries:
// - Customer's orders
// - Customer's orders by date
// - Customer's orders by date and status
```

### 分层组织数据
<a name="GSI.DesignPattern.MultiAttributeKeys.Hierarchical"></a>

组织层次结构建模

#### 代码示例
<a name="w2aac19c13c45c23b9c13b7b5b1"></a>

```
{
    TableName: 'Employees',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'employeeId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'employeeId', AttributeType: 'S' },
        { AttributeName: 'companyId', AttributeType: 'S' },
        { AttributeName: 'divisionId', AttributeType: 'S' },
        { AttributeName: 'departmentId', AttributeType: 'S' },
        { AttributeName: 'teamId', AttributeType: 'S' },
        { AttributeName: 'skillCategory', AttributeType: 'S' },
        { AttributeName: 'skillLevel', AttributeType: 'S' },
        { AttributeName: 'yearsExperience', AttributeType: 'N' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'OrganizationIndex',
            KeySchema: [
                { AttributeName: 'companyId', KeyType: 'HASH' },
                { AttributeName: 'divisionId', KeyType: 'HASH' },
                { AttributeName: 'departmentId', KeyType: 'RANGE' },
                { AttributeName: 'teamId', KeyType: 'RANGE' },
                { AttributeName: 'employeeId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'SkillsIndex',
            KeySchema: [
                { AttributeName: 'skillCategory', KeyType: 'HASH' },
                { AttributeName: 'skillLevel', KeyType: 'RANGE' },
                { AttributeName: 'yearsExperience', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'INCLUDE', NonKeyAttributes: ['employeeId', 'name'] }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// OrganizationIndex GSI query patterns:
// - All employees in company/division
// - Employees in specific department
// - Employees in specific team

// SkillsIndex GSI query patterns:
// - Employees by skill and experience level
```

### 稀疏多属性键
<a name="GSI.DesignPattern.MultiAttributeKeys.Sparse"></a>

组合多属性键以生成稀疏 GSI

#### 代码示例
<a name="w2aac19c13c45c23b9c13b9b5b1"></a>

```
{
    TableName: 'Products',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'productId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'productId', AttributeType: 'S' },
        { AttributeName: 'categoryId', AttributeType: 'S' },
        { AttributeName: 'subcategoryId', AttributeType: 'S' },
        { AttributeName: 'averageRating', AttributeType: 'N' },
        { AttributeName: 'reviewCount', AttributeType: 'N' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'CategoryIndex',
            KeySchema: [
                { AttributeName: 'categoryId', KeyType: 'HASH' },
                { AttributeName: 'subcategoryId', KeyType: 'HASH' },
                { AttributeName: 'productId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'ReviewedProductsIndex',
            KeySchema: [
                { AttributeName: 'categoryId', KeyType: 'HASH' },
                { AttributeName: 'averageRating', KeyType: 'RANGE' },  // Optional attribute
                { AttributeName: 'reviewCount', KeyType: 'RANGE' }     // Optional attribute
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// Only products with reviews appear in ReviewedProductsIndex GSI
// Automatic filtering without application logic
// Multi-attribute sort key enables rating and count queries
```

### SaaS 多租赁
<a name="GSI.DesignPattern.MultiAttributeKeys.SaaS"></a>

具有客户隔离功能的多租户 SaaS 平台

#### 代码示例
<a name="w2aac19c13c45c23b9c13c11b5b1"></a>

```
// Table design
{
    TableName: 'SaasData',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'resourceId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'resourceId', AttributeType: 'S' },
        { AttributeName: 'tenantId', AttributeType: 'S' },
        { AttributeName: 'customerId', AttributeType: 'S' },
        { AttributeName: 'resourceType', AttributeType: 'S' }
    ],
    // GSI with multi-attribute keys for tenant-customer isolation
    GlobalSecondaryIndexes: [{
        IndexName: 'TenantCustomerIndex',
        KeySchema: [
            { AttributeName: 'tenantId', KeyType: 'HASH' },
            { AttributeName: 'customerId', KeyType: 'HASH' },
            { AttributeName: 'resourceType', KeyType: 'RANGE' },
            { AttributeName: 'resourceId', KeyType: 'RANGE' }
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query GSI: All resources for tenant T001, customer C001
const resources = await docClient.send(new QueryCommand({
    TableName: 'SaasData',
    IndexName: 'TenantCustomerIndex',
    KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer',
    ExpressionAttributeValues: {
        ':tenant': 'T001',
        ':customer': 'C001'
    }
}));

// Query GSI: Specific resource type for tenant/customer
const documents = await docClient.send(new QueryCommand({
    TableName: 'SaasData',
    IndexName: 'TenantCustomerIndex',
    KeyConditionExpression: 'tenantId = :tenant AND customerId = :customer AND resourceType = :type',
    ExpressionAttributeValues: {
        ':tenant': 'T001',
        ':customer': 'C001',
        ':type': 'document'
    }
}));
```

**好处：**在租户-客户上下文和自然数据组织中高效地进行查询。

### 金融交易
<a name="GSI.DesignPattern.MultiAttributeKeys.Financial"></a>

银行系统使用 GSI 跟踪账户交易

#### 代码示例
<a name="w2aac19c13c45c23b9c13c13b5b1"></a>

```
// Table design
{
    TableName: 'BankTransactions',
    // Base table: Simple partition key
    KeySchema: [
        { AttributeName: 'transactionId', KeyType: 'HASH' }
    ],
    AttributeDefinitions: [
        { AttributeName: 'transactionId', AttributeType: 'S' },
        { AttributeName: 'accountId', AttributeType: 'S' },
        { AttributeName: 'year', AttributeType: 'S' },
        { AttributeName: 'month', AttributeType: 'S' },
        { AttributeName: 'day', AttributeType: 'S' },
        { AttributeName: 'transactionType', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [
        {
            IndexName: 'AccountTimeIndex',
            KeySchema: [
                { AttributeName: 'accountId', KeyType: 'HASH' },
                { AttributeName: 'year', KeyType: 'RANGE' },
                { AttributeName: 'month', KeyType: 'RANGE' },
                { AttributeName: 'day', KeyType: 'RANGE' },
                { AttributeName: 'transactionId', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        },
        {
            IndexName: 'TransactionTypeIndex',
            KeySchema: [
                { AttributeName: 'accountId', KeyType: 'HASH' },
                { AttributeName: 'transactionType', KeyType: 'RANGE' },
                { AttributeName: 'year', KeyType: 'RANGE' },
                { AttributeName: 'month', KeyType: 'RANGE' }
            ],
            Projection: { ProjectionType: 'ALL' }
        }
    ],
    BillingMode: 'PAY_PER_REQUEST'
}

// Query AccountTimeIndex GSI: All transactions for account in 2023
const yearTransactions = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'AccountTimeIndex',
    KeyConditionExpression: 'accountId = :account AND #year = :year',
    ExpressionAttributeNames: { '#year': 'year' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':year': '2023'
    }
}));

// Query AccountTimeIndex GSI: Transactions in specific month
const monthTransactions = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'AccountTimeIndex',
    KeyConditionExpression: 'accountId = :account AND #year = :year AND #month = :month',
    ExpressionAttributeNames: { '#year': 'year', '#month': 'month' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':year': '2023',
        ':month': '11'
    }
}));

// Query TransactionTypeIndex GSI: Deposits in 2023
const deposits = await docClient.send(new QueryCommand({
    TableName: 'BankTransactions',
    IndexName: 'TransactionTypeIndex',
    KeyConditionExpression: 'accountId = :account AND transactionType = :type AND #year = :year',
    ExpressionAttributeNames: { '#year': 'year' },
    ExpressionAttributeValues: {
        ':account': 'ACC-12345',
        ':type': 'deposit',
        ':year': '2023'
    }
}));
```

## 完整示例
<a name="GSI.DesignPattern.MultiAttributeKeys.CompleteExample"></a>

以下示例演示了多属性键从设置到清理的过程：

### 代码示例
<a name="w2aac19c13c45c23b9c15b5b1"></a>

```
import { 
    DynamoDBClient, 
    CreateTableCommand, 
    DeleteTableCommand, 
    waitUntilTableExists 
} from "@aws-sdk/client-dynamodb";
import { 
    DynamoDBDocumentClient, 
    PutCommand, 
    QueryCommand 
} from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: 'us-west-2' });
const docClient = DynamoDBDocumentClient.from(client);

async function multiAttributeKeysDemo() {
    console.log("Starting Multi-Attribute GSI Keys Demo\n");
    
    // Step 1: Create table with GSIs using multi-attribute keys
    console.log("1. Creating table with multi-attribute GSI keys...");
    await client.send(new CreateTableCommand({
        TableName: 'TournamentMatches',
        KeySchema: [
            { AttributeName: 'matchId', KeyType: 'HASH' }
        ],
        AttributeDefinitions: [
            { AttributeName: 'matchId', AttributeType: 'S' },
            { AttributeName: 'tournamentId', AttributeType: 'S' },
            { AttributeName: 'region', AttributeType: 'S' },
            { AttributeName: 'round', AttributeType: 'S' },
            { AttributeName: 'bracket', AttributeType: 'S' },
            { AttributeName: 'player1Id', AttributeType: 'S' },
            { AttributeName: 'matchDate', AttributeType: 'S' }
        ],
        GlobalSecondaryIndexes: [
            {
                IndexName: 'TournamentRegionIndex',
                KeySchema: [
                    { AttributeName: 'tournamentId', KeyType: 'HASH' },
                    { AttributeName: 'region', KeyType: 'HASH' },
                    { AttributeName: 'round', KeyType: 'RANGE' },
                    { AttributeName: 'bracket', KeyType: 'RANGE' },
                    { AttributeName: 'matchId', KeyType: 'RANGE' }
                ],
                Projection: { ProjectionType: 'ALL' }
            },
            {
                IndexName: 'PlayerMatchHistoryIndex',
                KeySchema: [
                    { AttributeName: 'player1Id', KeyType: 'HASH' },
                    { AttributeName: 'matchDate', KeyType: 'RANGE' },
                    { AttributeName: 'round', KeyType: 'RANGE' }
                ],
                Projection: { ProjectionType: 'ALL' }
            }
        ],
        BillingMode: 'PAY_PER_REQUEST'
    }));
    
    await waitUntilTableExists({ client, maxWaitTime: 120 }, { TableName: 'TournamentMatches' });
    console.log("Table created\n");
    
    // Step 2: Insert tournament matches
    console.log("2. Inserting tournament matches...");
    const matches = [
        { matchId: 'match-001', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '101', player2Id: '103', matchDate: '2024-01-20', winner: '101', score: '3-1' },
        { matchId: 'match-002', tournamentId: 'WINTER2024', region: 'NA-EAST', round: 'SEMIFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '105', matchDate: '2024-01-18', winner: '101', score: '3-2' },
        { matchId: 'match-003', tournamentId: 'WINTER2024', region: 'NA-WEST', round: 'FINALS', bracket: 'CHAMPIONSHIP', player1Id: '102', player2Id: '104', matchDate: '2024-01-20', winner: '102', score: '3-2' },
        { matchId: 'match-004', tournamentId: 'SPRING2024', region: 'NA-EAST', round: 'QUARTERFINALS', bracket: 'UPPER', player1Id: '101', player2Id: '108', matchDate: '2024-03-15', winner: '101', score: '3-0' }
    ];
    
    for (const match of matches) {
        await docClient.send(new PutCommand({ TableName: 'TournamentMatches', Item: match }));
    }
    console.log(`Inserted ${matches.length} tournament matches\n`);
    
    // Step 3: Query GSI with multi-attribute partition key
    console.log("3. Query TournamentRegionIndex GSI: WINTER2024/NA-EAST matches");
    const gsiQuery1 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'TournamentRegionIndex',
        KeyConditionExpression: 'tournamentId = :tournament AND #region = :region',
        ExpressionAttributeNames: { '#region': 'region' },
        ExpressionAttributeValues: { ':tournament': 'WINTER2024', ':region': 'NA-EAST' }
    }));
    
    console.log(`  Found ${gsiQuery1.Items.length} matches:`);
    gsiQuery1.Items.forEach(match => {
        console.log(`    ${match.round} - ${match.bracket} - ${match.winner} won`);
    });
    
    // Step 4: Query GSI with multi-attribute sort key
    console.log("\n4. Query PlayerMatchHistoryIndex GSI: All matches for Player 101");
    const gsiQuery2 = await docClient.send(new QueryCommand({
        TableName: 'TournamentMatches',
        IndexName: 'PlayerMatchHistoryIndex',
        KeyConditionExpression: 'player1Id = :player',
        ExpressionAttributeValues: { ':player': '101' }
    }));
    
    console.log(`  Found ${gsiQuery2.Items.length} matches for Player 101:`);
    gsiQuery2.Items.forEach(match => {
        console.log(`    ${match.tournamentId}/${match.region} - ${match.matchDate} - ${match.round}`);
    });
    
    console.log("\nDemo complete");
    console.log("No synthetic keys needed - GSIs use native attributes automatically");
}

async function cleanup() {
    console.log("Deleting table...");
    await client.send(new DeleteTableCommand({ TableName: 'TournamentMatches' }));
    console.log("Table deleted");
}

// Run demo
multiAttributeKeysDemo().catch(console.error);

// Uncomment to cleanup:
// cleanup().catch(console.error);
```

**最小代码脚手架**

### 代码示例
<a name="w2aac19c13c45c23b9c15b9b1"></a>

```
// 1. Create table with GSI using multi-attribute keys
await client.send(new CreateTableCommand({
    TableName: 'MyTable',
    KeySchema: [
        { AttributeName: 'id', KeyType: 'HASH' }        // Simple base table PK
    ],
    AttributeDefinitions: [
        { AttributeName: 'id', AttributeType: 'S' },
        { AttributeName: 'attr1', AttributeType: 'S' },
        { AttributeName: 'attr2', AttributeType: 'S' },
        { AttributeName: 'attr3', AttributeType: 'S' },
        { AttributeName: 'attr4', AttributeType: 'S' }
    ],
    GlobalSecondaryIndexes: [{
        IndexName: 'MyGSI',
        KeySchema: [
            { AttributeName: 'attr1', KeyType: 'HASH' },    // GSI PK attribute 1
            { AttributeName: 'attr2', KeyType: 'HASH' },    // GSI PK attribute 2
            { AttributeName: 'attr3', KeyType: 'RANGE' },   // GSI SK attribute 1
            { AttributeName: 'attr4', KeyType: 'RANGE' }    // GSI SK attribute 2
        ],
        Projection: { ProjectionType: 'ALL' }
    }],
    BillingMode: 'PAY_PER_REQUEST'
}));

// 2. Insert items with native attributes (no concatenation needed for GSI)
await docClient.send(new PutCommand({
    TableName: 'MyTable',
    Item: {
        id: 'item-001',
        attr1: 'value1',
        attr2: 'value2',
        attr3: 'value3',
        attr4: 'value4',
        // ... other attributes
    }
}));

// 3. Query GSI with all partition key attributes
await docClient.send(new QueryCommand({
    TableName: 'MyTable',
    IndexName: 'MyGSI',
    KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2',
    ExpressionAttributeValues: {
        ':v1': 'value1',
        ':v2': 'value2'
    }
}));

// 4. Query GSI with sort key attributes (left-to-right)
await docClient.send(new QueryCommand({
    TableName: 'MyTable',
    IndexName: 'MyGSI',
    KeyConditionExpression: 'attr1 = :v1 AND attr2 = :v2 AND attr3 = :v3',
    ExpressionAttributeValues: {
        ':v1': 'value1',
        ':v2': 'value2',
        ':v3': 'value3'
    }
}));

// Note: If any attribute name is a DynamoDB reserved keyword, use ExpressionAttributeNames:
// KeyConditionExpression: 'attr1 = :v1 AND #attr2 = :v2'
// ExpressionAttributeNames: { '#attr2': 'attr2' }
```

## 其他资源
<a name="GSI.DesignPattern.MultiAttributeKeys.AdditionalResources"></a>
+ [DynamoDB 最佳实践](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/best-practices.html)
+ [处理表和数据](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)
+ [全局二级索引](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)：
+ [查询和扫描操作](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html)

# 在 DynamoDB 中管理全局二级索引
<a name="GSI.OnlineOps"></a>

本节介绍如何在 Amazon DynamoDB 中创建、修改和删除全局二级索引。

**Topics**
+ [创建具有全局二级索引的表](#GSI.Creating)
+ [描述表的全局二级索引](#GSI.Describing)
+ [向现有表添加全局二级索引](#GSI.OnlineOps.Creating)
+ [删除全局二级索引](#GSI.OnlineOps.Deleting)
+ [在创建期间修改全局二级索引](#GSI.OnlineOps.Creating.Modify)

## 创建具有全局二级索引的表
<a name="GSI.Creating"></a>

要创建具有一个或多个全局二级索引的表，请使用 `CreateTable` 和 `GlobalSecondaryIndexes` 参数。为获得最高的查询灵活性，您可以为每个表创建最多 20 个全局二级索引（默认配额）。

您必须指定一个属性以充当索引分区键。您可以选择为索引排序键指定另一个属性。这些关键属性中的任何一个都不必与表中的关键属性相同。例如，在 *GameScores* 表（请参阅 [在 DynamoDB 中使用全局二级索引](GSI.md)），`TopScore` 和 `TopScoreDateTime` 都不是关键属性。您可以创建具有分区键 `TopScore` 以及排序键 `TopScoreDateTime` 的全局二级索引。您可以使用这样的索引来确定高分与玩游戏时间之间是否存在相关性。

每个索引键属性必须是标量类型 `String`、`Number` 或 `Binary`。（它不能是文档或集合。） 您可以将任何数据类型的属性投影到全局二级索引中。其中包括标量、文档和集。有关数据类型的完整列表，请参阅 [数据类型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)。

如果使用预置模式，必须提供索引的 `ProvisionedThroughput` 设置，包括 `ReadCapacityUnits` 和 `WriteCapacityUnits`。这些预置吞吐量设置与表的设置分开，但行为方式相似。有关更多信息，请参阅 [全局二级索引的预调配吞吐量注意事项](GSI.md#GSI.ThroughputConsiderations)。

 全局二级索引继承基表的读/写入容量模式。有关更多信息，请参阅 [在 DynamoDB 中切换容量模式时的注意事项](bp-switching-capacity-modes.md)。

**注意**  
 创建新的 GSI 时，请务必检查您选择的分区键在新索引的分区键值之间生成的数据或流量是否分布不均或缩小。如果发生这种情况，您可能会看到同时发生回填和写入操作并且会限制对基表的写入操作。该服务采取措施最大限度地减少这种情况的可能性，但不会深入了解与索引分区键、所选的预测或索引主键的稀疏程度相关的客户数据的形状。  
如果您怀疑新的全局二级索引可能出现分区键值中的数据或流量过窄或偏斜，请先考虑以下方面，然后再向操作重要的表添加新索引。  
最安全的方法是在应用程序驱动最少流量时添加索引。
请考虑在基表和索引上启用 CloudWatch Contributor Insights。这将为您提供有用的流量分布洞察。
 在整个过程中观察 `WriteThrottleEvents`、`ThrottledRequests` 和 `OnlineIndexPercentageProgress` CloudWatch 指标。根据需要调整预置的写入容量，以便在合理的时间内完成回填，而不会对正在进行的操作产生任何重大的节流影响。在索引回填过程中，`OnlineIndexConsumedWriteCapacity` 和 `OnlineThrottleEvents` 预计显示 0。
如果由于写节流而对操作产生影响，请准备取消索引创建。

## 描述表的全局二级索引
<a name="GSI.Describing"></a>

要查看表上所有全局二级索引的状态，请使用 `DescribeTable` 操作。响应的 `GlobalSecondaryIndexes` 部分显示表上的所有索引，以及各自当前状态 (`IndexStatus`)。

全局二级索引的 `IndexStatus` 为：
+ `CREATING`— 索引当前正在创建，但是还不能使用。
+ `ACTIVE`— 索引已准备就绪，应用程序可以对索引执行 `Query` 操作。
+ `UPDATING`— 索引的预置吞吐量设置正在更改。
+ `DELETING`— 索引当前正在删除，不能再使用。

当 DynamoDB 完成构建全局二级索引时，索引状态会从 `CREATING` 变为 `ACTIVE`。

## 向现有表添加全局二级索引
<a name="GSI.OnlineOps.Creating"></a>

要将全局二级索引添加到现有表，请使用 `UpdateTable` 操作和 `GlobalSecondaryIndexUpdates` 参数。您必须提供以下参数：
+ 索引名称。该名称在表的所有索引中必须唯一。
+ 索引的键架构。您必须为索引分区键指定一个属性；您可以选择为索引排序键指定另一个属性。这些关键属性中的任何一个都不必与表中的关键属性相同。每个架构属性的数据类型必须是标量：`String`、`Number` 或 `Binary`。
+ 从表投影到索引中的属性：
  + `KEYS_ONLY`— 索引中的每个项目仅包含表的分区键、排序键值以及索引键值。
  + `INCLUDE` – 除 `KEYS_ONLY` 中描述的属性外，二级索引还包括您指定的其他非键属性。
  + `ALL`— 索引包括源表中的所有属性。
+ 索引的预置吞吐量，由 `ReadCapacityUnits` 和 `WriteCapacityUnits` 组成。与表的预吞吐量设置是分开的。

您只能为每个 `UpdateTable` 操作创建一个全局二级索引。

### 索引创建的阶段
<a name="GSI.OnlineOps.Creating.Phases"></a>

将新的全局二级索引添加到现有表时，该表在构建索引期间继续可用。但是，新索引不可用于查询操作，直到其状态从 `CREATING` 变为 `ACTIVE`。

**注意**  
创建全局二级索引不会使用 Application Auto Scaling。增加 `MIN` Application Auto Scaling 容量不会缩短全局二级索引的创建时间。

DynamoDB 后台分两个阶段构建索引：

**资源分配**  
DynamoDB 分配构建索引所需的计算和存储资源。  
在资源分配阶段，`IndexStatus` 属性为 `CREATING`，`Backfilling` 属性为 false。使用 `DescribeTable` 操作检索表及其所有二级索引的状态。  
当索引处于资源分配阶段时，无法删除索引或删除其父表。您也无法修改索引或表的预置吞吐量。您无法在表上添加或删除其他索引。但是，您可以修改这些其他索引的预置吞吐量。

**回填**  
对于表中的每个项目，DynamoDB 根据其投影确定要写入索引的属性集合（`KEYS_ONLY`、`INCLUDE` 或 `ALL`）。然后，它将这些属性写入索引。在回填阶段，DynamoDB 会跟踪表中正在添加、删除或更新的项目。也会根据需要在索引中添加、删除或更新这些项目的属性。  
在回填阶段，`IndexStatus` 属性设置为 `CREATING`，`Backfilling` 属性为 true。使用 `DescribeTable` 操作检索表及其所有二级索引的状态。  
当索引处于回填状态时，您不能删除其父表。但是，您仍然可以删除索引或修改表及其任何全局二级索引的预置吞吐量。  
在回填阶段，一些违规项目的写入可能会成功，而另一些则会被拒绝。回填后，对违反新索引的键架构的项目的所有写入操作都将被拒绝。我们建议您在回填阶段完成后运行 Violation Detector 工具，以检测和解决可能发生的任何关键违规。有关更多信息，请参阅 [在 DynamoDB 中检测和纠正索引键违规](GSI.OnlineOps.ViolationDetection.md)。

当资源分配和回填阶段正在进行中时，索引位于 `CREATING` 状态。在此期间，DynamoDB 会对表执行读取操作。对于从基表中填充全局二级索引的读取操作，您不需要付费。

当索引构建完成后，其状态将更改为 `ACTIVE`。您不能 `Query` 或 `Scan` 索引，直到变为 `ACTIVE`。

**注意**  
某些情况下，DynamoDB 会因索引键冲突而无法将表中的数据写入索引。在以下情况下，可能会发生这种情况：  
属性值的数据类型与索引键架构数据类型不匹配。
属性的大小超出索引键属性的最大长度。
索引键属性具有空字符串或空二进制属性值。
索引键冲突不会干扰全局二级索引的创建。但是，当索引变为 `ACTIVE` 后，则索引中不存在违规键。  
DynamoDB 提供了一个独立的工具来查找和解决这些问题。有关更多信息，请参阅 [在 DynamoDB 中检测和纠正索引键违规](GSI.OnlineOps.ViolationDetection.md)。

### 向大型表添加全局二级索引
<a name="GSI.OnlineOps.Creating.LargeTable"></a>

构建全局二级索引所需的时间取决于几个因素，例如：
+ 表的大小
+ 表中有资格包含在索引中的项目数
+ 投影到索引中的属性数量
+ 在索引构建期间在主表上写入活动

如果要将全局二级索引添加到非常大的表中，则创建过程可能需要很长时间才能完成。要监控进度并确定索引是否具有足够的写入容量，请参阅以下 Amazon CloudWatch 指标：
+ `OnlineIndexPercentageProgress`

有关 DynamoDB 相关 CloudWatch 指标的更多信息，请参阅 [DynamoDB 指标](metrics-dimensions.md#dynamodb-metrics)。

**重要**  
在创建或更新全局二级索引之前，您可能需要将大量表加入允许列表。请联系 AWS Support 以将您的表加入允许列表。

回填索引时，DynamoDB 会使用内部系统容量从表中读取。这是为了最大限度地减少创建索引的影响，并确保您的表不会耗尽读取容量。

## 删除全局二级索引
<a name="GSI.OnlineOps.Deleting"></a>

如果您不再需要全局二级索引，可以使用 `UpdateTable` 操作删除。

您只能为每个 `UpdateTable` 操作删除一个全局二级索引。

删除全局二级索引时，对父表中的任何读取或写入活动都没有影响。删除过程中，您仍然可以修改其他索引上的预置吞吐量。

**注意**  
使用 `DeleteTable` 操作删除表时，会同时删除该表的全局二级索引。
将不会针对全局二级索引的删除操作向您的账户收取费用。

## 在创建期间修改全局二级索引
<a name="GSI.OnlineOps.Creating.Modify"></a>

在构建索引时，您可以使用 `DescribeTable` 操作确定它处于哪个阶段。索引的描述包括一个布尔属性 `Backfilling`，指示 DynamoDB 当前是否正在使用表中的项目加载索引。如果 `Backfilling` 为 true，则资源分配阶段已完成，索引现在正在回填。

在回填阶段，您可以删除正在创建的索引。在此阶段中，您无法在表上添加或删除其他索引。

**注意**  
对于作为 `CreateTable` 操作的一部分创建的索引，`Backfilling` 属性未显示在 `DescribeTable` 输出。有关更多信息，请参阅 [索引创建的阶段](#GSI.OnlineOps.Creating.Phases)。

# 在 DynamoDB 中检测和纠正索引键违规
<a name="GSI.OnlineOps.ViolationDetection"></a>

在全局二级索引创建的回填阶段，Amazon DynamoDB 会检查表中的每个项目，以确定它是否符合包含在索引中的条件。某些项目可能不符合条件，因为它们会导致索引键冲突。在这些情况下，项目仍保留在表中，但索引没有该项的相应条目。

以下情况下发生*索引键冲突*：
+ 属性值与索引键架构数据类型不匹配。例如，假设 `GameScores` 表的一个项目的类型 `String` 为 `TopScore` 值。如果您添加的全局二级索引的分区键 `TopScore` 类型 `Number`，则表中的项目将违反索引键。
+ 表的属性值超出索引键属性的最大长度。分区键的最大长度为 2048 字节，排序键的最大长度为 1024 字节。如果表中的任何相应属性值超过这些限制，则表中的项目将违反索引键。

**注意**  
如果为用作索引键的属性设置了字符串或二进制属性值，则属性值的长度必须大于零；否则，表中的项将与索引键冲突。  
此工具目前不会标记此索引键冲突。

如果发生索引键冲突，回填阶段将继续不会中断。但是，索引中不包含任何违规项目。回填阶段完成后，所有违反新索引键架构的项目写入操作都将被拒绝。

要标识和修复表中违反索引键的属性值，请使用 Violation Detector 工具。要运行 Violation Detector，您需要创建一个配置文件，该文件指定要扫描的表的名称、全局二级索引分区键和排序键的名称和数据类型，以及在发现任何索引键冲突时要执行的操作。Violation Detector 可以在以下两种不同模式之一运行：
+ **检测模式**— 检测索引键违规。使用检测模式报告表中会导致全局二级索引中键违规的项目。（您可以选择要求在找到这些违规表项时立即删除它们。） 检测模式的输出将写入文件，您可以使用该文件进行进一步分析。
+ **更正模式**— 更正索引键冲突。在更正模式下，Violation Detector 从检测模式中读取与输出文件格式相同的输入文件。更正模式从输入文件中读取记录，并且对于每条记录，它会删除或更新表中的相应项目。（请注意，如果选择更新项目，则必须编辑输入文件并为这些更新设置适当的值。）

## 下载并运行 Violation Detector
<a name="GSI.OnlineOps.ViolationDetection.Running"></a>

Violation Detector 可作为可执行 Java 归档文件（`.jar` 文件）提供，并在 Windows、macOS 或 Linux 计算机上运行。Violation Detector 需要 Java 1.7（或更高版本）和 Apache Maven。
+ [从 GitHub 下载 Violation Detector](https://github.com/awslabs/dynamodb-online-index-violation-detector)

按照 `README.md` 文件说明下载并使用 Maven 安装 Violation Detector。

要启动 Violation Detector，请转至生成 `ViolationDetector.java` 的目录并输入以下命令。

```
java -jar ViolationDetector.jar [options]
```

Violation Detector 命令行接受以下选项：
+ `-h | --help`— 输出 Violation Detector 的使用摘要和选项。
+ `-p | --configFilePath` `value`— Violation Detector 配置文件的完全限定名称。有关更多信息，请参阅 [Violation Detector 配置文件](#GSI.OnlineOps.ViolationDetection.ConfigFile)。
+ `-t | --detect` `value`— 检测表中的索引键违规，并将其写入 Violation Detector 输出文件。如果此参数的值设置为 `keep`，则不会修改带有键违规的项目。如果值设置为 `delete`，则会从表中删除带有键违规的项目。
+ `-c | --correct` `value`— 从输入文件中读取索引键冲突，并对表中的项目采取纠正措施。如果此参数的值设置为 `update`，则具有键违规的项目将使用新的不违规值进行更新。如果值设置为 `delete`，则会从表中删除带有键违规的项目。

## Violation Detector 配置文件
<a name="GSI.OnlineOps.ViolationDetection.ConfigFile"></a>

在运行时，Violation Detector 工具需要配置文件。此文件中的参数确定违规检测器可以访问哪些 DynamoDB 资源，以及它可以占用的预置吞吐量。下表介绍这些参数。


****  

| 参数名称 | 说明 | 必填？ | 
| --- | --- | --- | 
|  `awsCredentialsFile`  |  包含 AWS 凭证的文件完全限定名称。凭证文件必须为以下格式： <pre>accessKey = access_key_id_goes_here<br />secretKey = secret_key_goes_here </pre>  |  是  | 
|  `dynamoDBRegion`  |  表所在的 AWS 区域。例如：`us-west-2`。  |  是  | 
|  `tableName`  | 要扫描的 DynamoDB 表的名称。 |  是  | 
|  `gsiHashKeyName`  |  索引分区键的名称。  |  是  | 
|  `gsiHashKeyType`  |  索引分区键的数据类型 —`String`、`Number` 或 `Binary`： `S \| N \| B`  |  是  | 
|  `gsiRangeKeyName`  |  索引排序键的名称。如果索引只有简单主键（分区键），请不要指定此参数。  |  否  | 
|  `gsiRangeKeyType`  |  索引排序键的数据类型 —`String`、`Number` 或 `Binary`： `S \| N \| B`  如果索引只有简单主键（分区键），请不要指定此参数。  |  否  | 
|  `recordDetails`  |  是否将索引键违规的完整详细信息写入输出文件。如果设置为 `true`（默认值），则会报告有关违规项目的完整信息。如果设置为 `false`，则仅报告违规次数。  |  否  | 
|  `recordGsiValueInViolationRecord`  |  是否将违规索引键的值写入输出文件。如果设置为 `true`（默认值），则会报告键值。如果设置为 `false`，则不会报告键值。  |  否  | 
|  `detectionOutputPath`  |  Violation Detector 输出文件的完整路径。此参数支持写入本地目录或 Amazon Simple Storage Service (Amazon S3)。示例如下： `detectionOutputPath = ``//local/path/filename.csv` `detectionOutputPath = ``s3://bucket/filename.csv` 输出文件中的信息以逗号分隔值 (CSV) 格式显示。如果您没有设置 `detectionOutputPath`，则输出文件名为 `violation_detection.csv`，并写入到您当前的工作目录中。  |  否  | 
|  `numOfSegments`  | Violation Detector 扫描表时要使用的并行扫描段数。默认值为 1，表示按顺序扫描表。如果值为 2 或更高，则 Violation Detector 将表划分为多个逻辑段和相同数量的扫描线程。`numOfSegments` 最大设置为 4096。对于较大的表，并行扫描通常比顺序扫描快。此外，如果表大到跨越多个分区，则并行扫描会将其读取活动均匀分布到多个分区。有关 DynamoDB 中并行扫描的更多信息，请参阅 [并行扫描](Scan.md#Scan.ParallelScan)。 |  否  | 
|  `numOfViolations`  |  写入到输出文件的索引键违规上限。如果设置为 `-1`（默认值），则扫描整个表。如果设置为正整数，则 Violation Detector 会在遇到该数量的违规后停止。  |  否  | 
|  `numOfRecords`  |  要扫描的表中的项目数。如果设置为 -1（默认值），则扫描整个表。如果设置为正整数，Violation Detector 会在扫描表中的该数量项目后停止。  |  否  | 
|  `readWriteIOPSPercent`  |  调节表扫描期间使用的预置读取容量单位的百分比。有效值范围为 `1` 至 `100`。默认值 (`25`) 表示违规检测器消耗的表预置读取吞吐量不超过 25%。  |  否  | 
|  `correctionInputPath`  |  Violation Detector 更正输入文件的完整路径。如果在更正模式下运行 Violation Detector，则此文件的内容将用于修改或删除表中违反全局二级索引的数据项。 `correctionInputPath` 文件的格式与 `detectionOutputPath` 文件相同。这样，您就可以将检测模式的输出作为更正模式下的输入进行处理。  |  否  | 
|  `correctionOutputPath`  |  Violation Detector 更正输出文件的完整路径。仅当存在更新错误时创建此文件。 此参数支持写入本地目录或 Amazon S3。示例如下： `correctionOutputPath = ``//local/path/filename.csv` `correctionOutputPath = ``s3://bucket/filename.csv` 输出文件中的信息以 CSV 格式显示。如果您没有设置 `correctionOutputPath`，则输出文件名为 `violation_update_errors.csv`，并写入到您当前的工作目录中。  |  否  | 

## 检测
<a name="GSI.OnlineOps.ViolationDetection.Detection"></a>

若要检测索引键违规，请将 Violation Detector 与 `--detect` 命令行选项一起使用。要显示此选项的工作原理，请考虑 `ProductCatalog` 表。以下是表中项目的列表。仅显示主键 (`Id`) 和 `Price` 属性。


****  

| ID（主键） | Price | 
| --- | --- | 
| 101 |  5  | 
| 102 |  20  | 
| 103 | 200  | 
| 201 |  100  | 
| 202 |  200  | 
| 203 |  300  | 
| 204 |  400  | 
| 205 |  500  | 

`Price` 的所有值类型为 `Number`。但是，由于 DynamoDB 无架构，可以添加具有非数字 `Price` 项目。例如，假设您将另一项添加到 `ProductCatalog` 表。


****  

| ID（主键） | Price | 
| --- | --- | 
| 999 | "Hello" | 

该表现在共有 9 个项目。

现在，您将新的全局二级索引添加到表中：`PriceIndex`。此索引的主键为分区键 `Price`，类型为 `Number`。构建索引后，它将包含 8 个项目-但 `ProductCatalog` 表中有 9 个项目。这种差异的原因是，`"Hello"` 值是类型 `String`，但 `PriceIndex` 具有 `Number` 类型的主键。`String` 值违反了全局二级索引键，因此不出现在索引中。

若要在这种情况下使用 Violation Detector，请首先创建一个配置文件，如下所示。

```
# Properties file for violation detection tool configuration.
# Parameters that are not specified will use default values.

awsCredentialsFile = /home/alice/credentials.txt
dynamoDBRegion = us-west-2
tableName = ProductCatalog
gsiHashKeyName = Price
gsiHashKeyType = N
recordDetails = true
recordGsiValueInViolationRecord = true
detectionOutputPath = ./gsi_violation_check.csv
correctionInputPath = ./gsi_violation_check.csv
numOfSegments = 1
readWriteIOPSPercent = 40
```

接下来，您运行 Violation Detector，如以下示例所示。

```
$  java -jar ViolationDetector.jar --configFilePath config.txt --detect keep

Violation detection started: sequential scan, Table name: ProductCatalog, GSI name: PriceIndex
Progress: Items scanned in total: 9,    Items scanned by this thread: 9,    Violations found by this thread: 1, Violations deleted by this thread: 0
Violation detection finished: Records scanned: 9, Violations found: 1, Violations deleted: 0, see results at: ./gsi_violation_check.csv
```

如果 `recordDetails` 配置参数设置为 `true`，Violation Detector 将每个违规的详细信息写入输出文件，如以下示例所示。

```
Table Hash Key,GSI Hash Key Value,GSI Hash Key Violation Type,GSI Hash Key Violation Description,GSI Hash Key Update Value(FOR USER),Delete Blank Attributes When Updating?(Y/N) 

999,"{""S"":""Hello""}",Type Violation,Expected: N Found: S,,
```

输出文件采用 CSV 格式。文件中的第一行是标题，后面是违反索引键的每个项目的一条记录。这些违规记录的字段如下：
+ **Table hash key**（表哈希键）– 表中项目的分区键值。
+ **Table range key**（表范围键）– 表中项目的排序键值。
+ **GSI hash key value**（GSI 哈希键值）– 全局二级索引的分区键值。
+ **GSI hash key violation type**（GSI 哈希键违规类型）– `Type Violation` 或 `Size Violation`。
+ **GGSI hash key violation description**（GSI 哈希键违规描述）– 违规的原因。
+ **GSI hash key update Value(FOR USER)** [GSI 哈希键更新值（针对用户）] – 在更正模式下，用户为属性提供的新值。
+ **GSI range key value**（GSI 范围键值）– 全局二级索引的排序键值。
+ **GSI range key violation type**（GSI 范围键违规类型）– `Type Violation` 或 `Size Violation`。
+ **GSI range key violation description**（GSI 范围键违规描述）– 违规的原因。
+ **GSI range key update Value(FOR USER)** [GSI 范围键更新值（针对用户）] – 在更正模式下，用户为属性提供的新值。
+ **Delete blank attribute when Updating(Y/N)** [在更新时删除空白属性（是/否）] – 在更正模式下，确定是删除 (Y) 还是保留 (N) 表中违规项目，但仅当以下任一字段为空时：
  + `GSI Hash Key Update Value(FOR USER)`
  + `GSI Range Key Update Value(FOR USER)`

  如果这些字段中的任何一个不为空，则 `Delete Blank Attribute When Updating(Y/N)` 无效。

**注意**  
输出格式可能有所不同，具体取决于配置文件和命令行选项。例如，如果表具有简单主键（没有排序键），则输出中不会出现排序键字段。  
文件中的违规记录可能不按排序顺序排列。

## 纠正
<a name="GSI.OnlineOps.ViolationDetection.Correction"></a>

要更正索引键冲突，请将 Violation Detector 与 `--correct` 命令行选项一起使用。在更正模式下，Violation Detector 读取 `correctionInputPath` 参数指定的输入文件。此文件具有的格式与 `detectionOutputPath` 文件相同，以便您可以使用检测输出作为更正的输入。

Violation Detector 提供了两种不同的方法来纠正索引键违规：
+ **删除违反**— 删除具有违反属性值的表项。
+ **更新违反**— 更新表项目，将违规属性替换为不违规的值。

无论哪种情况，都可以使用检测模式的输出文件作为更正模式的输入。

继续 `ProductCatalog` 示例，假设您要从表中删除违规项目。为此，请使用以下命令行。

```
$  java -jar ViolationDetector.jar --configFilePath config.txt --correct delete
```

此时，系统将要求您确认是否要删除违规项目。

```
Are you sure to delete all violations on the table?y/n
y
Confirmed, will delete violations on the table...
Violation correction from file started: Reading records from file: ./gsi_violation_check.csv, will delete these records from table.
Violation correction from file finished: Violations delete: 1, Violations Update: 0
```

现在 `ProductCatalog` 和 `PriceIndex` 具有相同数量的项目。

# 处理全局二级索引：Java
<a name="GSIJavaDocumentAPI"></a>

您可以使用 适用于 Java 的 AWS SDK 文档 API 创建具有一个或多个全局二级索引的 Amazon DynamoDB 表，描述表中的索引，以及使用索引执行查询。

下面是表操作的常见步骤。

1. 创建 `DynamoDB` 类的实例。

1. 通过创建对应的请求对象，为操作提供必需参数和可选参数。

1. 调用您在前面步骤中创建的客户端提供的适当方法。

**Topics**
+ [创建一个具有全局二级索引的表。](#GSIJavaDocumentAPI.CreateTableWithIndex)
+ [描述一个具有全局二级索引的表](#GSIJavaDocumentAPI.DescribeTableWithIndex)
+ [查询全局二级索引](#GSIJavaDocumentAPI.QueryAnIndex)
+ [示例：使用 适用于 Java 的 AWS SDK 文档 API 创建全局二级索引](GSIJavaDocumentAPI.Example.md)

## 创建一个具有全局二级索引的表。
<a name="GSIJavaDocumentAPI.CreateTableWithIndex"></a>

创建表时可以同时创建全局二级索引。为此，请使用 `CreateTable` 并为一个或多个全局二级索引提供您的规范。以下 Java 代码示例创建一个保存天气数据信息的表。分区键为 `Location`，排序键为 `Date`。全局二级索引 `PrecipIndex` 允许快速访问各个位置的降水数据。

下面是使用 DynamoDB 文档 API 创建具有全局二级索引的表的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `CreateTableRequest` 类实例，以提供请求信息。

   您必须提供表名称、主键以及预配置吞吐量值。对于全局二级索引，您必须提供索引名称、其预置吞吐量设置、索引排序键的属性定义、索引的键架构以及属性投影。

1. 以参数形式提供请求对象，以调用 `createTable` 方法。

以下 Java 代码示例演示了上述步骤。创建具有全局二级索引 (`PrecipIndex`) 的表 (`WeatherData`)。索引分区键为 `Date`，排序键为 `Precipitation`。所有表属性都投影到索引中。用户可以查询此索引以获取特定日期的天气数据，也可以选择按降水量对数据进行排序。

由于 `Precipitation` 不是表的键属性，它不是必需的。然而,`WeatherData` 项目不包含 `Precipitation`，不会显示在 `PrecipIndex` 中。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

// Attribute definitions
ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>();

attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Location")
    .withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Date")
    .withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition()
    .withAttributeName("Precipitation")
    .withAttributeType("N"));

// Table key schema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement()
    .withAttributeName("Location")
    .withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement()
    .withAttributeName("Date")
    .withKeyType(KeyType.RANGE));  //Sort key

// PrecipIndex
GlobalSecondaryIndex precipIndex = new GlobalSecondaryIndex()
    .withIndexName("PrecipIndex")
    .withProvisionedThroughput(new ProvisionedThroughput()
        .withReadCapacityUnits((long) 10)
        .withWriteCapacityUnits((long) 1))
        .withProjection(new Projection().withProjectionType(ProjectionType.ALL));

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();

indexKeySchema.add(new KeySchemaElement()
    .withAttributeName("Date")
    .withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement()
    .withAttributeName("Precipitation")
    .withKeyType(KeyType.RANGE));  //Sort key

precipIndex.setKeySchema(indexKeySchema);

CreateTableRequest createTableRequest = new CreateTableRequest()
    .withTableName("WeatherData")
    .withProvisionedThroughput(new ProvisionedThroughput()
        .withReadCapacityUnits((long) 5)
        .withWriteCapacityUnits((long) 1))
    .withAttributeDefinitions(attributeDefinitions)
    .withKeySchema(tableKeySchema)
    .withGlobalSecondaryIndexes(precipIndex);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。

## 描述一个具有全局二级索引的表
<a name="GSIJavaDocumentAPI.DescribeTableWithIndex"></a>

要获取表上全局二级索引的信息，请使用 `DescribeTable`。对于每个索引，您都可以查看其名称、键架构和投影的属性。

以下是访问表的全局二级索引信息的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例表示要处理的表。

1. 调用 `describe` 对象上的 `Table` 方法。

以下 Java 代码示例演示了上述步骤。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("WeatherData");
TableDescription tableDesc = table.describe();
    

Iterator<GlobalSecondaryIndexDescription> gsiIter = tableDesc.getGlobalSecondaryIndexes().iterator();
while (gsiIter.hasNext()) {
    GlobalSecondaryIndexDescription gsiDesc = gsiIter.next();
    System.out.println("Info for index "
         + gsiDesc.getIndexName() + ":");

    Iterator<KeySchemaElement> kseIter = gsiDesc.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = gsiDesc.getProjection();
    System.out.println("\tThe projection type is: "
        + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: "
            + projection.getNonKeyAttributes());
    }
}
```

## 查询全局二级索引
<a name="GSIJavaDocumentAPI.QueryAnIndex"></a>

您可以对全局二级索引使用 `Query`，基本上与对表执行 `Query` 操作相同。您需要指定索引名称、索引分区键和排序键（如果有）的查询条件以及要返回的属性。在本示例中，索引是 `PrecipIndex`，其分区键为 `Date`，排序键 `Precipitation`。索引查询返回特定日期降水量大于零的所有天气数据。

以下是使用 适用于 Java 的 AWS SDK 文档 API 查询全局二级索引的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例来代表要处理的索引。

1. 为要查询的索引创建 `Index` 类实例。

1. 调用 `query` 对象上的 `Index` 方法。

属性名称 `Date` 是 DynamoDB 保留字。因此，您必须将表达式属性名称用作 `KeyConditionExpression` 的占位符。

以下 Java 代码示例演示了上述步骤。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

Table table = dynamoDB.getTable("WeatherData");
Index index = table.getIndex("PrecipIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("#d = :v_date and Precipitation = :v_precip")
    .withNameMap(new NameMap()
        .with("#d", "Date"))
    .withValueMap(new ValueMap()
        .withString(":v_date","2013-08-10")
        .withNumber(":v_precip",0));

ItemCollection<QueryOutcome> items = index.query(spec);
Iterator<Item> iter = items.iterator(); 
while (iter.hasNext()) {
    System.out.println(iter.next().toJSONPretty());
}
```

# 示例：使用 适用于 Java 的 AWS SDK 文档 API 创建全局二级索引
<a name="GSIJavaDocumentAPI.Example"></a>

以下 Java 代码示例显示如何处理全局二级索引。本示例创建了一个 `Issues` 表，可以用于软件开发的简单错误跟踪系统。分区键为 `IssueId`，排序键为 `Title`。此表上有 3 个全局二级索引：
+ `CreateDateIndex` — 分区键为 `CreateDate`，排序键为 `IssueId`。除了表键之外，属性 `Description` 和 `Status` 投影到索引中。
+ `TitleIndex` — 分区键为 `Title`，排序键为 `IssueId`。表键以外的其他属性都不投影到索引中。
+ `DueDateIndex` — 分区键为 `DueDate`；没有排序键。所有表属性都投影到索引中。

创建 `Issues` 表后，程序为该表加载表示软件问题报告的数据。然后使用全局二级索引查询这些数据。最后，程序会删除 `Issues` 表。

有关测试以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

**Example**  

```
package com.amazonaws.codesamples.document;

import java.util.ArrayList;
import java.util.Iterator;

import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Index;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.ItemCollection;
import com.amazonaws.services.dynamodbv2.document.QueryOutcome;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.document.utils.ValueMap;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;

public class DocumentAPIGlobalSecondaryIndexExample {

    static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
    static DynamoDB dynamoDB = new DynamoDB(client);

    public static String tableName = "Issues";

    public static void main(String[] args) throws Exception {

        createTable();
        loadData();

        queryIndex("CreateDateIndex");
        queryIndex("TitleIndex");
        queryIndex("DueDateIndex");

        deleteTable(tableName);

    }

    public static void createTable() {

        // Attribute definitions
        ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>();

        attributeDefinitions.add(new AttributeDefinition().withAttributeName("IssueId").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("Title").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("CreateDate").withAttributeType("S"));
        attributeDefinitions.add(new AttributeDefinition().withAttributeName("DueDate").withAttributeType("S"));

        // Key schema for table
        ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
        tableKeySchema.add(new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.HASH)); // Partition
                                                                                                           // key
        tableKeySchema.add(new KeySchemaElement().withAttributeName("Title").withKeyType(KeyType.RANGE)); // Sort
                                                                                                          // key

        // Initial provisioned throughput settings for the indexes
        ProvisionedThroughput ptIndex = new ProvisionedThroughput().withReadCapacityUnits(1L)
                .withWriteCapacityUnits(1L);

        // CreateDateIndex
        GlobalSecondaryIndex createDateIndex = new GlobalSecondaryIndex().withIndexName("CreateDateIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("CreateDate").withKeyType(KeyType.HASH), // Partition
                                                                                                                 // key
                        new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.RANGE)) // Sort
                                                                                                        // key
                .withProjection(
                        new Projection().withProjectionType("INCLUDE").withNonKeyAttributes("Description", "Status"));

        // TitleIndex
        GlobalSecondaryIndex titleIndex = new GlobalSecondaryIndex().withIndexName("TitleIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("Title").withKeyType(KeyType.HASH), // Partition
                                                                                                            // key
                        new KeySchemaElement().withAttributeName("IssueId").withKeyType(KeyType.RANGE)) // Sort
                                                                                                        // key
                .withProjection(new Projection().withProjectionType("KEYS_ONLY"));

        // DueDateIndex
        GlobalSecondaryIndex dueDateIndex = new GlobalSecondaryIndex().withIndexName("DueDateIndex")
                .withProvisionedThroughput(ptIndex)
                .withKeySchema(new KeySchemaElement().withAttributeName("DueDate").withKeyType(KeyType.HASH)) // Partition
                                                                                                              // key
                .withProjection(new Projection().withProjectionType("ALL"));

        CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
                .withProvisionedThroughput(
                        new ProvisionedThroughput().withReadCapacityUnits((long) 1).withWriteCapacityUnits((long) 1))
                .withAttributeDefinitions(attributeDefinitions).withKeySchema(tableKeySchema)
                .withGlobalSecondaryIndexes(createDateIndex, titleIndex, dueDateIndex);

        System.out.println("Creating table " + tableName + "...");
        dynamoDB.createTable(createTableRequest);

        // Wait for table to become active
        System.out.println("Waiting for " + tableName + " to become ACTIVE...");
        try {
            Table table = dynamoDB.getTable(tableName);
            table.waitForActive();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void queryIndex(String indexName) {

        Table table = dynamoDB.getTable(tableName);

        System.out.println("\n***********************************************************\n");
        System.out.print("Querying index " + indexName + "...");

        Index index = table.getIndex(indexName);

        ItemCollection<QueryOutcome> items = null;

        QuerySpec querySpec = new QuerySpec();

        if (indexName == "CreateDateIndex") {
            System.out.println("Issues filed on 2013-11-01");
            querySpec.withKeyConditionExpression("CreateDate = :v_date and begins_with(IssueId, :v_issue)")
                    .withValueMap(new ValueMap().withString(":v_date", "2013-11-01").withString(":v_issue", "A-"));
            items = index.query(querySpec);
        } else if (indexName == "TitleIndex") {
            System.out.println("Compilation errors");
            querySpec.withKeyConditionExpression("Title = :v_title and begins_with(IssueId, :v_issue)")
                    .withValueMap(
                            new ValueMap().withString(":v_title", "Compilation error").withString(":v_issue", "A-"));
            items = index.query(querySpec);
        } else if (indexName == "DueDateIndex") {
            System.out.println("Items that are due on 2013-11-30");
            querySpec.withKeyConditionExpression("DueDate = :v_date")
                    .withValueMap(new ValueMap().withString(":v_date", "2013-11-30"));
            items = index.query(querySpec);
        } else {
            System.out.println("\nNo valid index name provided");
            return;
        }

        Iterator<Item> iterator = items.iterator();

        System.out.println("Query: printing results...");

        while (iterator.hasNext()) {
            System.out.println(iterator.next().toJSONPretty());
        }

    }

    public static void deleteTable(String tableName) {

        System.out.println("Deleting table " + tableName + "...");

        Table table = dynamoDB.getTable(tableName);
        table.delete();

        // Wait for table to be deleted
        System.out.println("Waiting for " + tableName + " to be deleted...");
        try {
            table.waitForDelete();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void loadData() {

        System.out.println("Loading data into table " + tableName + "...");

        // IssueId, Title,
        // Description,
        // CreateDate, LastUpdateDate, DueDate,
        // Priority, Status

        putItem("A-101", "Compilation error", "Can't compile Project X - bad version number. What does this mean?",
                "2013-11-01", "2013-11-02", "2013-11-10", 1, "Assigned");

        putItem("A-102", "Can't read data file", "The main data file is missing, or the permissions are incorrect",
                "2013-11-01", "2013-11-04", "2013-11-30", 2, "In progress");

        putItem("A-103", "Test failure", "Functional test of Project X produces errors", "2013-11-01", "2013-11-02",
                "2013-11-10", 1, "In progress");

        putItem("A-104", "Compilation error", "Variable 'messageCount' was not initialized.", "2013-11-15",
                "2013-11-16", "2013-11-30", 3, "Assigned");

        putItem("A-105", "Network issue", "Can't ping IP address 127.0.0.1. Please fix this.", "2013-11-15",
                "2013-11-16", "2013-11-19", 5, "Assigned");

    }

    public static void putItem(

            String issueId, String title, String description, String createDate, String lastUpdateDate, String dueDate,
            Integer priority, String status) {

        Table table = dynamoDB.getTable(tableName);

        Item item = new Item().withPrimaryKey("IssueId", issueId).withString("Title", title)
                .withString("Description", description).withString("CreateDate", createDate)
                .withString("LastUpdateDate", lastUpdateDate).withString("DueDate", dueDate)
                .withNumber("Priority", priority).withString("Status", status);

        table.putItem(item);
    }

}
```

# 处理全局二级索引：.NET
<a name="GSILowLevelDotNet"></a>

您可以使用 适用于 .NET 的 AWS SDK 低级 API 创建具有一个或多个全局二级索引的 Amazon DynamoDB 表、描述表中的索引，以及使用索引执行查询。这些操作映射到对应的 DynamoDB 操作。有关更多信息，请参阅 [Amazon DynamoDB API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)。

以下是使用 .NET 低级 API 执行表操作的常见步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 通过创建对应的请求对象，为操作提供必需参数和可选参数。

   例如，创建一个 `CreateTableRequest` 对象以创建表；创建一个 `QueryRequest` 数据元以查询表或索引。

1. 执行您在前面步骤中创建的客户端提供的适当方法。

**Topics**
+ [创建一个具有全局二级索引的表。](#GSILowLevelDotNet.CreateTableWithIndex)
+ [描述一个具有全局二级索引的表](#GSILowLevelDotNet.DescribeTableWithIndex)
+ [查询全局二级索引](#GSILowLevelDotNet.QueryAnIndex)
+ [示例：使用 适用于 .NET 的 AWS SDK 低级 API 的全局二级索引](GSILowLevelDotNet.Example.md)

## 创建一个具有全局二级索引的表。
<a name="GSILowLevelDotNet.CreateTableWithIndex"></a>

创建表时可以同时创建全局二级索引。为此，请使用 `CreateTable` 并为一个或多个全局二级索引提供您的规范。以下 C\$1 代码示例创建一个保存天气数据信息的表。分区键为 `Location`，排序键为 `Date`。全局二级索引 `PrecipIndex` 允许快速访问各个位置的降水数据。

以下是使用 .NET 低级别 API 创建具有全局二级索引的表的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `CreateTableRequest` 类实例，以提供请求信息。

   您必须提供表名称、主键以及预配置吞吐量值。对于全局二级索引，您必须提供索引名称、其预置的吞吐量设置、索引排序键的属性定义、索引的键架构以及属性投影。

1. 以参数形式提供请求对象，运行 `CreateTable` 方法。

以下 C\$1 代码示例演示了上述步骤。创建具有全局二级索引 (`PrecipIndex`) 的表 (`WeatherData`)。索引分区键为 `Date`，排序键为 `Precipitation`。所有表属性都投影到索引中。用户可以查询此索引以获取特定日期的天气数据，也可以选择按降水量对数据进行排序。

由于 `Precipitation` 不是表的键属性，它不是必需的。然而,`WeatherData` 项目不包含 `Precipitation`，不会显示在 `PrecipIndex` 中。

```
client = new AmazonDynamoDBClient();
string tableName = "WeatherData";

// Attribute definitions
var attributeDefinitions = new List<AttributeDefinition>()
{
    {new AttributeDefinition{
        AttributeName = "Location",
        AttributeType = "S"}},
    {new AttributeDefinition{
        AttributeName = "Date",
        AttributeType = "S"}},
    {new AttributeDefinition(){
        AttributeName = "Precipitation",
        AttributeType = "N"}
    }
};

// Table key schema
var tableKeySchema = new List<KeySchemaElement>()
{
    {new KeySchemaElement {
        AttributeName = "Location",
        KeyType = "HASH"}},  //Partition key
    {new KeySchemaElement {
        AttributeName = "Date",
        KeyType = "RANGE"}  //Sort key
    }
};

// PrecipIndex
var precipIndex = new GlobalSecondaryIndex
{
    IndexName = "PrecipIndex",
    ProvisionedThroughput = new ProvisionedThroughput
    {
        ReadCapacityUnits = (long)10,
        WriteCapacityUnits = (long)1
    },
    Projection = new Projection { ProjectionType = "ALL" }
};

var indexKeySchema = new List<KeySchemaElement> {
    {new KeySchemaElement { AttributeName = "Date", KeyType = "HASH"}},  //Partition key
    {new KeySchemaElement{AttributeName = "Precipitation",KeyType = "RANGE"}}  //Sort key
};

precipIndex.KeySchema = indexKeySchema;

CreateTableRequest createTableRequest = new CreateTableRequest
{
    TableName = tableName,
    ProvisionedThroughput = new ProvisionedThroughput
    {
        ReadCapacityUnits = (long)5,
        WriteCapacityUnits = (long)1
    },
    AttributeDefinitions = attributeDefinitions,
    KeySchema = tableKeySchema,
    GlobalSecondaryIndexes = { precipIndex }
};

CreateTableResponse response = client.CreateTable(createTableRequest);
Console.WriteLine(response.CreateTableResult.TableDescription.TableName);
Console.WriteLine(response.CreateTableResult.TableDescription.TableStatus);
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。

## 描述一个具有全局二级索引的表
<a name="GSILowLevelDotNet.DescribeTableWithIndex"></a>

要获取表上全局二级索引的信息，请使用 `DescribeTable`。对于每个索引，您都可以查看其名称、键架构和投影的属性。

以下介绍使用 .NET 低级 API 访问表中全局二级索引信息的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 以参数形式提供请求对象，运行 `describeTable` 方法。

   创建 `DescribeTableRequest` 类实例，以提供请求信息。您必须提供表名称。

以下 C\$1 代码示例演示了上述步骤。

**Example**  

```
client = new AmazonDynamoDBClient();
string tableName = "WeatherData";

DescribeTableResponse response = client.DescribeTable(new DescribeTableRequest { TableName = tableName});

List<GlobalSecondaryIndexDescription> globalSecondaryIndexes =
response.DescribeTableResult.Table.GlobalSecondaryIndexes;

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

foreach (GlobalSecondaryIndexDescription gsiDescription in globalSecondaryIndexes) {
     Console.WriteLine("Info for index " + gsiDescription.IndexName + ":");

     foreach (KeySchemaElement kse in gsiDescription.KeySchema) {
          Console.WriteLine("\t" + kse.AttributeName + ": key type is " + kse.KeyType);
     }

      Projection projection = gsiDescription.Projection;
      Console.WriteLine("\tThe projection type is: " + projection.ProjectionType);

      if (projection.ProjectionType.ToString().Equals("INCLUDE")) {
           Console.WriteLine("\t\tThe non-key projected attributes are: "
                + projection.NonKeyAttributes);
      }
}
```

## 查询全局二级索引
<a name="GSILowLevelDotNet.QueryAnIndex"></a>

您可以对全局二级索引使用 `Query`，基本上与对表执行 `Query` 操作相同。您需要指定索引名称、索引分区键和排序键（如果有）的查询条件以及要返回的属性。在本示例中，索引是 `PrecipIndex`，其分区键为 `Date`，排序键 `Precipitation`。索引查询返回特定日期降水量大于零的所有天气数据。

以下是使用 .NET 低级别 API 查询全局二级索引的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `QueryRequest` 类实例，以提供请求信息。

1. 以参数形式提供请求对象，运行 `query` 方法。

属性名称 `Date` 是 DynamoDB 保留关键字。因此，您必须将表达式属性名称用作 `KeyConditionExpression` 的占位符。

以下 C\$1 代码示例演示了上述步骤。

**Example**  

```
client = new AmazonDynamoDBClient();

QueryRequest queryRequest = new QueryRequest
{
    TableName = "WeatherData",
    IndexName = "PrecipIndex",
    KeyConditionExpression = "#dt = :v_date and Precipitation > :v_precip",
    ExpressionAttributeNames = new Dictionary<String, String> {
        {"#dt", "Date"}
    },
    ExpressionAttributeValues = new Dictionary<string, AttributeValue> {
        {":v_date", new AttributeValue { S =  "2013-08-01" }},
        {":v_precip", new AttributeValue { N =  "0" }}
    },
    ScanIndexForward = true
};

var result = client.Query(queryRequest);

var items = result.Items;
foreach (var currentItem in items)
{
    foreach (string attr in currentItem.Keys)
    {
        Console.Write(attr + "---> ");
        if (attr == "Precipitation")
        {
            Console.WriteLine(currentItem[attr].N);
    }
    else
    {
        Console.WriteLine(currentItem[attr].S);
    }

         }
     Console.WriteLine();
}
```

# 示例：使用 适用于 .NET 的 AWS SDK 低级 API 的全局二级索引
<a name="GSILowLevelDotNet.Example"></a>

以下 C\$1 代码示例显示如何处理全局二级索引。本示例创建了一个 `Issues` 表，可以用于软件开发的简单错误跟踪系统。分区键为 `IssueId`，排序键为 `Title`。此表上有 3 个全局二级索引：
+ `CreateDateIndex` — 分区键为 `CreateDate`，排序键为 `IssueId`。除了表键之外，属性 `Description` 和 `Status` 投影到索引中。
+ `TitleIndex` — 分区键为 `Title`，排序键为 `IssueId`。表键以外的其他属性都不投影到索引中。
+ `DueDateIndex` — 分区键为 `DueDate`；没有排序键。所有表属性都投影到索引中。

创建 `Issues` 表后，程序为该表加载表示软件问题报告的数据。然后使用全局二级索引查询这些数据。最后，程序会删除 `Issues` 表。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelGlobalSecondaryIndexExample
    {
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        public static String tableName = "Issues";

        public static void Main(string[] args)
        {
            CreateTable();
            LoadData();

            QueryIndex("CreateDateIndex");
            QueryIndex("TitleIndex");
            QueryIndex("DueDateIndex");

            DeleteTable(tableName);

            Console.WriteLine("To continue, press enter");
            Console.Read();
        }

        private static void CreateTable()
        {
            // Attribute definitions
            var attributeDefinitions = new List<AttributeDefinition>()
        {
            {new AttributeDefinition {
                 AttributeName = "IssueId", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "Title", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "CreateDate", AttributeType = "S"
             }},
            {new AttributeDefinition {
                 AttributeName = "DueDate", AttributeType = "S"
             }}
        };

            // Key schema for table
            var tableKeySchema = new List<KeySchemaElement>() {
            {
                new KeySchemaElement {
                    AttributeName= "IssueId",
                    KeyType = "HASH" //Partition key
                }
            },
            {
                new KeySchemaElement {
                    AttributeName = "Title",
                    KeyType = "RANGE" //Sort key
                }
            }
        };

            // Initial provisioned throughput settings for the indexes
            var ptIndex = new ProvisionedThroughput
            {
                ReadCapacityUnits = 1L,
                WriteCapacityUnits = 1L
            };

            // CreateDateIndex
            var createDateIndex = new GlobalSecondaryIndex()
            {
                IndexName = "CreateDateIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "CreateDate", KeyType = "HASH" //Partition key
                },
                new KeySchemaElement {
                    AttributeName = "IssueId", KeyType = "RANGE" //Sort key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "INCLUDE",
                    NonKeyAttributes = {
                    "Description", "Status"
                }
                }
            };

            // TitleIndex
            var titleIndex = new GlobalSecondaryIndex()
            {
                IndexName = "TitleIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "Title", KeyType = "HASH" //Partition key
                },
                new KeySchemaElement {
                    AttributeName = "IssueId", KeyType = "RANGE" //Sort key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "KEYS_ONLY"
                }
            };

            // DueDateIndex
            var dueDateIndex = new GlobalSecondaryIndex()
            {
                IndexName = "DueDateIndex",
                ProvisionedThroughput = ptIndex,
                KeySchema = {
                new KeySchemaElement {
                    AttributeName = "DueDate",
                    KeyType = "HASH" //Partition key
                }
            },
                Projection = new Projection
                {
                    ProjectionType = "ALL"
                }
            };



            var createTableRequest = new CreateTableRequest
            {
                TableName = tableName,
                ProvisionedThroughput = new ProvisionedThroughput
                {
                    ReadCapacityUnits = (long)1,
                    WriteCapacityUnits = (long)1
                },
                AttributeDefinitions = attributeDefinitions,
                KeySchema = tableKeySchema,
                GlobalSecondaryIndexes = {
                createDateIndex, titleIndex, dueDateIndex
            }
            };

            Console.WriteLine("Creating table " + tableName + "...");
            client.CreateTable(createTableRequest);

            WaitUntilTableReady(tableName);
        }

        private static void LoadData()
        {
            Console.WriteLine("Loading data into table " + tableName + "...");

            // IssueId, Title,
            // Description,
            // CreateDate, LastUpdateDate, DueDate,
            // Priority, Status

            putItem("A-101", "Compilation error",
                "Can't compile Project X - bad version number. What does this mean?",
                "2013-11-01", "2013-11-02", "2013-11-10",
                1, "Assigned");

            putItem("A-102", "Can't read data file",
                "The main data file is missing, or the permissions are incorrect",
                "2013-11-01", "2013-11-04", "2013-11-30",
                2, "In progress");

            putItem("A-103", "Test failure",
                "Functional test of Project X produces errors",
                "2013-11-01", "2013-11-02", "2013-11-10",
                1, "In progress");

            putItem("A-104", "Compilation error",
                "Variable 'messageCount' was not initialized.",
                "2013-11-15", "2013-11-16", "2013-11-30",
                3, "Assigned");

            putItem("A-105", "Network issue",
                "Can't ping IP address 127.0.0.1. Please fix this.",
                "2013-11-15", "2013-11-16", "2013-11-19",
                5, "Assigned");
        }

        private static void putItem(
            String issueId, String title,
            String description,
            String createDate, String lastUpdateDate, String dueDate,
            Int32 priority, String status)
        {
            Dictionary<String, AttributeValue> item = new Dictionary<string, AttributeValue>();

            item.Add("IssueId", new AttributeValue
            {
                S = issueId
            });
            item.Add("Title", new AttributeValue
            {
                S = title
            });
            item.Add("Description", new AttributeValue
            {
                S = description
            });
            item.Add("CreateDate", new AttributeValue
            {
                S = createDate
            });
            item.Add("LastUpdateDate", new AttributeValue
            {
                S = lastUpdateDate
            });
            item.Add("DueDate", new AttributeValue
            {
                S = dueDate
            });
            item.Add("Priority", new AttributeValue
            {
                N = priority.ToString()
            });
            item.Add("Status", new AttributeValue
            {
                S = status
            });

            try
            {
                client.PutItem(new PutItemRequest
                {
                    TableName = tableName,
                    Item = item
                });
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void QueryIndex(string indexName)
        {
            Console.WriteLine
                ("\n***********************************************************\n");
            Console.WriteLine("Querying index " + indexName + "...");

            QueryRequest queryRequest = new QueryRequest
            {
                TableName = tableName,
                IndexName = indexName,
                ScanIndexForward = true
            };


            String keyConditionExpression;
            Dictionary<string, AttributeValue> expressionAttributeValues = new Dictionary<string, AttributeValue>();

            if (indexName == "CreateDateIndex")
            {
                Console.WriteLine("Issues filed on 2013-11-01\n");

                keyConditionExpression = "CreateDate = :v_date and begins_with(IssueId, :v_issue)";
                expressionAttributeValues.Add(":v_date", new AttributeValue
                {
                    S = "2013-11-01"
                });
                expressionAttributeValues.Add(":v_issue", new AttributeValue
                {
                    S = "A-"
                });
            }
            else if (indexName == "TitleIndex")
            {
                Console.WriteLine("Compilation errors\n");

                keyConditionExpression = "Title = :v_title and begins_with(IssueId, :v_issue)";
                expressionAttributeValues.Add(":v_title", new AttributeValue
                {
                    S = "Compilation error"
                });
                expressionAttributeValues.Add(":v_issue", new AttributeValue
                {
                    S = "A-"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else if (indexName == "DueDateIndex")
            {
                Console.WriteLine("Items that are due on 2013-11-30\n");

                keyConditionExpression = "DueDate = :v_date";
                expressionAttributeValues.Add(":v_date", new AttributeValue
                {
                    S = "2013-11-30"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else
            {
                Console.WriteLine("\nNo valid index name provided");
                return;
            }

            queryRequest.KeyConditionExpression = keyConditionExpression;
            queryRequest.ExpressionAttributeValues = expressionAttributeValues;

            var result = client.Query(queryRequest);
            var items = result.Items;
            foreach (var currentItem in items)
            {
                foreach (string attr in currentItem.Keys)
                {
                    if (attr == "Priority")
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].N);
                    }
                    else
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].S);
                    }
                }
                Console.WriteLine();
            }
        }

        private static void DeleteTable(string tableName)
        {
            Console.WriteLine("Deleting table " + tableName + "...");
            client.DeleteTable(new DeleteTableRequest
            {
                TableName = tableName
            });
            WaitForTableToBeDeleted(tableName);
        }

        private static void WaitUntilTableReady(string tableName)
        {
            string status = null;
            // Let us wait until table is created. Call DescribeTable.
            do
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                    status = res.Table.TableStatus;
                }
                catch (ResourceNotFoundException)
                {
                    // DescribeTable is eventually consistent. So you might
                    // get resource not found. So we handle the potential exception.
                }
            } while (status != "ACTIVE");
        }

        private static void WaitForTableToBeDeleted(string tableName)
        {
            bool tablePresent = true;

            while (tablePresent)
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                }
                catch (ResourceNotFoundException)
                {
                    tablePresent = false;
                }
            }
        }
    }
}
```

# 借助 AWS CLI 在 DynamoDB 中使用全局二级索引
<a name="GCICli"></a>

您可以使用 AWS CLI 创建具有一个或多个全局二级索引的 Amazon DynamoDB 表、描述表中的索引，以及使用索引执行查询。

**Topics**
+ [创建一个具有全局二级索引的表。](#GCICli.CreateTableWithIndex)
+ [向现有表添加全局二级索引](#GCICli.CreateIndexAfterTable)
+ [描述一个具有全局二级索引的表](#GCICli.DescribeTableWithIndex)
+ [查询全局二级索引](#GCICli.QueryAnIndex)

## 创建一个具有全局二级索引的表。
<a name="GCICli.CreateTableWithIndex"></a>

全局二级索引可以在您创建表的同时创建。为此，请使用 `create-table` 参数并为一个或多个全局二级索引提供您的规范。下面的示例创建了一个 `GameScores` 表，全局二级索引 `GameTitleIndex`。基表的分区键为 `UserId`，排序键 `GameTitle`，可以有效地找到特定游戏的单个用户的最佳分数，而 GSI 则具有分区键 `GameTitle` 和排序键 `TopScore`，允许您快速找到特定游戏的总体最高分。

```
aws dynamodb create-table \
    --table-name GameScores \
    --attribute-definitions AttributeName=UserId,AttributeType=S \
                            AttributeName=GameTitle,AttributeType=S \
                            AttributeName=TopScore,AttributeType=N  \
    --key-schema AttributeName=UserId,KeyType=HASH \
                 AttributeName=GameTitle,KeyType=RANGE \
    --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --global-secondary-indexes \
        "[
            {
                \"IndexName\": \"GameTitleIndex\",
                \"KeySchema\": [{\"AttributeName\":\"GameTitle\",\"KeyType\":\"HASH\"},
                                {\"AttributeName\":\"TopScore\",\"KeyType\":\"RANGE\"}],
                \"Projection\":{
                    \"ProjectionType\":\"INCLUDE\",
                    \"NonKeyAttributes\":[\"UserId\"]
                },
                \"ProvisionedThroughput\": {
                    \"ReadCapacityUnits\": 10,
                    \"WriteCapacityUnits\": 5
                }
            }
        ]"
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。您可以使用 [describe-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html) 确定表创建的状态。

## 向现有表添加全局二级索引
<a name="GCICli.CreateIndexAfterTable"></a>

创建表后也可以添加或修改全局二级索引。为此，请使用 `update-table` 参数并为一个或多个全局二级索引提供您的规范。以下示例使用与前面示例相同的架构，但假定表已经创建，我们稍后将添加 GSI。

```
aws dynamodb update-table \
    --table-name GameScores \
    --attribute-definitions AttributeName=TopScore,AttributeType=N  \
    --global-secondary-index-updates \
        "[
            {
                \"Create\": {
                    \"IndexName\": \"GameTitleIndex\",
                    \"KeySchema\": [{\"AttributeName\":\"GameTitle\",\"KeyType\":\"HASH\"},
                                    {\"AttributeName\":\"TopScore\",\"KeyType\":\"RANGE\"}],
                    \"Projection\":{
                        \"ProjectionType\":\"INCLUDE\",
                        \"NonKeyAttributes\":[\"UserId\"]
                    }
                }
            }
        ]"
```

## 描述一个具有全局二级索引的表
<a name="GCICli.DescribeTableWithIndex"></a>

要获取有关表的全局二级索引的信息，请使用 `describe-table` 参数。对于每个索引，您都可以查看其名称、键架构和投影的属性。

```
aws dynamodb describe-table --table-name GameScores
```

## 查询全局二级索引
<a name="GCICli.QueryAnIndex"></a>

您可以对全局二级索引使用 `query` 操作，基本上与对表执行 `query` 操作相同。您需要指定索引名称、索引排序键的查询条件以及要返回的属性。在本示例中，索引为 `GameTitleIndex`，索引排序键为 `GameTitle`。

要返回的只包含投影到索引的属性。您也可以修改此查询，让返回结果中也包含非键属性，但是这样会导致表抓取活动的成本相对较高的。有关表获取的更多信息，请参阅 [属性投影](GSI.md#GSI.Projections)。

```
aws dynamodb query --table-name GameScores\
    --index-name GameTitleIndex \
    --key-condition-expression "GameTitle = :v_game" \
    --expression-attribute-values '{":v_game":{"S":"Alien Adventure"} }'
```

# 本地二级索引
<a name="LSI"></a>

某些应用程序只需要使用基表的主键查询数据。但是，在某些情况下，替代排序键可能会有所帮助。要为您的应用程序选择排序键，您可以在 Amazon DynamoDB 表上创建一个或多个本地二级索引，然后对这些索引发出 `Query` 或 `Scan` 请求。

**Topics**
+ [场景：使用本地二级索引](#LSI.Scenario)
+ [属性投影](#LSI.Projections)
+ [创建本地二级索引](#LSI.Creating)
+ [从本地二级索引读取数据](#LSI.Reading)
+ [项目写入和本地二级索引](#LSI.Writes)
+ [全局二级索引的预调配吞吐量注意事项](#LSI.ThroughputConsiderations)
+ [本地二级索引的存储注意事项](#LSI.StorageConsiderations)
+ [本地二级索引中的项目集合](#LSI.ItemCollections)
+ [处理本地二级索引：Java](LSIJavaDocumentAPI.md)
+ [处理本地二级索引：.NET](LSILowLevelDotNet.md)
+ [在 DynamoDB AWS CLI 中使用本地二级索引](LCICli.md)

## 场景：使用本地二级索引
<a name="LSI.Scenario"></a>

例如，请考虑 `Thread` 表。此表对于 [AWS 论坛](https://forums.aws.amazon.com/)等应用程序有用。下表显示了此表中项目的组织方式，（并未显示所有属性。）

![\[包含论坛名称、主题、上次发布时间和回复次数列表的 Thread 表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/LSI_01.png)


DynamoDB 连续存储具有相同分区键值的所有项目。在本例中，给定特定 `ForumName`，`Query` 操作可以立即找到该论坛的所有话题。在具有相同分区键值的项目组中，按排序键值对项目进行排序。如果排序键 (`Subject`)，DynamoDB 可以缩小返回的结果范围-例如，返回“S3”论坛中 `Subject` 以字母“a”开始的所有话题。

某些请求可能需要更复杂的数据访问模式。例如：
+ 哪些论坛主题获得的查看和回复最多？
+ 特定论坛中的哪个主题具有最多的消息？
+ 在特定时间段内，特定论坛上发布了多少主题？

要回答这些问题，`Query` 操作是不够的。相反，您将不得不`Scan`整个表。对于包含数百万个项目的表，这将占用大量预置读取吞吐量，并需要很长时间才能完成。

但是，您可以在非键属性上指定一个或多个本地二级索引，例如 `Replies` 或 `LastPostDateTime`。

*本地二级索引*为给定分区键值维护替代排序键。本地二级索引还包含其基表中部分或全部属性的副本。您可以指定在创建表时将哪些属性投影到本地二级索引中。本地二级索引数据按照基表相同分区键组织，但排序键不同。这样，您就可以跨不同维度高效地访问数据项。为获得更高的查询或扫描灵活性，您可以为每个表创建最多 5 个本地二级索引。

假设应用程序需要查找过去三个月内在某个论坛中发布的所有话题。如果没有本地二级索引，应用程序将不得不 `Scan` 整个 `Thread` 表并放弃任何未在指定时间范围内的帖子。使用本地二级索引，`Query` 操作可以使用 `LastPostDateTime` 作为排序键，快速查找数据。

下图显示了名为 `LastPostIndex` 的本地二级索引。请注意，分区键与 `Thread` 表相同，但排序键是 `LastPostDateTime`。

![\[LastPostIndex 表包含论坛名称、主题和上次发布时间的列表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/LSI_02.png)


每个本地二级索引必须符合以下条件：
+ 分区键与其基表的分区键相同。
+ 排序键仅由一个标量属性组成。
+ 基表的排序键将投影到索引中，其中它充当非键属性。

在本示例中，分区键是 `ForumName`，本地二级索引的排序键为 `LastPostDateTime`。此外，基表中的排序键值（在本示例中，`Subject`）投影到索引中，但它不是索引键的一部分。如果应用程序需要基于 `ForumName` 和 `LastPostDateTime` 的列表，它可以对 `LastPostIndex` 发出 `Query` 请求。查询结果按照 `LastPostDateTime` 排序，并且可以按升序或降序排序返回。查询还可以应用关键条件，例如，仅返回在特定的时间范围内具有 `LastPostDateTime` 的项目。

每个本地二级索引自动包含其基表中的分区和排序键；您可以选择将非键属性投影到索引中。当您查询索引时，DynamoDB 便可高效地检索这些已投影的属性。查询本地二级索引时，查询还可以检索*未*投影到索引的属性。DynamoDB 会自动从基表中提取这些属性，但延迟更大，预置吞吐量成本也更高。

对于任何本地二级索引，每个不同分区键值最多可以存储 10 GB 的数据。此数字包括基表中的所有项目，以及索引中具有相同分区键值的所有项目。有关更多信息，请参阅 [本地二级索引中的项目集合](#LSI.ItemCollections)。

## 属性投影
<a name="LSI.Projections"></a>

对于 `LastPostIndex`，应用程序可以使用 `ForumName` 和 `LastPostDateTime` 作为查询条件。但是，要检索任何其他属性，DynamoDB 必须对 `Thread` 表执行额外读取操作。这些额外读取称为*获取*，可以增加查询所需的预置吞吐量总量。

假设您希望使用“S3”中所有话题的列表以及每个话题的回复数量填充一个网页，并按最近回复开始的最后一个回复日期/时间排序。要填充此列表，您需要以下属性：
+ `Subject`
+ `Replies`
+ `LastPostDateTime`

查询这些数据并避免获取操作的最有效方法是将 `Replies` 属性从表投影到本地二级索引，如此图所示。

![\[LastPostIndex 表，其中包含论坛名称、上次发布时间、主题和回复的列表。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/LSI_03.png)




*投影*是从表复制到二级索引的属性集。表的分区键和排序键始终投影到索引中；您可以投影其他属性以支持应用程序的查询要求。当您查询索引时，Amazon DynamoDB 可以访问投影中的任何属性，就像这些属性位于自己的表中一样。

创建二级索引时，需要指定将投影到索引中的属性。DynamoDB 为此提供了三种不同的选项：
+ *KEYS\$1ONLY* – 索引中的每个项目仅包含表的分区键、排序键值以及索引键值。`KEYS_ONLY` 选项会导致最小二级索引。
+ *INCLUDE* – 除 `KEYS_ONLY` 中描述的属性外，二级索引还包括您指定的其他非键属性。
+ *ALL* – 二级索引包括源表中的所有属性。由于所有表数据都在索引中复制，因此 `ALL` 投影会产生最大二级索引。

在上图中，非键属性 `Replies` 投影到 `LastPostIndex`。应用程序可以查询 `LastPostIndex` 而不是整个 `Thread` 表，用 `Subject`、`Replies` 和 `LastPostDateTime` 填充网页。如果请求任何其他非键属性，DynamoDB 将需要从 `Thread` 表获取这些属性。

从应用程序的角度来看，从基表中获取其他属性是自动且透明的，因此无需重写任何应用程序逻辑。但是，这种读取可以大大降低使用本地二级索引的性能优势。

选择要投影到本地二级索引中的属性时，必须在预置吞吐量成本和存储成本之间做出权衡：
+ 如果只需要访问少量属性，同时尽可能降低延迟，就应考虑仅将键属性投影到本地二级索引。索引越小，存储索引所需的成本越少，并且写入成本也会越少。如果您偶尔需要获取属性，则预置吞吐量的成本可能会超过存储这些属性的较长期成本。
+ 如果您的应用程序频繁访问某些非键属性，就应考虑将这些属性投影到本地二级索引。本地二级索引的额外存储成本会抵消频繁执行表扫描的成本。
+ 如果需要频繁访问大多数非键属性，则可以将这些属性（甚至整个基表）投影到本地二级索引中。这为您提供了最大的灵活性和最低的预置吞吐量消耗，因为不需要提取。但是，如果投影所有属性，您的存储成本将增加，甚至翻倍。
+ 如果您的应用程序并不会频繁查询表，但必须要对表中的数据执行大量写入或更新操作，就应考虑投影 *KEYS\$1ONLY*。这是最小的本地二级索引，但仍可用于查询活动。

## 创建本地二级索引
<a name="LSI.Creating"></a>

要在表上创建一个或多个本地二级索引，请使用 `CreateTable` 操作的 `LocalSecondaryIndexes` 参数。表上的本地二级索引是在创建表时创建的。删除表时，会同时删除该表的任何本地二级索引。

必须指定一个非键属性以充当本地二级索引的排序键。您选择的属性必须是标量 `String`、`Number` 或 `Binary`。不允许使用其他标量类型、文档类型和集合类型。有关数据类型的完整列表，请参阅 [数据类型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)。

**重要**  
对于具有本地二级索引的表，每个分区键值有 10 GB 的大小限制。具有本地二级索引的表可以存储任意数量的项目，只要任何一个分区键值的总大小不超过 10 GB。有关更多信息，请参阅 [项目集合大小限制](#LSI.ItemCollections.SizeLimit)。

您可以将任何数据类型的属性投影到本地二级索引中。这包括标量、文档和集。有关数据类型的完整列表，请参阅 [数据类型](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)。

## 从本地二级索引读取数据
<a name="LSI.Reading"></a>

您可以使用 `Query` 和 `Scan` 操作从本地二级索引检索项目。`GetItem` 和 `BatchGetItem` 操作不能在本地二级索引上使用。

### 查询本地二级索引
<a name="LSI.Querying"></a>

在 DynamoDB 表中，每个项目的组合分区键值和排序键值必须唯一。但是，在本地二级索引中，排序键值不需要对于给定分区键值唯一。如果本地二级索引中有多个具有相同排序键值的项目，则 `Query` 操作返回具有相同分区键值的所有项目。在响应中，匹配的项目不会以任何特定顺序返回。

您可以使用最终一致性读取或强一致性读取来查询本地二级索引。要指定所需的一致性类型，请使用 `Query` 操作的 `ConsistentRead` 参数。从本地二级索引进行的强一致性读取始终返回上次更新的值。如果查询需要从基表中获取其他属性，则这些属性将与索引保持一致。

**Example**  
考虑以下数据从 `Query` 返回，请求来自特定论坛中的讨论主题的数据。  

```
{
    "TableName": "Thread",
    "IndexName": "LastPostIndex",
    "ConsistentRead": false,
    "ProjectionExpression": "Subject, LastPostDateTime, Replies, Tags",
    "KeyConditionExpression": 
        "ForumName = :v_forum and LastPostDateTime between :v_start and :v_end",
    "ExpressionAttributeValues": {
        ":v_start": {"S": "2015-08-31T00:00:00.000Z"},
        ":v_end": {"S": "2015-11-31T00:00:00.000Z"},
        ":v_forum": {"S": "EC2"}
    }
}
```
在此查询中：  
+ DynamoDB 访问 `LastPostIndex`，使用 `ForumName` 分区键查找“EC2”的索引项目。具有此键的所有索引项目都彼此相邻存储，以实现快速检索。
+ 在此论坛中，DynamoDB 使用索引来查找匹配指定 `LastPostDateTime` 条件的键。
+ 由于 `Replies` 属性投影到索引中，DynamoDB 可以检索此属性，而不会占用任何额外的预置吞吐量。
+ `Tags` 属性不会投影到索引中，因此 DynamoDB 必须访问 `Thread` 表并获取此属性。
+ 返回结果，按 `LastPostDateTime` 排序。索引条目按分区键值排序，然后按排序键值排序，`Query` 按照存储顺序返回它们。（可以使用 `ScanIndexForward` 参数按降序返回结果。）
由于 `Tags` 属性不会投影到本地二级索引中，DynamoDB 必须占用额外的读取容量单位才能从基表中获取此属性。如果需要经常运行此查询，应将 `Tags` 投影到 `LastPostIndex` 以避免从基表提取。但是，如果仅偶尔需要访问 `Tags`，将 `Tags` 投影到索引的额外存储成本可能不值得。

### 扫描本地二级索引
<a name="LSI.Scanning"></a>

您可以使用 `Scan` 从本地二级索引检索全部数据。您必须在请求中提供基表名称和索引名称。通过 `Scan`，DynamoDB 可读取索引中的全部数据并将其返回到应用程序。您还可以请求仅返回部分数据并放弃其余数据。为此，请使用 `Scan` API 的 `FilterExpression` 参数。有关更多信息，请参阅 [扫描的筛选表达式](Scan.md#Scan.FilterExpression)。

## 项目写入和本地二级索引
<a name="LSI.Writes"></a>

DynamoDB 会自动保持所有本地二级索引与各自的基表同步。应用程序绝不会直接向索引中写入内容。但是，您有必要了解 DynamoDB 如何维护这些索引。

创建本地二级索引时，指定一个属性以作为索引的排序键。您还可以为该属性指定数据类型。这就意味着，无论您何时向基表中写入项目，如果项目定义索引键值，其类型必须匹配索引键架构的数据类型。在 `LastPostIndex` 的情况下，索引中的 `LastPostDateTime` 排序键定义为 `String` 数据类型。如果您尝试向 `Thread` 表添加项目并为 `LastPostDateTime`（如 `Number`）指定其他数据类型，DynamoDB 会因数据类型不匹配而返回 `ValidationException`。

基表中的项目与本地二级索引中的项目之间不需要一对一的关系。事实上，这种行为对于许多应用程序都是有利的。

相较于索引数量较少的表，拥有较多本地二级索引的表会产生较高的写入活动成本。有关更多信息，请参阅 [全局二级索引的预调配吞吐量注意事项](#LSI.ThroughputConsiderations)。

**重要**  
对于具有本地二级索引的表，每个分区键值有 10 GB 的大小限制。具有本地二级索引的表可以存储任意数量的项目，只要任何一个分区键值的总大小不超过 10 GB。有关更多信息，请参阅 [项目集合大小限制](#LSI.ItemCollections.SizeLimit)。

## 全局二级索引的预调配吞吐量注意事项
<a name="LSI.ThroughputConsiderations"></a>

在 DynamoDB 中创建表时，为表的预期工作负载预置读取和写入容量单位。该工作负载包括对表的本地二级索引的读取和写入活动。

要查看预置吞吐量容量的当前值，请参阅 [Amazon DynamoDB 定价](https://aws.amazon.com/dynamodb/pricing)。

### 读取容量单位
<a name="LSI.ThroughputConsiderations.Reads"></a>

查询本地二级索引时，占用的读取容量单位数取决于访问数据的方式。

与表查询一样，索引查询可以使用最终一致性读取或强一致性读取，具体取决于 `ConsistentRead` 值。一个强一致性读取占用一个读取容量单位；最终一致性读取只占用一半的读取容量。因此，选择最终一致性读取，可以减少读取容量单位费用。

对于仅请求索引键和投影属性的索引查询，DynamoDB 计算预配置读取活动的方式与对表查询使用的方式相同。唯一不同的是，本次计算基于索引条目的大小，而不是基表中项目的大小。读取容量单位的数量就是返回的所有项目的所有投影属性大小之和；然后结果向上取整至 4 KB 边界。有关 DynamoDB 如何计算预置吞吐量使用情况的更多信息，请参阅 [DynamoDB 预置容量模式](provisioned-capacity-mode.md)。

对于读取未投影到本地二级索引的属性的索引查询，DynamoDB 除了从索引读取投影属性之外，还需要从基表中获取这些属性。如果在 `Query` 操作的 `Select` 或 `ProjectionExpression` 参数中加入任何非投影属性，将发生获取。读取会导致查询响应中的额外延迟，并且还会产生更高的预配置吞吐量成本：除了上述从本地二级索引进行读取之外，您还需要为获取的每个基表项目支付读取容量单位费用。此费用用于从表读取每个完整项目，而不仅仅是请求的属性。

`Query` 操作返回的结果大小上限为 1 MB。这包括所有属性名称的大小和所返回的所有项目的值。但是，如果针对本地二级索引的查询导致 DynamoDB 从基表中获取项目属性，则结果中数据的最大大小可能会较低。在此情况下，结果大小为以下总和：
+ 索引中匹配项目的大小，四舍五入到下一个 4 KB。
+ 基表中每个匹配项目的大小，每个项目分别向上舍入到下一个 4 KB。

使用此公式，查询操作返回的结果大小上限仍为 1 MB。

例如，请考虑使用每个项目为 300 字节的表。该表有一个本地二级索引，但只有 200 个字节的每个项目投影到索引中。现在假设 `Query` 此索引，查询需要为每个项目提取表，并且查询返回 4 个项目。DynamoDB 总结了以下几点：
+ 索引中匹配项目的大小：200 字节 × 4 个项目 = 800 字节；然后四舍五入为 4 KB。
+ 基表中每个匹配项目的大小：（300 字节，四舍五入为 4 KB）×4 个项目 = 16 KB。

因此，结果中数据的总大小为 20 KB。

### 写入容量单位
<a name="LSI.ThroughputConsiderations.Writes"></a>

添加、更新或删除表中的项目时，更新本地二级索引将占用表的预置写入容量单位。一次写入操作的预置吞吐量总成本是对表执行的写入操作以及更新本地二级索引所占用的写入容量单位之和。

向本地二级索引写入项目的成本取决于多个因素：
+ 如果您向定义了索引属性的表中写入新项目，或更新现有的项目来定义之前未定义的索引属性，只需一个写入操作即可将项目放置到索引中。
+ 如果对表执行的更新操作更改了索引键属性的值（从 A 更改为 B），就需要执行两次写入操作，一次用于删除索引中之前的项目，另一次用于将新项目放置到索引中。  
+ 如果索引中已有某一项目，而对表执行的写入操作删除了索引属性，就需要执行一次写入操作删除索引中旧的项目投影。
+ 如果更新项目前后索引中没有此项目，此索引就不会额外产生写入成本。

所有这些因素都假定索引中每个项目的大小小于或等于 1 KB 这一项目大小（用于计算写入容量单位）。如果索引条目大于这一大小，就会占用额外的写入容量单位。您可以考虑查询需要返回的属性类型并仅将这些属性投影到索引中，从而最大程度地减少写入成本。

## 本地二级索引的存储注意事项
<a name="LSI.StorageConsiderations"></a>

当应用程序向表中写入项目时，DynamoDB 会自动将适当的属性子集复制到应包含这些属性的所有本地二级索引。您的 AWS 账户需要支付在基表中存储项目以及在表的任何本地二级索引中存储属性的费用。

索引项目所占用的空间大小就是以下内容之和：
+ 基表的主键 (分区键和排序键) 的大小 (按字节计算)
+ 索引键属性的大小（按字节计算）
+ 投影的属性（如果有）的大小（按字节计算）
+ 每个索引项目 100 字节的开销

要估算本地二级索引的存储要求，您可以估算索引中项目的平均大小，然后乘以索引中的项目数。

如果表包含的某个项目未定义特定属性，但是该属性定义为索引排序键，则 DynamoDB 不会将该项目的任何数据写入到索引中。

## 本地二级索引中的项目集合
<a name="LSI.ItemCollections"></a>

**注意**  
本节仅涉及具有本地二级索引的表。

在 DynamoDB 中，*项目集合*是指表中具有相同分区键值的项目及其所有本地二级索引的任何组。在本节中使用的示例中，`Thread` 表的分区键为 `ForumName`，`LastPostIndex` 的分区键也是 `ForumName`。所有具有相同 `ForumName` 的表和索引项目是同一个项目集合的一部分。例如，在 `Thread` 表和 `LastPostIndex` 本地二级索引，有一个论坛的项目集合 `EC2` 和论坛的不同项目集合 `RDS`。

下图显示了论坛 `S3` 的项目集合。

![\[DynamoDB 项目集合，其中包含具有相同 S3 分区键值的表项目和本地二级索引项目。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/LSI_04.png)


在此图中，项目集合包含 `Thread` 和 `LastPostIndex` 中，`ForumName` 分区键值为“S3”的所有项目。如果表上有其他本地二级索引，那么这些索引中 `ForumName` 等于“S3”的任何项目也将成为项目集合的一部分。

您可以在 DynamoDB 中使用以下任何操作来返回有关项目集合的信息：
+ `BatchWriteItem`
+ `DeleteItem`
+ `PutItem`
+ `UpdateItem`
+ `TransactWriteItems`

这些操作中的每个操作都支持 `ReturnItemCollectionMetrics` 参数。将此参数设置为 `SIZE`，可以查看有关索引中每个项目集合大小的信息。

**Example**  
下面是 `Thread` 表的 `UpdateItem` 操作输出示例，`ReturnItemCollectionMetrics` 设置为 `SIZE`。更新的项目的 `ForumName` 值为“EC2”，因此输出包含有关该项目集合的信息。  

```
{
    ItemCollectionMetrics: {
        ItemCollectionKey: {
            ForumName: "EC2"
        },
        SizeEstimateRangeGB: [0.0, 1.0]
    }
}
```
`SizeEstimateRangeGB` 对象显示此项目集合的大小介于 0 到 1 GB 之间。DynamoDB 会定期更新此大小估计值，因此下次修改项目时，数字可能会有所不同。

### 项目集合大小限制
<a name="LSI.ItemCollections.SizeLimit"></a>

对于具有一个或多个本地二级索引的表，任何项目集合的最大大小均为 10GB。这不适用于没有本地二级索引的表中的项目集合，也不适用于全局二级索引中的项目集合。只有具有一个或多个本地二级索引的表受影响。

如果项目集合超过 10 GB 限制，则 DynamoDB 可能返回 `ItemCollectionSizeLimitExceededException`，并且您可能无法向项目集合添加更多项目或增加项目集合中项目的大小。（仍然允许对项目集合的大小进行读取和写入操作。） 您仍然可以将项目添加到其他项目集合。

要减小项目集合的大小，您可以执行以下操作之一：
+ 删除具有相关分区键值的所有不必要项目。从基表中删除这些项目时，DynamoDB 还会删除具有相同分区键值的所有索引条目。
+ 通过删除属性或减小属性的大小来更新项目。如果将这些属性投影到任何本地二级索引中，DynamoDB 还会减小相应索引条目的大小。
+ 使用相同的分区键和排序键创建新表，然后将项目从旧表移动到新表。如果表具有不经常访问的历史数据，这可能是一种很好的方法。您还可能考虑将此历史数据存档到 Amazon Simple Storage Service (Amazon S3)。

当项目集合的总大小低于 10 GB 时，您可以再次添加具有相同分区键值的项目。

作为最佳实践，我们建议设置应用程序监控项目集合的大小。一种方法使用 `BatchWriteItem`、`DeleteItem`、`PutItem` 或 `UpdateItem` 时将 `SIZE` 参数设置为 `ReturnItemCollectionMetrics`。您的应用程序应检查输出的 `ReturnItemCollectionMetrics` 对象，在项目集合超过用户定义的限制（例如 8 GB）时记录错误消息。设置一个小于 10 GB 的限制将提供一个预警系统，以便您知道项目集合即将接近限制，以便对其执行某些操作。

### 项目集合和分区
<a name="LSI.ItemCollections.OnePartition"></a>

在带一个或多个本地二级索引的表中，每个项目集合存储在一个分区中。此类项目集合的总大小限制为该分区的容量：10GB。对于应用程序而言，如果其数据模型包含大小不受限制的项目集合，或者您可能合理地预期某些项目集合将来会增长到 10GB 以上，那么您应该考虑改用全局二级索引。

应设计应用程序，在不同分区键值之间均匀分布表数据。对于具有本地二级索引的表，应用程序不应在单个分区上的单个项目集合中创建读取和写入活动的“热点”。

# 处理本地二级索引：Java
<a name="LSIJavaDocumentAPI"></a>

您可以使用 适用于 Java 的 AWS SDK 文档 API 创建具有一个或多个本地二级索引的 Amazon DynamoDB 表、描述表中的索引，以及使用索引执行查询。

下面是使用 适用于 Java 的 AWS SDK 文档 API 执行表操作的常见步骤。

1. 创建 `DynamoDB` 类的实例。

1. 通过创建对应的请求对象，为操作提供必需参数和可选参数。

1. 调用您在前面步骤中创建的客户端提供的适当方法。

**Topics**
+ [创建具有本地二级索引的表](#LSIJavaDocumentAPI.CreateTableWithIndex)
+ [描述具有本地二级索引的表](#LSIJavaDocumentAPI.DescribeTableWithIndex)
+ [查询本地二级索引](#LSIJavaDocumentAPI.QueryAnIndex)
+ [示例：使用 Java 文档 API 的本地二级索引](LSIJavaDocumentAPI.Example.md)

## 创建具有本地二级索引的表
<a name="LSIJavaDocumentAPI.CreateTableWithIndex"></a>

本地二级索引必须在您创建表的同时创建。为此，请使用 `createTable` 方法并为一个或多个本地二级索引提供您的规范。以下 Java 代码示例创建一个包含音乐精选中歌曲信息的表。分区键为 `Artist`，排序键为 `SongTitle`。`AlbumTitleIndex` 这一二级索引可以按专辑名称进行查询。

下面是使用 DynamoDB 文档 API 创建具有本地二级索引的表的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `CreateTableRequest` 类实例，以提供请求信息。

   您必须提供表名称、主键以及预配置吞吐量值。对于本地二级索引，您必须提供索引名称、索引排序键的名称和数据类型、索引的键架构以及属性投影。

1. 以参数形式提供请求对象，以调用 `createTable` 方法。

以下 Java 代码示例演示了上述步骤。该代码创建表 (`Music`)，在 `AlbumTitle` 属性上具有二级索引。投影到索引的属性只有表的分区键、排序键以及索引排序键。

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName);

//ProvisionedThroughput
createTableRequest.setProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits((long)5).withWriteCapacityUnits((long)5));

//AttributeDefinitions
ArrayList<AttributeDefinition> attributeDefinitions= new ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("Artist").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("SongTitle").withAttributeType("S"));
attributeDefinitions.add(new AttributeDefinition().withAttributeName("AlbumTitle").withAttributeType("S"));

createTableRequest.setAttributeDefinitions(attributeDefinitions);

//KeySchema
ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>();
tableKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
tableKeySchema.add(new KeySchemaElement().withAttributeName("SongTitle").withKeyType(KeyType.RANGE));  //Sort key

createTableRequest.setKeySchema(tableKeySchema);

ArrayList<KeySchemaElement> indexKeySchema = new ArrayList<KeySchemaElement>();
indexKeySchema.add(new KeySchemaElement().withAttributeName("Artist").withKeyType(KeyType.HASH));  //Partition key
indexKeySchema.add(new KeySchemaElement().withAttributeName("AlbumTitle").withKeyType(KeyType.RANGE));  //Sort key

Projection projection = new Projection().withProjectionType(ProjectionType.INCLUDE);
ArrayList<String> nonKeyAttributes = new ArrayList<String>();
nonKeyAttributes.add("Genre");
nonKeyAttributes.add("Year");
projection.setNonKeyAttributes(nonKeyAttributes);

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
    .withIndexName("AlbumTitleIndex").withKeySchema(indexKeySchema).withProjection(projection);

ArrayList<LocalSecondaryIndex> localSecondaryIndexes = new ArrayList<LocalSecondaryIndex>();
localSecondaryIndexes.add(localSecondaryIndex);
createTableRequest.setLocalSecondaryIndexes(localSecondaryIndexes);

Table table = dynamoDB.createTable(createTableRequest);
System.out.println(table.getDescription());
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。

## 描述具有本地二级索引的表
<a name="LSIJavaDocumentAPI.DescribeTableWithIndex"></a>

要获取表上有关本地二级索引的信息，请使用 `describeTable` 方法。对于每个索引，您都可以查看其名称、键架构和投影的属性。

以下是使用 适用于 Java 的 AWS SDK 文档 API 访问表的本地二级索引信息的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例。您必须提供表名称。

1. 调用 `describeTable` 对象上的 `Table` 方法。

以下 Java 代码示例演示了上述步骤。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);

TableDescription tableDescription = table.describe();

List<LocalSecondaryIndexDescription> localSecondaryIndexes 
    = tableDescription.getLocalSecondaryIndexes();

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.

Iterator<LocalSecondaryIndexDescription> lsiIter = localSecondaryIndexes.iterator();
while (lsiIter.hasNext()) {

    LocalSecondaryIndexDescription lsiDescription = lsiIter.next();
    System.out.println("Info for index " + lsiDescription.getIndexName() + ":");
    Iterator<KeySchemaElement> kseIter = lsiDescription.getKeySchema().iterator();
    while (kseIter.hasNext()) {
        KeySchemaElement kse = kseIter.next();
        System.out.printf("\t%s: %s\n", kse.getAttributeName(), kse.getKeyType());
    }
    Projection projection = lsiDescription.getProjection();
    System.out.println("\tThe projection type is: " + projection.getProjectionType());
    if (projection.getProjectionType().toString().equals("INCLUDE")) {
        System.out.println("\t\tThe non-key projected attributes are: " + projection.getNonKeyAttributes());
    }
}
```

## 查询本地二级索引
<a name="LSIJavaDocumentAPI.QueryAnIndex"></a>

您可以对本地二级索引使用 `Query` 操作，基本与对表执行 `Query` 操作一样。您需要指定索引名称、索引排序键的查询条件以及要返回的属性。在本示例中，索引为 `AlbumTitleIndex`，索引排序键为 `AlbumTitle`。

要返回的只包含投影到索引的属性。您也可以修改此查询，让返回结果中也包含非键属性，但是这样会导致表抓取活动的成本相对较高的。有关表获取的更多信息，请参阅 [属性投影](LSI.md#LSI.Projections)。

以下是使用 适用于 Java 的 AWS SDK 文档 API 查询本地二级索引的步骤。

1. 创建 `DynamoDB` 类的实例。

1. 创建 `Table` 类的实例。您必须提供表名称。

1. 创建 `Index` 类的实例。您必须提供索引名称。

1. 调用 `query` 类的 `Index` 方法。

以下 Java 代码示例演示了上述步骤。

**Example**  

```
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDB dynamoDB = new DynamoDB(client);

String tableName = "Music";

Table table = dynamoDB.getTable(tableName);
Index index = table.getIndex("AlbumTitleIndex");

QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"));

ItemCollection<QueryOutcome> items = index.query(spec);

Iterator<Item> itemsIter = items.iterator();

while (itemsIter.hasNext()) {
    Item item = itemsIter.next();
    System.out.println(item.toJSONPretty());
}
```

### 对本地二级索引的一致性读取
<a name="LSIJavaDocumentAPI.ConsistentReads"></a>

与仅支持最终一致性读取的全局二级索引不同，本地二级索引支持最终一致性读取和强一致性读取。从本地二级索引进行的强一致性读取始终返回上次更新的值。如果查询需要从基表中提取其他属性，则这些提取的属性同样与索引保持一致。

默认情况下，`Query` 使用最终一致性读取。要请求强一致性读取，请在 `QuerySpec` 中将 `ConsistentRead` 设置为 `true`。以下示例使用强一致性读取查询 `AlbumTitleIndex`：

**Example**  

```
QuerySpec spec = new QuerySpec()
    .withKeyConditionExpression("Artist = :v_artist and AlbumTitle = :v_title")
    .withValueMap(new ValueMap()
        .withString(":v_artist", "Acme Band")
        .withString(":v_title", "Songs About Life"))
    .withConsistentRead(true);
```

**注意**  
对于返回的每 4 KB 数据（向上取整），一个强一致性读取消耗一个读取容量单位，而最终一致性读取仅消耗一半的读取容量单位。例如，返回 9 KB 数据的强一致性读取消耗 3 个读取容量单位（9 KB/4 KB = 2.25，向上取整为 3），而使用最终一致性读取的相同查询消耗 1.5 个读取容量单位。如果您的应用程序能够容忍读取可能稍微陈旧的数据，请使用最终一致性读取来减少读取容量使用量。有关更多信息，请参阅 [读取容量单位](LSI.md#LSI.ThroughputConsiderations.Reads)。

# 示例：使用 Java 文档 API 的本地二级索引
<a name="LSIJavaDocumentAPI.Example"></a>

以下 Java 代码示例显示如何在Amazon DynamoDB 中处理本地二级索引。示例创建名为 `CustomerOrders` 的表，其分区键为 `CustomerId`，排序键为 `OrderId`。此表上有两个本地二级索引：
+ `OrderCreationDateIndex` — 排序键是 `OrderCreationDate`，并将以下属性投影到索引：
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex` — 排序键是 `IsOpen`，并将表的所有属性投影到索引。

创建 `CustomerOrders` 表后，程序为该表加载表示客户订单的数据。然后使用本地二级索引查询这些数据。最后，程序会删除 `CustomerOrders` 表。

有关测试以下示例的分步说明，请参阅 [Java 代码示例](CodeSamples.Java.md)。

**Example**  

```
package com.example.dynamodb;

import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;

import java.util.HashMap;
import java.util.Map;

public class DocumentAPILocalSecondaryIndexExample {

    static DynamoDbClient client = DynamoDbClient.create();
    public static String tableName = "CustomerOrders";

    public static void main(String[] args) {
        createTable();
        loadData();
        query(null);
        query("IsOpenIndex");
        query("OrderCreationDateIndex");
        deleteTable(tableName);
    }

    public static void createTable() {
        CreateTableRequest request = CreateTableRequest.builder()
            .tableName(tableName)
            .provisionedThroughput(ProvisionedThroughput.builder()
                .readCapacityUnits(1L)
                .writeCapacityUnits(1L)
                .build())
            .attributeDefinitions(
                AttributeDefinition.builder().attributeName("CustomerId").attributeType(ScalarAttributeType.S).build(),
                AttributeDefinition.builder().attributeName("OrderId").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("OrderCreationDate").attributeType(ScalarAttributeType.N).build(),
                AttributeDefinition.builder().attributeName("IsOpen").attributeType(ScalarAttributeType.N).build())
            .keySchema(
                KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                KeySchemaElement.builder().attributeName("OrderId").keyType(KeyType.RANGE).build())
            .localSecondaryIndexes(
                LocalSecondaryIndex.builder()
                    .indexName("OrderCreationDateIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("OrderCreationDate").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.INCLUDE)
                        .nonKeyAttributes("ProductCategory", "ProductName")
                        .build())
                    .build(),
                LocalSecondaryIndex.builder()
                    .indexName("IsOpenIndex")
                    .keySchema(
                        KeySchemaElement.builder().attributeName("CustomerId").keyType(KeyType.HASH).build(),
                        KeySchemaElement.builder().attributeName("IsOpen").keyType(KeyType.RANGE).build())
                    .projection(Projection.builder()
                        .projectionType(ProjectionType.ALL)
                        .build())
                    .build())
            .build();

        System.out.println("Creating table " + tableName + "...");
        client.createTable(request);

        try (DynamoDbWaiter waiter = client.waiter()) {
            WaiterResponse<DescribeTableResponse> response = waiter.waitUntilTableExists(r -> r.tableName(tableName));
            response.matched().response().ifPresent(System.out::println);
        }
    }

    public static void query(String indexName) {
        System.out.println("\n***********************************************************\n");
        System.out.println("Querying table " + tableName + "...");

        if ("IsOpenIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that are open.");
            System.out.println("Only a user-specified list of attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_isopen", AttributeValue.builder().n("1").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and IsOpen = :v_isopen")
                .expressionAttributeValues(values)
                .projectionExpression("OrderCreationDate, ProductCategory, ProductName, OrderStatus")
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else if ("OrderCreationDateIndex".equals(indexName)) {
            System.out.println("\nUsing index: '" + indexName + "': Bob's orders that were placed after 01/31/2015.");
            System.out.println("Only the projected attributes are returned\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());
            values.put(":v_orddate", AttributeValue.builder().n("20150131").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .indexName(indexName)
                .keyConditionExpression("CustomerId = :v_custid and OrderCreationDate >= :v_orddate")
                .expressionAttributeValues(values)
                .select(Select.ALL_PROJECTED_ATTRIBUTES)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);

        } else {
            System.out.println("\nNo index: All of Bob's orders, by OrderId:\n");

            Map<String, AttributeValue> values = new HashMap<>();
            values.put(":v_custid", AttributeValue.builder().s("bob@example.com").build());

            QueryRequest request = QueryRequest.builder()
                .tableName(tableName)
                .keyConditionExpression("CustomerId = :v_custid")
                .expressionAttributeValues(values)
                .build();

            System.out.println("Query: printing results...");
            client.query(request).items().forEach(System.out::println);
        }
    }

    public static void deleteTable(String tableName) {
        System.out.println("Deleting table " + tableName + "...");
        client.deleteTable(DeleteTableRequest.builder().tableName(tableName).build());

        try (DynamoDbWaiter waiter = client.waiter()) {
            waiter.waitUntilTableNotExists(r -> r.tableName(tableName));
        }
    }

    public static void loadData() {
        System.out.println("Loading data into table " + tableName + "...");

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150101").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("The Great Outdoors").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Bike").build(),
            "ProductName", AttributeValue.builder().s("Super Mountain").build(),
            "OrderStatus", AttributeValue.builder().s("ORDER RECEIVED").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("alice@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150304").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("A Quiet Interlude").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("176493").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150111").build(),
            "ProductCategory", AttributeValue.builder().s("Movie").build(),
            "ProductName", AttributeValue.builder().s("Calm Before The Storm").build(),
            "OrderStatus", AttributeValue.builder().s("SHIPPING DELAY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("859323").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("2").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150124").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("E-Z Listening").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("756943").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("3").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150221").build(),
            "ProductCategory", AttributeValue.builder().s("Music").build(),
            "ProductName", AttributeValue.builder().s("Symphony 9").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("645193").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("4").build(),
            "IsOpen", AttributeValue.builder().n("1").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150222").build(),
            "ProductCategory", AttributeValue.builder().s("Hardware").build(),
            "ProductName", AttributeValue.builder().s("Extra Heavy Hammer").build(),
            "OrderStatus", AttributeValue.builder().s("PACKING ITEMS").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("5").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150309").build(),
            "ProductCategory", AttributeValue.builder().s("Book").build(),
            "ProductName", AttributeValue.builder().s("How To Cook").build(),
            "OrderStatus", AttributeValue.builder().s("IN TRANSIT").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("440185").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("6").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150318").build(),
            "ProductCategory", AttributeValue.builder().s("Luggage").build(),
            "ProductName", AttributeValue.builder().s("Really Big Suitcase").build(),
            "OrderStatus", AttributeValue.builder().s("DELIVERED").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("893927").build()));

        putItem(Map.of(
            "CustomerId", AttributeValue.builder().s("bob@example.com").build(),
            "OrderId", AttributeValue.builder().n("7").build(),
            "OrderCreationDate", AttributeValue.builder().n("20150324").build(),
            "ProductCategory", AttributeValue.builder().s("Golf").build(),
            "ProductName", AttributeValue.builder().s("PGA Pro II").build(),
            "OrderStatus", AttributeValue.builder().s("OUT FOR DELIVERY").build(),
            "ShipmentTrackingId", AttributeValue.builder().s("383283").build()));
    }

    private static void putItem(Map<String, AttributeValue> item) {
        client.putItem(PutItemRequest.builder().tableName(tableName).item(item).build());
    }
}
```

# 处理本地二级索引：.NET
<a name="LSILowLevelDotNet"></a>

**Topics**
+ [创建具有本地二级索引的表](#LSILowLevelDotNet.CreateTableWithIndex)
+ [描述具有本地二级索引的表](#LSILowLevelDotNet.DescribeTableWithIndex)
+ [查询本地二级索引](#LSILowLevelDotNet.QueryAnIndex)
+ [示例：使用 适用于 .NET 的 AWS SDK 低级 API 的本地二级索引](LSILowLevelDotNet.Example.md)

您可以使用 适用于 .NET 的 AWS SDK 低级 API 创建具有一个或多个本地二级索引的 Amazon DynamoDB 表、描述表中的索引，以及使用索引执行查询。这些操作会映射到对应的低级 DynamoDB API 操作。有关更多信息，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

以下是使用 .NET 低级 API 执行表操作的常见步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 通过创建对应的请求对象，为操作提供必需参数和可选参数。

   例如，创建一个 `CreateTableRequest` 数据元以创建表；创建一个 `QueryRequest` 数据元以查询表或索引。

1. 运行您在前面步骤中创建的客户端提供的适当方法。

## 创建具有本地二级索引的表
<a name="LSILowLevelDotNet.CreateTableWithIndex"></a>

本地二级索引必须在您创建表的同时创建。为此，请使用 `CreateTable` 并为一个或多个本地二级索引提供您的规范。以下 C\$1 代码示例创建一个包含音乐精选中歌曲信息的表。分区键为 `Artist`，排序键为 `SongTitle`。`AlbumTitleIndex` 这一二级索引可以按专辑名称进行查询。

以下是使用 .NET 低级别 API 创建具有本地二级索引的表的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `CreateTableRequest` 类实例，以提供请求信息。

   您必须提供表名称、主键以及预配置吞吐量值。对于本地二级索引，您必须提供索引名称、索引排序键的名称和数据类型、索引的键架构以及属性投影。

1. 以参数形式提供请求对象，运行 `CreateTable` 方法。

以下 C\$1 代码示例演示了上述步骤。该代码创建表 (`Music`)，在 `AlbumTitle` 属性上具有二级索引。投影到索引的属性只有表的分区键、排序键以及索引排序键。

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

CreateTableRequest createTableRequest = new CreateTableRequest()
{
    TableName = tableName
};

//ProvisionedThroughput
createTableRequest.ProvisionedThroughput = new ProvisionedThroughput()
{
    ReadCapacityUnits = (long)5,
    WriteCapacityUnits = (long)5
};

//AttributeDefinitions
List<AttributeDefinition> attributeDefinitions = new List<AttributeDefinition>();

attributeDefinitions.Add(new AttributeDefinition()
{
    AttributeName = "Artist",
    AttributeType = "S"
});

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "SongTitle",
     AttributeType = "S"
 });

attributeDefinitions.Add(new AttributeDefinition()
 {
     AttributeName = "AlbumTitle",
     AttributeType = "S"
 });

createTableRequest.AttributeDefinitions = attributeDefinitions;

//KeySchema
List<KeySchemaElement> tableKeySchema = new List<KeySchemaElement>();

tableKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
tableKeySchema.Add(new KeySchemaElement() { AttributeName = "SongTitle", KeyType = "RANGE" });  //Sort key

createTableRequest.KeySchema = tableKeySchema;

List<KeySchemaElement> indexKeySchema = new List<KeySchemaElement>();
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "Artist", KeyType = "HASH" });  //Partition key
indexKeySchema.Add(new KeySchemaElement() { AttributeName = "AlbumTitle", KeyType = "RANGE" });  //Sort key

Projection projection = new Projection() { ProjectionType = "INCLUDE" };

List<string> nonKeyAttributes = new List<string>();
nonKeyAttributes.Add("Genre");
nonKeyAttributes.Add("Year");
projection.NonKeyAttributes = nonKeyAttributes;

LocalSecondaryIndex localSecondaryIndex = new LocalSecondaryIndex()
{
    IndexName = "AlbumTitleIndex",
    KeySchema = indexKeySchema,
    Projection = projection
};

List<LocalSecondaryIndex> localSecondaryIndexes = new List<LocalSecondaryIndex>();
localSecondaryIndexes.Add(localSecondaryIndex);
createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

CreateTableResponse result = client.CreateTable(createTableRequest);
Console.WriteLine(result.CreateTableResult.TableDescription.TableName);
Console.WriteLine(result.CreateTableResult.TableDescription.TableStatus);
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。

## 描述具有本地二级索引的表
<a name="LSILowLevelDotNet.DescribeTableWithIndex"></a>

要获取表上有关本地二级索引的信息，请使用 `DescribeTable` API。对于每个索引，您都可以查看其名称、键架构和投影的属性。

以下介绍使用 .NET 低级 API 访问表中的本地二级索引信息的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `DescribeTableRequest` 类实例，以提供请求信息。您必须提供表名称。

1. 以参数形式提供请求对象，运行 `describeTable` 方法。

以下 C\$1 代码示例演示了上述步骤。

**Example**  

```
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
string tableName = "Music";

DescribeTableResponse response = client.DescribeTable(new DescribeTableRequest() { TableName = tableName });
List<LocalSecondaryIndexDescription> localSecondaryIndexes =
    response.DescribeTableResult.Table.LocalSecondaryIndexes;

// This code snippet will work for multiple indexes, even though
// there is only one index in this example.
foreach (LocalSecondaryIndexDescription lsiDescription in localSecondaryIndexes)
{
    Console.WriteLine("Info for index " + lsiDescription.IndexName + ":");

    foreach (KeySchemaElement kse in lsiDescription.KeySchema)
    {
        Console.WriteLine("\t" + kse.AttributeName + ": key type is " + kse.KeyType);
    }

    Projection projection = lsiDescription.Projection;

    Console.WriteLine("\tThe projection type is: " + projection.ProjectionType);

    if (projection.ProjectionType.ToString().Equals("INCLUDE"))
    {
        Console.WriteLine("\t\tThe non-key projected attributes are:");

        foreach (String s in projection.NonKeyAttributes)
        {
            Console.WriteLine("\t\t" + s);
        }

    }
}
```

## 查询本地二级索引
<a name="LSILowLevelDotNet.QueryAnIndex"></a>

您可以对本地二级索引使用 `Query`，基本上与对表执行 `Query` 操作相同。您需要指定索引名称、索引排序键的查询条件以及要返回的属性。在本示例中，索引为 `AlbumTitleIndex`，索引排序键为 `AlbumTitle`。

要返回的只包含投影到索引的属性。您也可以修改此查询，让返回结果中也包含非键属性，但是这样会导致表抓取活动的成本相对较高的。有关表抓取的更多信息，请参阅 [属性投影](LSI.md#LSI.Projections)。

以下是使用 .NET 低级别 API 查询本地二级索引的步骤。

1. 创建 `AmazonDynamoDBClient` 类的实例。

1. 创建 `QueryRequest` 类实例，以提供请求信息。

1. 以参数形式提供请求对象，运行 `query` 方法。

以下 C\$1 代码示例演示了上述步骤。

**Example**  

```
QueryRequest queryRequest = new QueryRequest
{
    TableName = "Music",
    IndexName = "AlbumTitleIndex",
    Select = "ALL_ATTRIBUTES",
    ScanIndexForward = true,
    KeyConditionExpression = "Artist = :v_artist and AlbumTitle = :v_title",
    ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
    {
        {":v_artist",new AttributeValue {S = "Acme Band"}},
        {":v_title",new AttributeValue {S = "Songs About Life"}}
    },
};

QueryResponse response = client.Query(queryRequest);

foreach (var attribs in response.Items)
{
    foreach (var attrib in attribs)
    {
        Console.WriteLine(attrib.Key + " ---> " + attrib.Value.S);
    }
    Console.WriteLine();
}
```

# 示例：使用 适用于 .NET 的 AWS SDK 低级 API 的本地二级索引
<a name="LSILowLevelDotNet.Example"></a>

以下 C\$1 代码示例显示如何在 Amazon DynamoDB 中处理本地二级索引。示例创建名为 `CustomerOrders` 的表，其分区键为 `CustomerId`，排序键为 `OrderId`。此表上有两个本地二级索引：
+ `OrderCreationDateIndex` — 排序键是 `OrderCreationDate`，并将以下属性投影到索引：
  + `ProductCategory`
  + `ProductName`
  + `OrderStatus`
  + `ShipmentTrackingId`
+ `IsOpenIndex` — 排序键是 `IsOpen`，并将表的所有属性投影到索引。

创建 `CustomerOrders` 表后，程序为该表加载表示客户订单的数据。然后使用本地二级索引查询这些数据。最后，程序会删除 `CustomerOrders` 表。

有关测试以下示例的分步说明，请参阅 [.NET 代码示例](CodeSamples.DotNet.md)。

**Example**  

```
using System;
using System.Collections.Generic;
using System.Linq;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.SecurityToken;

namespace com.amazonaws.codesamples
{
    class LowLevelLocalSecondaryIndexExample
    {
        private static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
        private static string tableName = "CustomerOrders";

        static void Main(string[] args)
        {
            try
            {
                CreateTable();
                LoadData();

                Query(null);
                Query("IsOpenIndex");
                Query("OrderCreationDateIndex");

                DeleteTable(tableName);

                Console.WriteLine("To continue, press Enter");
                Console.ReadLine();
            }
            catch (AmazonDynamoDBException e) { Console.WriteLine(e.Message); }
            catch (AmazonServiceException e) { Console.WriteLine(e.Message); }
            catch (Exception e) { Console.WriteLine(e.Message); }
        }

        private static void CreateTable()
        {
            var createTableRequest =
                new CreateTableRequest()
                {
                    TableName = tableName,
                    ProvisionedThroughput =
                    new ProvisionedThroughput()
                    {
                        ReadCapacityUnits = (long)1,
                        WriteCapacityUnits = (long)1
                    }
                };

            var attributeDefinitions = new List<AttributeDefinition>()
        {
            // Attribute definitions for table primary key
            { new AttributeDefinition() {
                  AttributeName = "CustomerId", AttributeType = "S"
              } },
            { new AttributeDefinition() {
                  AttributeName = "OrderId", AttributeType = "N"
              } },
            // Attribute definitions for index primary key
            { new AttributeDefinition() {
                  AttributeName = "OrderCreationDate", AttributeType = "N"
              } },
            { new AttributeDefinition() {
                  AttributeName = "IsOpen", AttributeType = "N"
              }}
        };

            createTableRequest.AttributeDefinitions = attributeDefinitions;

            // Key schema for table
            var tableKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                  //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderId", KeyType = "RANGE"
              } }                                                //Sort key
        };

            createTableRequest.KeySchema = tableKeySchema;

            var localSecondaryIndexes = new List<LocalSecondaryIndex>();

            // OrderCreationDateIndex
            LocalSecondaryIndex orderCreationDateIndex = new LocalSecondaryIndex()
            {
                IndexName = "OrderCreationDateIndex"
            };

            // Key schema for OrderCreationDateIndex
            var indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              } },                                                    //Partition key
            { new KeySchemaElement() {
                  AttributeName = "OrderCreationDate", KeyType = "RANGE"
              } }                                                            //Sort key
        };

            orderCreationDateIndex.KeySchema = indexKeySchema;

            // Projection (with list of projected attributes) for
            // OrderCreationDateIndex
            var projection = new Projection()
            {
                ProjectionType = "INCLUDE"
            };

            var nonKeyAttributes = new List<string>()
        {
            "ProductCategory",
            "ProductName"
        };
            projection.NonKeyAttributes = nonKeyAttributes;

            orderCreationDateIndex.Projection = projection;

            localSecondaryIndexes.Add(orderCreationDateIndex);

            // IsOpenIndex
            LocalSecondaryIndex isOpenIndex
                = new LocalSecondaryIndex()
                {
                    IndexName = "IsOpenIndex"
                };

            // Key schema for IsOpenIndex
            indexKeySchema = new List<KeySchemaElement>()
        {
            { new KeySchemaElement() {
                  AttributeName = "CustomerId", KeyType = "HASH"
              }},                                                     //Partition key
            { new KeySchemaElement() {
                  AttributeName = "IsOpen", KeyType = "RANGE"
              }}                                                  //Sort key
        };

            // Projection (all attributes) for IsOpenIndex
            projection = new Projection()
            {
                ProjectionType = "ALL"
            };

            isOpenIndex.KeySchema = indexKeySchema;
            isOpenIndex.Projection = projection;

            localSecondaryIndexes.Add(isOpenIndex);

            // Add index definitions to CreateTable request
            createTableRequest.LocalSecondaryIndexes = localSecondaryIndexes;

            Console.WriteLine("Creating table " + tableName + "...");
            client.CreateTable(createTableRequest);
            WaitUntilTableReady(tableName);
        }

        public static void Query(string indexName)
        {
            Console.WriteLine("\n***********************************************************\n");
            Console.WriteLine("Querying table " + tableName + "...");

            QueryRequest queryRequest = new QueryRequest()
            {
                TableName = tableName,
                ConsistentRead = true,
                ScanIndexForward = true,
                ReturnConsumedCapacity = "TOTAL"
            };


            String keyConditionExpression = "CustomerId = :v_customerId";
            Dictionary<string, AttributeValue> expressionAttributeValues = new Dictionary<string, AttributeValue> {
            {":v_customerId", new AttributeValue {
                 S = "bob@example.com"
             }}
        };


            if (indexName == "IsOpenIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that are open.");
                Console.WriteLine("Only a user-specified list of attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and IsOpen = :v_isOpen";
                expressionAttributeValues.Add(":v_isOpen", new AttributeValue
                {
                    N = "1"
                });

                // ProjectionExpression
                queryRequest.ProjectionExpression = "OrderCreationDate, ProductCategory, ProductName, OrderStatus";
            }
            else if (indexName == "OrderCreationDateIndex")
            {
                Console.WriteLine("\nUsing index: '" + indexName
                          + "': Bob's orders that were placed after 01/31/2013.");
                Console.WriteLine("Only the projected attributes are returned\n");
                queryRequest.IndexName = indexName;

                keyConditionExpression += " and OrderCreationDate > :v_Date";
                expressionAttributeValues.Add(":v_Date", new AttributeValue
                {
                    N = "20130131"
                });

                // Select
                queryRequest.Select = "ALL_PROJECTED_ATTRIBUTES";
            }
            else
            {
                Console.WriteLine("\nNo index: All of Bob's orders, by OrderId:\n");
            }
            queryRequest.KeyConditionExpression = keyConditionExpression;
            queryRequest.ExpressionAttributeValues = expressionAttributeValues;

            var result = client.Query(queryRequest);
            var items = result.Items;
            foreach (var currentItem in items)
            {
                foreach (string attr in currentItem.Keys)
                {
                    if (attr == "OrderId" || attr == "IsOpen"
                        || attr == "OrderCreationDate")
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].N);
                    }
                    else
                    {
                        Console.WriteLine(attr + "---> " + currentItem[attr].S);
                    }
                }
                Console.WriteLine();
            }
            Console.WriteLine("\nConsumed capacity: " + result.ConsumedCapacity.CapacityUnits + "\n");
        }

        private static void DeleteTable(string tableName)
        {
            Console.WriteLine("Deleting table " + tableName + "...");
            client.DeleteTable(new DeleteTableRequest()
            {
                TableName = tableName
            });
            WaitForTableToBeDeleted(tableName);
        }

        public static void LoadData()
        {
            Console.WriteLine("Loading data into table " + tableName + "...");

            Dictionary<string, AttributeValue> item = new Dictionary<string, AttributeValue>();

            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130101"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "The Great Outdoors"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            PutItemRequest putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Bike"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Super Mountain"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "ORDER RECEIVED"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "alice@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130304"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "A Quiet Interlude"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "176493"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "1"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130111"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Movie"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Calm Before The Storm"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "SHIPPING DELAY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "859323"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "2"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130124"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "E-Z Listening"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "756943"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "3"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130221"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Music"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Symphony 9"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "645193"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "4"
            };
            item["IsOpen"] = new AttributeValue
            {
                N = "1"
            };
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130222"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Hardware"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Extra Heavy Hammer"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "PACKING ITEMS"
            };
            /* no ShipmentTrackingId attribute */
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "5"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130309"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Book"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "How To Cook"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "IN TRANSIT"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "440185"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "6"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130318"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Luggage"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "Really Big Suitcase"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "DELIVERED"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "893927"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);

            item = new Dictionary<string, AttributeValue>();
            item["CustomerId"] = new AttributeValue
            {
                S = "bob@example.com"
            };
            item["OrderId"] = new AttributeValue
            {
                N = "7"
            };
            /* no IsOpen attribute */
            item["OrderCreationDate"] = new AttributeValue
            {
                N = "20130324"
            };
            item["ProductCategory"] = new AttributeValue
            {
                S = "Golf"
            };
            item["ProductName"] = new AttributeValue
            {
                S = "PGA Pro II"
            };
            item["OrderStatus"] = new AttributeValue
            {
                S = "OUT FOR DELIVERY"
            };
            item["ShipmentTrackingId"] = new AttributeValue
            {
                S = "383283"
            };
            putItemRequest = new PutItemRequest
            {
                TableName = tableName,
                Item = item,
                ReturnItemCollectionMetrics = "SIZE"
            };
            client.PutItem(putItemRequest);
        }

        private static void WaitUntilTableReady(string tableName)
        {
            string status = null;
            // Let us wait until table is created. Call DescribeTable.
            do
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                    status = res.Table.TableStatus;
                }
                catch (ResourceNotFoundException)
                {
                    // DescribeTable is eventually consistent. So you might
                    // get resource not found. So we handle the potential exception.
                }
            } while (status != "ACTIVE");
        }

        private static void WaitForTableToBeDeleted(string tableName)
        {
            bool tablePresent = true;

            while (tablePresent)
            {
                System.Threading.Thread.Sleep(5000); // Wait 5 seconds.
                try
                {
                    var res = client.DescribeTable(new DescribeTableRequest
                    {
                        TableName = tableName
                    });

                    Console.WriteLine("Table name: {0}, status: {1}",
                              res.Table.TableName,
                              res.Table.TableStatus);
                }
                catch (ResourceNotFoundException)
                {
                    tablePresent = false;
                }
            }
        }
    }
}
```

# 在 DynamoDB AWS CLI 中使用本地二级索引
<a name="LCICli"></a>

您可以使用 AWS CLI 创建具有一个或多个本地二级索引的 Amazon DynamoDB 表、描述表中的索引，以及使用索引执行查询。

**Topics**
+ [创建具有本地二级索引的表](#LCICli.CreateTableWithIndex)
+ [描述具有本地二级索引的表](#LCICli.DescribeTableWithIndex)
+ [查询本地二级索引](#LCICli.QueryAnIndex)

## 创建具有本地二级索引的表
<a name="LCICli.CreateTableWithIndex"></a>

本地二级索引必须在您创建表的同时创建。为此，请使用 `create-table` 参数并为一个或多个本地二级索引提供您的规范。以下示例创建一个包含音乐精选中歌曲信息的表 (`Music`)。分区键为 `Artist`，排序键为 `SongTitle`。`AlbumTitle` 属性的二级索引 `AlbumTitleIndex` 可以按专辑名称进行查询。

```
aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions AttributeName=Artist,AttributeType=S AttributeName=SongTitle,AttributeType=S \
        AttributeName=AlbumTitle,AttributeType=S  \
    --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=10,WriteCapacityUnits=5 \
    --local-secondary-indexes \
        "[{\"IndexName\": \"AlbumTitleIndex\",
        \"KeySchema\":[{\"AttributeName\":\"Artist\",\"KeyType\":\"HASH\"},
                      {\"AttributeName\":\"AlbumTitle\",\"KeyType\":\"RANGE\"}],
        \"Projection\":{\"ProjectionType\":\"INCLUDE\",  \"NonKeyAttributes\":[\"Genre\", \"Year\"]}}]"
```

您必须等待 DynamoDB 创建该表并将表的状态设置为 `ACTIVE`。然后，您就可以开始在表中添加数据项目。您可以使用 [describe-table](https://docs.aws.amazon.com/cli/latest/reference/dynamodb/describe-table.html) 确定表创建的状态。

## 描述具有本地二级索引的表
<a name="LCICli.DescribeTableWithIndex"></a>

要获取表上有关本地二级索引的信息，请使用 `describe-table` 参数。对于每个索引，您都可以查看其名称、键架构和投影的属性。

```
aws dynamodb describe-table --table-name Music
```

## 查询本地二级索引
<a name="LCICli.QueryAnIndex"></a>

您可以对本地二级索引使用 `query` 操作，基本上与对表执行 `query` 操作相同。您需要指定索引名称、索引排序键的查询条件以及要返回的属性。在本示例中，索引为 `AlbumTitleIndex`，索引排序键为 `AlbumTitle`。

要返回的只包含投影到索引的属性。您也可以修改此查询，让返回结果中也包含非键属性，但是这样会导致表抓取活动的成本相对较高的。有关表获取的更多信息，请参阅 [属性投影](LSI.md#LSI.Projections)。

```
aws dynamodb query \
    --table-name Music \
    --index-name AlbumTitleIndex \
    --key-condition-expression "Artist = :v_artist and AlbumTitle = :v_title" \
    --expression-attribute-values  '{":v_artist":{"S":"Acme Band"},":v_title":{"S":"Songs About Life"} }'
```

# 使用 DynamoDB 事务管理复杂工作流
<a name="transactions"></a>

Amazon DynamoDB Transactions 简化了开发人员对表内和表间的多个项目进行“要么全有要么全无”的协调式更改的体验。在 DynamoDB 中，事务提供原子性、一致性、隔离性和持久性 (ACID)，帮助您维护应用程序中的数据正确性。

您可以使用 DynamoDB 事务读取和写入 API 管理复杂的业务工作流，这些业务流需要作为单个“要么全有要么全无”操作添加、更新或删除多个项目。例如，当玩家在游戏中交换物品或在游戏中购买物品时，视频游戏开发人员可以确保他们的个人资料得到正确更新。

使用事务写入 API，您可以分组多个 `Put`、`Update`、`Delete` 和 `ConditionCheck` 操作。您可将多个操作作为单个 `TransactWriteItems` 操作提交，然后整体成功或失败。这同样适用于多个 `Get` 操作，您可以对它们进行分组并作为单个 `TransactGetItems` 操作提交。

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

要开始使用 DynamoDB 事务，请下载最新的 AWS SDK 或 AWS Command Line Interface (AWS CLI)。然后，按照 [DynamoDB 事务示例](transaction-example.md) 中的说明操作。

下面各个部分详细概述事务 API 以及如何在 DynamoDB 中使用它们。

**Topics**
+ [工作原理](transaction-apis.md)
+ [将 IAM 与事务结合使用](transaction-apis-iam.md)
+ [代码示例](transaction-example.md)

# Amazon DynamoDB Transactions：工作原理
<a name="transaction-apis"></a>

借助 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
<a name="transaction-apis-txwriteitems"></a>

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

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

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

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

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

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

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

### 幂等性
<a name="transaction-apis-txwriteitems-idempotency"></a>

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

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

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

### 写入错误处理
<a name="transaction-apis-txwriteitems-errors"></a>

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

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

## TransactGetItems API
<a name="transaction-apis-txgetitems"></a>

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

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

### 读取错误处理
<a name="transaction-apis-txgetitems-errors"></a>

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

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

## DynamoDB 事务的隔离级别
<a name="transaction-isolation"></a>

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

### 可序列化
<a name="transaction-isolation-serializable"></a>

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

以下操作类型之间具有可序列化隔离：
+ 在任何事务操作与任何标准写入操作（`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) 读取。

### 读取已提交
<a name="transaction-isolation-read-committed"></a>

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

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

### 操作摘要
<a name="transaction-isolation-table"></a>

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


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

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

## DynamoDB 中的事务冲突处理
<a name="transaction-conflict-handling"></a>

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

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

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

## 在 DynamoDB Accelerator（DAX）中使用事务 API
<a name="transaction-apis-dax"></a>

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

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

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

## 事务的容量管理
<a name="transaction-capacity-handling"></a>

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

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

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

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

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

## 事务的最佳实践
<a name="transaction-best-practices"></a>

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

## 将事务 API 与全局表结合使用
<a name="transaction-integration"></a>

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

## DynamoDB 事务与 AWSLabs 事务客户端库
<a name="transaction-vs-library"></a>

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

# 将 IAM 与 DynamoDB 事务结合使用
<a name="transaction-apis-iam"></a>

您可以使用 AWS Identity and Access Management (IAM) 限制事务操作可以在 Amazon DynamoDB 中执行的操作。有关在 DynamoDB 中使用 IAM 策略的更多信息，请参阅。[适用于 DynamoDB 的基于身份的策略](security_iam_service-with-iam.md#security_iam_service-with-iam-id-based-policies)

`Put`、`Update`、`Delete` 和 `Get` 操作的权限由用于基础 `PutItem`、`UpdateItem`、`DeleteItem` 和 `GetItem` 操作的权限管控。对于 `ConditionCheck` 操作，您可以在 IAM 策略中使用 `dynamodb:ConditionCheckItem` 权限。

以下是可用于配置 DynamoDB 事务的 IAM 策略的示例。

## 示例 1：允许事务操作
<a name="tx-policy-example-1"></a>

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:ConditionCheckItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:*:*:table/table04"
            ]
        }
    ]
}
```

------

## 示例 2：仅允许事务操作
<a name="tx-policy-example-2"></a>

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:ConditionCheckItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:*:*:table/table04"
            ],
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "dynamodb:EnclosingOperation": [
                        "TransactWriteItems",
                        "TransactGetItems"
                    ]
                }
            }
        }
    ]
}
```

------

## 示例 3：允许非事务读取和写入，并阻止事务读取和写入
<a name="tx-policy-example-3"></a>

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [
                "dynamodb:ConditionCheckItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:*:*:table/table04"
            ],
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "dynamodb:EnclosingOperation": [
                        "TransactWriteItems",
                        "TransactGetItems"
                    ]
                }
            }
        },
        {
            "Effect": "Allow",
             "Action": [
                 "dynamodb:PutItem",
                 "dynamodb:DeleteItem",
                 "dynamodb:GetItem",
                 "dynamodb:UpdateItem"
             ],
             "Resource": [
                 "arn:aws:dynamodb:*:*:table/table04"
             ]
         }
    ]
}
```

------

## 示例 4：阻止在 ConditionCheck 失败时返回信息
<a name="tx-policy-example-4"></a>

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:ConditionCheckItem",
                "dynamodb:PutItem",
                "dynamodb:UpdateItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/table01",
            "Condition": {
                "StringEqualsIfExists": {
                    "dynamodb:ReturnValues": "NONE"
                }
            }
        }
    ]
}
```

------

# DynamoDB 事务示例
<a name="transaction-example"></a>

作为 Amazon DynamoDB Transactions 可能有用的情况示例，请考虑此适用于在线商城的 Java 应用程序示例。

该应用程序在后端有三个 DynamoDB 表：
+ `Customers`— 此表存储有关商城买家的详细信息。它的主键是 `CustomerId` 唯一标识符。
+ `ProductCatalog`— 此表存储有关商品在商城中销售的价格和供货情况等详细信息。它的主键是 `ProductId` 唯一标识符。
+ `Orders`— 此表存储有关来自市场的订单的详细信息。它的主键是 `OrderId` 唯一标识符。

## 下订单
<a name="transaction-example-write-order"></a>

以下代码片段说明了如何使用 DynamoDB 事务来协调创建和处理订单所需的多个步骤。使用单个全有或全无操作可确保如果事务的任何部分失败，事务中不会运行任何操作，也不会进行任何更改。

在此示例中，您设置来自 `customerId` 为 `09e8e9c8-ec48` 的客户订单。然后，您可以使用以下简单的订单处理工作流将其作为单个事务处理运行：

1. 确定客户 ID 是否有效。

1. 确保产品是 `IN_STOCK`，并将产品状态更新为 `SOLD`。

1. 确保订单不存在，然后创建订单。

### 验证客户
<a name="transaction-example-order-part-a"></a>

首先，定义一个操作以验证 `customerId` 等于 `09e8e9c8-ec48` 的客户存在于客户表中。

```
final String CUSTOMER_TABLE_NAME = "Customers";
final String CUSTOMER_PARTITION_KEY = "CustomerId";
final String customerId = "09e8e9c8-ec48";
final HashMap<String, AttributeValue> customerItemKey = new HashMap<>();
customerItemKey.put(CUSTOMER_PARTITION_KEY, new AttributeValue(customerId));

ConditionCheck checkCustomerValid = new ConditionCheck()
    .withTableName(CUSTOMER_TABLE_NAME)
    .withKey(customerItemKey)
    .withConditionExpression("attribute_exists(" + CUSTOMER_PARTITION_KEY + ")");
```

### 更新产品状态
<a name="transaction-example-order-part-b"></a>

接下来，定义一个操作，如果产品状态当前设置为 `IN_STOCK` 的条件为 `true`，则将产品状态更新为 `SOLD`。设置 `ReturnValuesOnConditionCheckFailure` 参数，如果项目的产品状态属性不等于 `IN_STOCK`，则返回项目。

```
final String PRODUCT_TABLE_NAME = "ProductCatalog";
final String PRODUCT_PARTITION_KEY = "ProductId";
HashMap<String, AttributeValue> productItemKey = new HashMap<>();
productItemKey.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));

Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
expressionAttributeValues.put(":new_status", new AttributeValue("SOLD"));
expressionAttributeValues.put(":expected_status", new AttributeValue("IN_STOCK"));

Update markItemSold = new Update()
    .withTableName(PRODUCT_TABLE_NAME)
    .withKey(productItemKey)
    .withUpdateExpression("SET ProductStatus = :new_status")
    .withExpressionAttributeValues(expressionAttributeValues)
    .withConditionExpression("ProductStatus = :expected_status")
    .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
```

### 创建订单
<a name="transaction-example-order-part-c"></a>

最后，只要不存在具有 `OrderId` 的订单，则创建订单。

```
final String ORDER_PARTITION_KEY = "OrderId";
final String ORDER_TABLE_NAME = "Orders";

HashMap<String, AttributeValue> orderItem = new HashMap<>();
orderItem.put(ORDER_PARTITION_KEY, new AttributeValue(orderId));
orderItem.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));
orderItem.put(CUSTOMER_PARTITION_KEY, new AttributeValue(customerId));
orderItem.put("OrderStatus", new AttributeValue("CONFIRMED"));
orderItem.put("OrderTotal", new AttributeValue("100"));

Put createOrder = new Put()
    .withTableName(ORDER_TABLE_NAME)
    .withItem(orderItem)
    .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
    .withConditionExpression("attribute_not_exists(" + ORDER_PARTITION_KEY + ")");
```

### 运行事务
<a name="transaction-example-order-part-d"></a>

以下示例说明如何运行以前定义为单个全有或全无操作的操作。

```
    Collection<TransactWriteItem> actions = Arrays.asList(
        new TransactWriteItem().withConditionCheck(checkCustomerValid),
        new TransactWriteItem().withUpdate(markItemSold),
        new TransactWriteItem().withPut(createOrder));

    TransactWriteItemsRequest placeOrderTransaction = new TransactWriteItemsRequest()
        .withTransactItems(actions)
        .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);

    // Run the transaction and process the result.
    try {
        client.transactWriteItems(placeOrderTransaction);
        System.out.println("Transaction Successful");

    } catch (ResourceNotFoundException rnf) {
        System.err.println("One of the table involved in the transaction is not found" + rnf.getMessage());
    } catch (InternalServerErrorException ise) {
        System.err.println("Internal Server Error" + ise.getMessage());
    } catch (TransactionCanceledException tce) {
        System.out.println("Transaction Canceled " + tce.getMessage());
    }
```

## 阅读订单详细信息
<a name="transaction-example-read-order"></a>

以下示例演示了如何以事务方式读取 `Orders` 和 `ProductCatalog` 表中已完成的订单。

```
HashMap<String, AttributeValue> productItemKey = new HashMap<>();
productItemKey.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));

HashMap<String, AttributeValue> orderKey = new HashMap<>();
orderKey.put(ORDER_PARTITION_KEY, new AttributeValue(orderId));

Get readProductSold = new Get()
    .withTableName(PRODUCT_TABLE_NAME)
    .withKey(productItemKey);
Get readCreatedOrder = new Get()
    .withTableName(ORDER_TABLE_NAME)
    .withKey(orderKey);

Collection<TransactGetItem> getActions = Arrays.asList(
    new TransactGetItem().withGet(readProductSold),
    new TransactGetItem().withGet(readCreatedOrder));

TransactGetItemsRequest readCompletedOrder = new TransactGetItemsRequest()
    .withTransactItems(getActions)
    .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);

// Run the transaction and process the result.
try {
    TransactGetItemsResult result = client.transactGetItems(readCompletedOrder);
    System.out.println(result.getResponses());
} catch (ResourceNotFoundException rnf) {
    System.err.println("One of the table involved in the transaction is not found" + rnf.getMessage());
} catch (InternalServerErrorException ise) {
    System.err.println("Internal Server Error" + ise.getMessage());
} catch (TransactionCanceledException tce) {
    System.err.println("Transaction Canceled" + tce.getMessage());
}
```

# 将更改数据捕获与 Amazon DynamoDB 结合使用
<a name="streamsmain"></a>

当存储在 DynamoDB 表中的项目发生更改时，许多应用程序都会因能够捕获此类更改而受益。下面是一些使用场景示例：
+ 一个热门移动应用程序以每秒数千次更新的速率修改 DynamoDB 表中的数据。第二个应用程序捕获和存储有关这些更新的数据，并提供针对该移动应用程序的近乎实时用量指标。
+ 财务应用程序修改 DynamoDB 表中的股票市场数据。并行运行的不同应用程序实时跟踪这些变化，计算风险价值，并根据股票价格变动自动重新平衡投资组合。
+ 运输车辆和工业设备中的传感器将数据发送到 DynamoDB 表中。不同的应用程序监控性能并在检测到问题时发送消息警报，通过应用机器学习算法预测任何潜在缺陷，并将数据压缩和存档到 Amazon Simple Storage Service (Amazon S3)。
+ 一旦某个好友上传新图片，一个应用程序就会自动向群组中的所有好友的移动设备发送通知。
+ 一个新客户将数据添加到 DynamoDB 表。此事件调用另一个应用程序，以便向该新客户发送欢迎电子邮件。

DynamoDB 支持近实时流式处理项目级别更改数据捕获记录。可以构建使用这些流并根据内容采取操作的应用程序。

**注意**  
不支持向 DynamoDB Streams 添加标签以及将[基于属性的访问权限控制（ABAC）](access-control-resource-based.md)和 DynamoDB Streams 结合使用。

以下视频将向您介绍更改数据捕获概念。

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/VVv_-mZ5Ge8/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/VVv_-mZ5Ge8)


**Topics**
+ [更改数据捕获的流式传输选项](#streamsmain.choose)
+ [使用 Kinesis Data Streams 捕获 DynamoDB 的更改](kds.md)
+ [将更改数据捕获用于 DynamoDB Streams](Streams.md)

## 更改数据捕获的流式传输选项
<a name="streamsmain.choose"></a>

DynamoDB 提供了两个用于更改数据捕获的流模型：Kinesis Data Streams for DynamoDB 和 DynamoDB Streams。

为了帮助选择适合应用程序的解决方案，下表总结了每个流式处理模型的功能。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/streamsmain.html)

您可以在同一 DynamoDB 表上启用两个流式处理模型。

下面的视频详细介绍了这两个选项之间的差异。

[![AWS Videos](http://img.youtube.com/vi/https://www.youtube.com/embed/UgG17Wh2y0g/0.jpg)](http://www.youtube.com/watch?v=https://www.youtube.com/embed/UgG17Wh2y0g)


# 使用 Kinesis Data Streams 捕获 DynamoDB 的更改
<a name="kds"></a>

您可以使用 Amazon Kinesis Data Streams 捕获 Amazon DynamoDB 的更改。

Kinesis Data Streams 捕获任何 DynamoDB 表中的项目级别修改，并将它们复制到 [Kinesis Data Streams](https://docs.aws.amazon.com/streams/latest/dev/introduction.html)。您的应用程序可以访问此数据流，近实时查看项目级别的更改。您可以每小时持续捕获和存储 TB 级的数据。您可以利用更长的数据保留时间，还可以借助增强的扇出功能同时访问两个或更多下游应用程序。其他优势包括额外的审计和安全透明度。

Kinesis Data Streams 还可让您访问 [Amazon Data Firehose](https://docs.aws.amazon.com/firehose/latest/dev/what-is-this-service.html) 和[适用于 Apache Flink 的亚马逊托管服务](https://docs.aws.amazon.com/kinesisanalytics/latest/dev/what-is.html)。这些服务可帮助您构建应用程序来支持实时控制面板、生成警报、实施动态定价和广告以及实现复杂的数据分析和机器学习算法。

**注意**  
为 DynamoDB 使用 Kinesis 数据流受数据流的 [Kinesis Data Streams 定价](https://aws.amazon.com/kinesis/data-streams/pricing/)和源表的 [DynamoDB 定价](https://aws.amazon.com/dynamodb/pricing/)影响。

要使用控制台、AWS CLI 或 Java SDK 在 DynamoDB 表上启用 Kinesis 流式传输，请参阅[适用于 Amazon DynamoDB 的 Kinesis Data Streams 入门](kds_gettingstarted.md)。

**Topics**
+ [Kinesis Data Streams 如何与 DynamoDB 结合使用](#kds_howitworks)
+ [适用于 Amazon DynamoDB 的 Kinesis Data Streams 入门](kds_gettingstarted.md)
+ [将 DynamoDB Streams 中的分片和指标与 Kinesis Data Streams 配合使用](kds_using-shards-and-metrics.md)
+ [使用适用于 Amazon Kinesis Data Streams 和 Amazon DynamoDB 的 IAM 策略](kds_iam.md)

## Kinesis Data Streams 如何与 DynamoDB 结合使用
<a name="kds_howitworks"></a>

为 DynamoDB 表启用 Kinesis 数据流时，该表将发送一条数据记录，其中捕获该表数据的任何更改。此数据记录包括：
+ 最近创建、更新或删除任何项目的具体时间
+ 该项目的主键
+ 修改前记录的快照
+ 修改后记录的快照 

将近乎实时地捕获并发布这些数据记录。将它们写入 Kinesis 数据流后，就可以像任何其他记录一样读取它们。您可以使用 Kinesis 客户端库，使用 AWS Lambda，调用 Kinesis Data Streams API，以及使用其他连接的服务。有关更多信息，请参阅 Amazon Kinesis Data Streams 开发人员指南的[从 Amazon Kinesis Data Streams 读取数据](https://docs.aws.amazon.com/streams/latest/dev/building-consumers.html)。

数据的这些更改也是异步捕获的。Kinesis 对其正在进行流式传输的表没有性能影响。存储在 Kinesis 数据流中的流记录也会静态加密。有关更多信息，请参阅 [Amazon Kinesis Data Streams 数据保护](https://docs.aws.amazon.com/streams/latest/dev/server-side-encryption.html)。

Kinesis 数据流记录的显示顺序可能与项目更改发生时的顺序不同。同一项目通知也可能会在数据流中多次出现。您可以检查 `ApproximateCreationDateTime` 属性，以确定项目修改的发生顺序，并识别重复的记录。

当您启用 Kinesis 数据流作为 DynamoDB 表的流式传输目标时，您可以用毫秒或微秒为单位配置 `ApproximateCreationDateTime` 值的精度。默认情况下，`ApproximateCreationDateTime` 指示更改时间（以毫秒为单位）。此外，您可以在活动的流式传输目标上更改该值。更新后，写入 Kinesis 的流记录将具有所需精度的 `ApproximateCreationDateTime` 值。

写入 DynamoDB 的二进制值必须采用 [base64 编码格式](HowItWorks.NamingRulesDataTypes.md)进行编码。但是，当数据记录写入 Kinesis 数据流时，这些编码的二进制值将再次使用 base64 编码进行编码。从 Kinesis 数据流读取这些记录时，为了检索原始二进制值，应用程序必须对这些值进行两次解码。

DynamoDB 会对在更改数据捕获单元中使用 Kinesis Data Streams 收取费用。每个项目的 1KB 更改计为一个更改数据捕获单元。每个项目的更改 KB 数是由写入数据流的项目的“之前”和“之后”镜像中较大者计算得出，期间使用与[写入操作的容量单位消耗](read-write-operations.md#write-operation-consumption)相同的逻辑。您无需为更改数据捕获单元预置容量吞吐量，类似于 DynamoDB [按需](capacity-mode.md#capacity-mode-on-demand)模式的工作原理。

### 为 DynamoDB 表启用 Kinesis 数据流
<a name="kds_howitworks.enabling"></a>

可以使用 AWS 管理控制台、AWS SDK 或 AWS Command Line Interface (AWS CLI)，启用或禁用从现有 DynamoDB 表流式传输到 Kinesis。
+ 您只能从 DynamoDB 流式传输到从同一 AWS 账户和 AWS 区域中的 Kinesis Data Streams 作为您的表。
+ 您只能将数据从 DynamoDB 表流式传输到一个 Kinesis 数据流。

  

### 更改 DynamoDB 表上的 Kinesis Data Streams 目标
<a name="kds_howitworks.makingchanges"></a>

默认情况下，所有 Kinesis 数据流记录都包含一个 `ApproximateCreationDateTime` 属性。此属性表示创建每条记录的大致时间的时间戳（以毫秒为单位）。您可以使用 [https://console.aws.amazon.com/kinesis](https://console.aws.amazon.com/kinesis)、SDK 或 AWS CLI 更改这些值的精度 

# 适用于 Amazon DynamoDB 的 Kinesis Data Streams 入门
<a name="kds_gettingstarted"></a>

本节介绍了如何通过 Amazon DynamoDB 控制台、AWS Command Line Interface（AWS CLI）和 API 将 Kinesis Data Streams 用于 Amazon DynamoDB 表。

## 创建有效的 Amazon Kinesis 数据流
<a name="kds_gettingstarted.making-changes"></a>

所有这些示例都使用 [DynamoDB 入门](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStartedDynamoDB.html)教程中创建的 `Music` DynamoDB 表。

要了解如何构建使用器并将 Kinesis Data Stream 连接到其他 AWS 服务，请参阅《Amazon Kinesis Data Streams 开发人员指南》**中的[从 Kinesis Data Streams 读取数据](https://docs.aws.amazon.com/streams/latest/dev/building-consumers.html)。

**注意**  
 当您首次使用 KDS 分片时，建议您将分片设置为根据使用模式横向和纵向扩展。在积累了更多有关使用模式的数据后，您可以调整数据流中的分片以与模式匹配。

------
#### [ Console ]

1. 登录到 AWS 管理控制台，然后打开 Kinesis 控制台：[https://console.aws.amazon.com/kinesis/](https://console.aws.amazon.com/kinesis/)。

1. 选择**创建数据流**并按照说明创建一个名为 `samplestream` 的流。

1. 打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 在控制台左侧的导航窗格中，选择**表**。

1. 选择 **Music** 表。

1. 选择**导出和流式传输**选项卡。

1. （可选）在 **Amazon Kinesis Data Streams 详细信息**下，您可以将记录时间戳精度从微秒（默认）更改为毫秒。

1. 从下拉列表选择 **samplestream**。

1. 选择**开启**按钮。

------
#### [ AWS CLI ]

1. 使用 [create-stream 命令](https://docs.aws.amazon.com/cli/latest/reference/kinesis/create-stream.html)创建名为 `samplestream` 的 Kinesis 流。

   ```
   aws kinesis create-stream --stream-name samplestream --shard-count 3 
   ```

   请参阅[Kinesis Data Streams 的分片管理注意事项](kds_using-shards-and-metrics.md#kds_using-shards-and-metrics.shardmanagment)，然后设置 Kinesis 数据流的分片数。

1. 使用 [describe-stream 命令](https://docs.aws.amazon.com/cli/latest/reference/kinesis/describe-stream.html)，检查 Kinesis 流是否处于活动状态并准备好使用。

   ```
   aws kinesis describe-stream --stream-name samplestream
   ```

1. 使用 DynamoDB `enable-kinesis-streaming-destination` 命令，在 DynamoDB 表上启用 Kinesis 数据流。将 `stream-arn` 值替换为上一步返回的 `describe-stream`。（可选）启用在每条记录上返回更精细（微秒）精度时间戳值的流式传输。

   启用微秒时间戳精度的流式传输：

   ```
   aws dynamodb enable-kinesis-streaming-destination \
     --table-name Music \
     --stream-arn arn:aws:kinesis:us-west-2:12345678901:stream/samplestream
     --enable-kinesis-streaming-configuration ApproximateCreationDateTimePrecision=MICROSECOND
   ```

   或者启用默认时间戳精度（毫秒）的流式传输：

   ```
   aws dynamodb enable-kinesis-streaming-destination \
     --table-name Music \
     --stream-arn arn:aws:kinesis:us-west-2:12345678901:stream/samplestream
   ```

1. 使用 DynamoDB `describe-kinesis-streaming-destination` 命令检查是否在表上激活 Kinesis 流。

   ```
   aws dynamodb describe-kinesis-streaming-destination --table-name Music
   ```

1. 使用 [DynamoDB 开发人员指南](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/getting-started-step-2.html)介绍的 `put-item` 命令，将数据写入 DynamoDB 表。

   ```
   aws dynamodb put-item \
       --table-name Music  \
       --item \
           '{"Artist": {"S": "No One You Know"}, "SongTitle": {"S": "Call Me Today"}, "AlbumTitle": {"S": "Somewhat Famous"}, "Awards": {"N": "1"}}'
   
   aws dynamodb put-item \
       --table-name Music \
       --item \
           '{"Artist": {"S": "Acme Band"}, "SongTitle": {"S": "Happy Day"}, "AlbumTitle": {"S": "Songs About Life"}, "Awards": {"N": "10"} }'
   ```

1. 使用 Kinesis [get-records](https://docs.aws.amazon.com/cli/latest/reference/kinesis/get-records.html) CLI 命令检索 Kinesis 流内容。然后使用以下代码片段来反序列化流内容。

   ```
   /**
    * Takes as input a Record fetched from Kinesis and does arbitrary processing as an example.
    */
   public void processRecord(Record kinesisRecord) throws IOException {
       ByteBuffer kdsRecordByteBuffer = kinesisRecord.getData();
       JsonNode rootNode = OBJECT_MAPPER.readTree(kdsRecordByteBuffer.array());
       JsonNode dynamoDBRecord = rootNode.get("dynamodb");
       JsonNode oldItemImage = dynamoDBRecord.get("OldImage");
       JsonNode newItemImage = dynamoDBRecord.get("NewImage");
       Instant recordTimestamp = fetchTimestamp(dynamoDBRecord);
   
       /**
        * Say for example our record contains a String attribute named "stringName" and we want to fetch the value
        * of this attribute from the new item image. The following code fetches this value.
        */
       JsonNode attributeNode = newItemImage.get("stringName");
       JsonNode attributeValueNode = attributeNode.get("S"); // Using DynamoDB "S" type attribute
       String attributeValue = attributeValueNode.textValue();
       System.out.println(attributeValue);
   }
   
   private Instant fetchTimestamp(JsonNode dynamoDBRecord) {
       JsonNode timestampJson = dynamoDBRecord.get("ApproximateCreationDateTime");
       JsonNode timestampPrecisionJson = dynamoDBRecord.get("ApproximateCreationDateTimePrecision");
       if (timestampPrecisionJson != null && timestampPrecisionJson.equals("MICROSECOND")) {
           return Instant.EPOCH.plus(timestampJson.longValue(), ChronoUnit.MICROS);
       }
       return Instant.ofEpochMilli(timestampJson.longValue());
   }
   ```

------
#### [ Java ]

1. 按照 Kinesis Data Streams 开发人员指南中的说明，使用 Java 创建一个名为 `samplestream` 的[https://docs.aws.amazon.com/streams/latest/dev/kinesis-using-sdk-java-create-stream.html](https://docs.aws.amazon.com/streams/latest/dev/kinesis-using-sdk-java-create-stream.html) Kinesis 数据流。

   请参阅[Kinesis Data Streams 的分片管理注意事项](kds_using-shards-and-metrics.md#kds_using-shards-and-metrics.shardmanagment)，然后设置 Kinesis 数据流的分片数。

1. 使用以下代码段在DynamoDB 表上启用 Kinesis 数据流。（可选）启用在每条记录上返回更精细（微秒）精度时间戳值的流式传输。

   启用微秒时间戳精度的流式传输：

   ```
   EnableKinesisStreamingConfiguration enableKdsConfig = EnableKinesisStreamingConfiguration.builder()
     .approximateCreationDateTimePrecision(ApproximateCreationDateTimePrecision.MICROSECOND)
     .build();
   
   EnableKinesisStreamingDestinationRequest enableKdsRequest = EnableKinesisStreamingDestinationRequest.builder()
     .tableName(tableName)
     .streamArn(kdsArn)
     .enableKinesisStreamingConfiguration(enableKdsConfig)
     .build();
   
   EnableKinesisStreamingDestinationResponse enableKdsResponse = ddbClient.enableKinesisStreamingDestination(enableKdsRequest);
   ```

   或者启用默认时间戳精度（毫秒）的流式传输：

   ```
   EnableKinesisStreamingDestinationRequest enableKdsRequest = EnableKinesisStreamingDestinationRequest.builder()
     .tableName(tableName)
     .streamArn(kdsArn)
     .build();
   
   EnableKinesisStreamingDestinationResponse enableKdsResponse = ddbClient.enableKinesisStreamingDestination(enableKdsRequest);
   ```

1. 按照 *Kinesis Data Streams 开发人员指南*中的说明进行操作，从创建的数据流[读取](https://docs.aws.amazon.com/streams/latest/dev/building-consumers.html)。

1. 然后使用以下代码段来反序列化流内容。

   ```
   /**
    * Takes as input a Record fetched from Kinesis and does arbitrary processing as an example.
    */
   public void processRecord(Record kinesisRecord) throws IOException {
       ByteBuffer kdsRecordByteBuffer = kinesisRecord.getData();
       JsonNode rootNode = OBJECT_MAPPER.readTree(kdsRecordByteBuffer.array());
       JsonNode dynamoDBRecord = rootNode.get("dynamodb");
       JsonNode oldItemImage = dynamoDBRecord.get("OldImage");
       JsonNode newItemImage = dynamoDBRecord.get("NewImage");
       Instant recordTimestamp = fetchTimestamp(dynamoDBRecord);
   
       /**
        * Say for example our record contains a String attribute named "stringName" and we wanted to fetch the value
        * of this attribute from the new item image, the below code would fetch this.
        */
       JsonNode attributeNode = newItemImage.get("stringName");
       JsonNode attributeValueNode = attributeNode.get("S"); // Using DynamoDB "S" type attribute
       String attributeValue = attributeValueNode.textValue();
       System.out.println(attributeValue);
   }
   
   private Instant fetchTimestamp(JsonNode dynamoDBRecord) {
       JsonNode timestampJson = dynamoDBRecord.get("ApproximateCreationDateTime");
       JsonNode timestampPrecisionJson = dynamoDBRecord.get("ApproximateCreationDateTimePrecision");
       if (timestampPrecisionJson != null && timestampPrecisionJson.equals("MICROSECOND")) {
           return Instant.EPOCH.plus(timestampJson.longValue(), ChronoUnit.MICROS);
       }
       return Instant.ofEpochMilli(timestampJson.longValue());
   }
   ```

------

## 更改活动的 Amazon Kinesis Data Streams
<a name="kds_gettingstarted.making-changes"></a>

本节介绍了如何使用控制台、AWS CLI 和 API 更改活动的 Kinesis Data Streams for DynamoDB 设置。

**AWS 管理控制台**

1. 打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 转至您的表。

1. 选择**导出和流**。

**AWS CLI**

1. 调用 `describe-kinesis-streaming-destination` 以确认流的状态是 `ACTIVE`。

1. 调用 `UpdateKinesisStreamingDestination`，如以下示例所示：

   ```
   aws dynamodb update-kinesis-streaming-destination --table-name enable_test_table --stream-arn arn:aws:kinesis:us-east-1:12345678901:stream/enable_test_stream --update-kinesis-streaming-configuration ApproximateCreationDateTimePrecision=MICROSECOND
   ```

1. 调用 `describe-kinesis-streaming-destination` 以确认流的状态是 `UPDATING`。

1. 定期调用 `describe-kinesis-streaming-destination`，直到流式传输状态恢复 `ACTIVE`。时间戳精度更新通常最多需要 5 分钟的时间才能生效。此状态更新后，即表示更新已完成，新的精度值将应用于未来的记录。

1. 使用 `putItem` 写入表。

1. 使用 Kinesis `get-records` 命令检索流内容。

1. 确认写入的 `ApproximateCreationDateTime` 具有所需的精度。

**Java API**

1. 提供构造 `UpdateKinesisStreamingDestination` 请求和 `UpdateKinesisStreamingDestination` 响应的代码段。

1. 提供构造 `DescribeKinesisStreamingDestination` 请求和 `DescribeKinesisStreamingDestination response` 的代码段。

1. 定期调用 `describe-kinesis-streaming-destination`，直到流状态恢复 `ACTIVE`，这表明更新已完成，新的精度值将应用于未来的记录。

1. 向表执行写入操作。

1.  从流中读取并反序列化流内容。

1. 确认写入的 `ApproximateCreationDateTime` 具有所需的精度。

# 将 DynamoDB Streams 中的分片和指标与 Kinesis Data Streams 配合使用
<a name="kds_using-shards-and-metrics"></a>

## Kinesis Data Streams 的分片管理注意事项
<a name="kds_using-shards-and-metrics.shardmanagment"></a>

Kinesis 数据流以[分片](https://docs.aws.amazon.com/streams/latest/dev/key-concepts.html)形式计算其吞吐量。在 Amazon Kinesis Data Streams 中，您可以为数据流选择**按需**模式和**预置**模式。

如果您的 DynamoDB 写入工作负载变化很大且不可预测，我们建议您对 Kinesis Data Streams 使用按需模式。若采用按需模式，则无需容量规划，因为 Kinesis Data Streams 会自动管理分片来提供必要的吞吐量。

对于可预测的工作负载，您可以为 Kinesis Data Streams 使用预置模式。若采用预置模式，您必须为数据流指定分片数，以容纳 DynamoDB 的更改数据捕获记录。要确定 Kinesis 数据流支持 DynamoDB 表所需的分片数量，您需要以下输入值：
+ DynamoDB 表记录的平均大小，以字节为单位 (`average_record_size_in_bytes`)。
+ 每秒对 DynamoDB 表执行的最大写入操作数。这包括由您的应用程序执行的创建、删除和更新操作以及自动生成的操作，例如“生存时间”生成的删除操作（`write_throughput`）。
+ 您对表执行的更新和覆盖操作与创建或删除操作相比的百分比 (`percentage_of_updates`)。请注意，更新和覆盖操作会将已修改项目的旧镜像和新镜像复制到流中。这会生成两倍 DynamoDB 项目大小。

可使用以下公式中的输入值来计算 Kinesis 数据流所需的分片数量（`number_of_shards`）。

```
number_of_shards = ceiling( max( ((write_throughput * (4+percentage_of_updates) * average_record_size_in_bytes) / 1024 / 1024), (write_throughput/1000)), 1)
```

例如，您的最大吞吐量为 1040 个写入操作/秒（`write_throughput`），平均记录大小为 800 字节（`average_record_size_in_bytes)`）。如果这些写入操作中有 25% 是更新操作（`percentage_of_updates`），则将需要两个分片（`number_of_shards`）来容纳您的 DynamoDB 流式传输吞吐量：

```
ceiling( max( ((1040 * (4+25/100) * 800)/ 1024 / 1024), (1040/1000)), 1).
```

在使用公式计算 Kinesis 数据流在预置模式下所需的分片数之前，请考虑以下几点：
+ 此公式有助于估算容纳 DynamoDB 更改数据记录所需的分片数量。它不代表 Kinesis 数据流中所需的分片总数，例如支持额外 Kinesis 数据流使用者所需的分片数量。
+ 如果您未将数据流配置为处理峰值吞吐量，则在预置模式下仍可能遇到读写吞吐量异常的问题。在这种情况下，您必须手动扩展数据流以适应数据流量。
+ 此公式考虑了 DynamoDB 在将更改日志数据记录流式传输到 Kinesis 数据流之前产生的额外膨胀。

要了解有关 Kinesis 数据流容量模式的更多信息，请参阅[选择数据流容量模式](https://docs.aws.amazon.com/streams/latest/dev/how-do-i-size-a-stream.html)。要详细了解不同容量模式之间的定价差异，请参阅 [Amazon Kinesis Data Streams 定价](https://aws.amazon.com/kinesis/data-streams/pricing/)。

## 使用 Kinesis Data Streams 监控更改数据捕获
<a name="kds_using-shards-and-metrics.monitoring"></a>

DynamoDB 提供多个 Amazon CloudWatch 指标，帮助您监控将更改数据捕获到 Kinesis 的复制。有关 CloudWatch 指标的完整列表，请参阅[DynamoDB 指标与维度](metrics-dimensions.md)。

我们建议您在流启用期间和生产中监视以下项目，以确定流是否有足够的容量：
+ `ThrottledPutRecordCount`：由于 Kinesis Data Streams 容量不足而受到 Kinesis 数据流限制的记录数量。异常使用高峰期间可能会遇到一些限制，但 `ThrottledPutRecordCount` 应保持尽可能低。DynamoDB 重试将受限制的记录发送到 Kinesis 数据流，这可能会导致更高的复制延迟。

  如果遇到过多和定期的限制，则可能需要按照观察到的表写入吞吐量成比例增加 Kinesis 流分片数量。要了解有关如何确定 Kinesis 数据流的大小的详细信息，请参阅[确定 Kinesis Data Streams 的初始大小](https://docs.aws.amazon.com/streams/latest/dev/amazon-kinesis-streams.html#how-do-i-size-a-stream)。
+ `AgeOfOldestUnreplicatedRecord`：从等待复制的最早的项目级别更改，到 Kinesis 数据流出现在 DynamoDB 表中经过的时间。在正常运行情况下，`AgeOfOldestUnreplicatedRecord` 应该以毫秒为单位。如果失败的尝试由客户控制的配置选项造成，失败的复制尝试次数会增加。

   如果 `AgeOfOldestUnreplicatedRecord` 指标超过 168 小时，则将自动禁用从 DynamoDB 表到 Kinesis 数据流的项目级别更改的复制。

  例如，可能导致复制尝试失败的客户控制配置包括：预置不足的 Kinesis 数据流容量导致过度限制，或者手动更新 Kinesis 数据流的访问策略阻止 DynamoDB 向数据流添加数据。您可能需要确保预置合适的 Kinesis 数据流容量，并确保未更改 DynamoDB 的权限，才能将该指标保持在尽可能低的水平。
+ `FailedToReplicateRecordCount`：DynamoDB 无法复制到 Kinesis 数据流的记录数。某些大于 34KB 的项目可能会扩增，以更改大于 Kinesis Data Streams 1MB 项目大小限制的数据记录。当大于 34KB 的项目包含大量布尔值或空属性值时，就会出现此扩增现象。布尔值和空属性值在 DynamoDB 中存储为 1 个字节，但在使用标准 JSON 进行 Kinesis Data Streams 复制时，最多可扩展到 5 个字节。DynamoDB 无法将此类更改记录复制到 Kinesis 数据流中。DynamoDB 跳过这些更改数据记录，并自动继续复制后续记录。

   

您可以创建 Amazon CloudWatch 警报，以便在上述任何指标超过特定阈值时发送 Amazon Simple Notification Service (Amazon SNS) 消息，以便发送通知。

# 使用适用于 Amazon Kinesis Data Streams 和 Amazon DynamoDB 的 IAM 策略
<a name="kds_iam"></a>

首次启用 Amazon Kinesis Data Streams for Amazon DynamoDB 时，DynamoDB 会自动创建一个 AWS Identity and Access Management (IAM) 服务相关角色。此角色 `AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 允许 DynamoDB 代表您管理对 Kinesis Data Streams 的项目级别更改的复制。不要删除该服务相关角色。

有关服务相关角色的更多信息，请参见 *IAM 用户指南*中的[使用服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html)。

**注意**  
DynamoDB 不支持基于标签的 IAM 策略条件。

要启用 Amazon Kinesis Data Streams for Amazon DynamoDB，您必须对该表具有以下权限：
+ `dynamodb:EnableKinesisStreamingDestination`
+ `kinesis:ListStreams`
+ `kinesis:PutRecords`
+ `kinesis:DescribeStream`

要为给定的 DynamoDB 表描述 Amazon Kinesis Data Streams for Amazon DynamoDB，您必须对该表具有以下权限。
+ `dynamodb:DescribeKinesisStreamingDestination`
+ `kinesis:DescribeStreamSummary`
+ `kinesis:DescribeStream`

要禁用 Amazon Kinesis Data Streams for Amazon DynamoDB，您必须对该表具有以下权限。
+ `dynamodb:DisableKinesisStreamingDestination`

要更新 Amazon Kinesis Data Streams for Amazon DynamoDB，您必须对该表具有以下权限。
+ `dynamodb:UpdateKinesisStreamingDestination`

以下示例说明如何使用 IAM 策略授予 Amazon Kinesis Data Streams for Amazon DynamoDB 权限。

## 示例：启用 Amazon Kinesis Data Streams for Amazon DynamoDB
<a name="access-policy-kds-example1"></a>

以下 IAM 策略授予了为 `Music` 表启用 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。它不授予为 `Music` 表禁用、更新或描述 Kinesis Data Streams for DynamoDB 的权限。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "iam:CreateServiceLinkedRole",
            "Resource": "arn:aws:iam::*:role/aws-service-role/kinesisreplication.dynamodb.amazonaws.com/AWSServiceRoleForDynamoDBKinesisDataStreamsReplication",
            "Condition": {
                "StringLike": {
                    "iam:AWSServiceName": "kinesisreplication.dynamodb.amazonaws.com"
                }
            }
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:EnableKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        }
    ]
}
```

------

## 示例：更新 Amazon Kinesis Data Streams for Amazon DynamoDB
<a name="access-policy-kds-example2"></a>

以下 IAM 策略授予了为 `Music` 表更新 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。它不授予为 `Music` 表启用、禁用或描述 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:UpdateKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        }
    ]
}
```

------

## 示例：禁用 Amazon Kinesis Data Streams for Amazon DynamoDB
<a name="access-policy-kds-example2"></a>

以下 IAM 策略授予了为 `Music` 表禁用 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。它不授予为 `Music` 表启用、更新或描述 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DisableKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        }
    ]
}
```

------

## 示例：根据资源有选择地应用 Amazon Kinesis Data Streams for Amazon DynamoDB 权限
<a name="access-policy-kds-example3"></a>

下面的 IAM 策略授予了为 `Music` 表启用和描述 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限，并且拒绝为 `Orders` 表禁用 Amazon Kinesis Data Streams for Amazon DynamoDB 的权限。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:EnableKinesisStreamingDestination",
                "dynamodb:DescribeKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        },
        {
            "Effect": "Deny",
            "Action": [
                "dynamodb:DisableKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Orders"
        }
    ]
}
```

------

## 使用 Kinesis Data Streams for DynamoDB 的服务相关角色
<a name="kds-service-linked-roles"></a>

Amazon Kinesis Data Streams for Amazon DynamoDB 使用 AWS Identity and Access Management (IAM) [服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#iam-term-service-linked-role)。服务相关角色是一种与 Kinesis Data Streams for DynamoDB 直接关联的独特类型的 IAM 角色。服务相关角色由 Kinesis Data Streams for DynamoDB 预定义，具有服务代表您调用其他 AWS 服务所需的所有权限。

服务相关角色可让您更轻松地设置 Kinesis Data Streams for DynamoDB，因为您不必手动添加必要的权限。Kinesis Data Streams for DynamoDB 定义其服务相关角色的权限，除非另外定义，否则只有 Kinesis Data Streams for DynamoDB 可以代入该角色。定义的权限包括信任策略和权限策略，而且权限策略不能附加到任何其他 IAM 实体。

有关支持服务相关角色的其它服务的信息，请参阅[使用 IAM 的 AWS 服务](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)并查找**服务相关角色**列中显示为**是**的服务。选择**是**和链接，查看该服务的服务关联角色文档。

### Kinesis Data Streams for DynamoDB 的服务相关角色权限
<a name="slr-permissions"></a>

Kinesis Data Streams for DynamoDB 使用名为 **AWSServiceRoleForDynamoDBKinesisDataStreamsReplication** 的服务相关角色。服务相关角色的目的是允许 Amazon DynamoDB 代表您管理项目级 Kinesis Data Streams 改动的复制。

`AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 服务相关角色信任以下服务代入该角色：
+ `kinesisreplication.dynamodb.amazonaws.com`

角色权限策略允许 Kinesis Data Streams for DynamoDB 对指定资源完成以下操作：
+ 操作：`Kinesis stream` 上的 `Put records and describe`
+ 操作：`AWS KMS` 上的 `Generate data keys`，以将数据放到使用用户生成的 AWS KMS 密钥加密的 Kinesis 数据流中。

有关该策略文档的确切内容，请参阅 [DynamoDBKinesisReplicationServiceRolePolicy](https://console.aws.amazon.com/iam/home#policies/arn:aws:iam::aws:policy/aws-service-role/DynamoDBKinesisReplicationServiceRolePolicy)。

您必须配置权限，允许 IAM 实体（如用户、组或角色）创建、编辑或删除服务关联角色。有关更多信息，请参阅《IAM 用户指南》**中的[服务关联角色权限](https://docs.aws.amazon.com/IAM/latest/UserGuide/contributorinsights-service-linked-roles.html#service-linked-role-permissions)。

### 创建 Kinesis Data Streams for DynamoDB 的服务相关角色
<a name="create-slr"></a>

您无需手动创建服务关联角色。在 AWS 管理控制台、AWS CLI 或 AWS API 中启用 Kinesis Data Streams for DynamoDB 后，Kinesis Data Streams for DynamoDB 将为您创建服务相关角色。

如果您删除该服务关联角色，然后需要再次创建，您可以使用相同流程在账户中重新创建此角色。启用 Kinesis Data Streams for DynamoDB 后，Kinesis Data Streams for DynamoDB 将再次为您创建与服务相关的角色。

### 编辑 Kinesis Data Streams for DynamoDB 的服务相关角色
<a name="edit-slr"></a>

Kinesis Data Streams for DynamoDB 不允许您编辑 `AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 服务相关角色。创建服务关联角色后，您将无法更改角色的名称，因为可能有多种实体引用该角色。但是可以使用 IAM 编辑角色描述。有关更多信息，请参见 *IAM 用户指南*中的[编辑服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/contributorinsights-service-linked-roles.html#edit-service-linked-role)。

### 删除 Kinesis Data Streams for DynamoDB 的服务相关角色
<a name="delete-slr"></a>

还可以使用 IAM 控制台、AWS CLI 或 AWS API 手动删除服务相关角色。为此，必须先手动清除服务相关角色的资源，然后才能手动删除。

**注意**  
如果在您试图删除资源时 Kinesis Data Streams for DynamoDB 服务正在使用该角色，则删除操作可能会失败。如果发生这种情况，请等待几分钟后重试。

**使用 IAM 手动删除服务关联角色**

使用 IAM 控制台，即 AWS CLI 或 AWS API 来删除 `AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 服务相关角色。有关更多信息，请参见 *IAM 用户指南*中的[删除服务相关角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html)。

# 将更改数据捕获用于 DynamoDB Streams
<a name="Streams"></a>

 DynamoDB Streams 可在任何 DynamoDB 表中捕获按时间排序的项目级修改序列，并将这类信息存储在日志中长达 24 个小时。应用程序可访问此日志，并在数据项目修改前后近乎实时地查看所显示的数据项目。

 静态加密会加密 DynamoDB 流中的数据。有关更多信息，请参阅 [静态 DynamoDB 加密](EncryptionAtRest.md)。

*DynamoDB 流*是一种有关 DynamoDB 表中的项目更改的有序信息流。当您对表启用流时，DynamoDB 将捕获有关对表中的数据项目进行的每项修改的信息。

每当应用程序在表中创建、更新或删除项目时，DynamoDB Streams 都将编写一条具有已修改项目的主键属性的流记录。*流记录*包含有关对 DynamoDB 表中的单个项目所做的数据修改的信息。您可以配置流，以便流记录捕获其他信息，例如已修改项目的“前”和“后”图像。

DynamoDB Streams 可以帮助确保：
+ 每个流记录仅在流中显示一次。
+ 对于 DynamoDB 表中修改的每个项目，流记录将按照对该项目进行的实际修改的顺序显示。

DynamoDB Streams 近乎实时编写流记录，以便您能构建使用这些流并根据内容采取操作的应用程序。

**Topics**
+ [DynamoDB Streams 的端点](#Streams.Endpoints)
+ [启用流](#Streams.Enabling)
+ [读取和处理流](#Streams.Processing)
+ [DynamoDB Streams 和生存时间](time-to-live-ttl-streams.md)
+ [使用 DynamoDB Streams Kinesis Adapter 处理流记录](Streams.KCLAdapter.md)
+ [DynamoDB Streams 低级 API：Java 示例](Streams.LowLevel.Walkthrough.md)
+ [DynamoDB Streams 和 AWS Lambda 触发器](Streams.Lambda.md)
+ [DynamoDB Streams 和 Apache Flink](StreamsApacheFlink.xml.md)

## DynamoDB Streams 的端点
<a name="Streams.Endpoints"></a>

AWS 为 DynamoDB 和 DynamoDB Streams 维护单独的端点。要使用数据库表和索引，您的应用程序必须访问 DynamoDB 端点。要读取和处理 DynamoDB Streams 记录，您的应用程序必须访问相同区域内的 DynamoDB Streams 端点。

DynamoDB Streams 提供两组端点。它们是：
+ **仅 IPv4 的端点**：使用 `streams.dynamodb.<region>.amazonaws.com` 命名约定的端点。
+ **双栈端点**：兼容 IPv4 和 IPv6 并遵循 `streams-dynamodb.<region>.api.aws` 命名约定的新端点。

**注意**  
有关 DynamoDB 和 DynamoDB Streams 区域和端点的完整列表，请参阅《AWS 一般参考》**中的[区域和端点](https://docs.aws.amazon.com/general/latest/gr/rande.html)。

AWS SDK 为 DynamoDB 和 DynamoDB Streams 提供单独的客户端。根据您的要求，您的应用程序可以访问 DynamoDB 端点，DynamoDB Streams 端点或同时访问二者。要连接到两个端点，您的应用程序必须实例化两个客户端 — 一个用于 DynamoDB，另一个用于 DynamoDB Streams。

## 启用流
<a name="Streams.Enabling"></a>

使用 AWS CLI 或某个 AWS SDK 创建新表时，可以在新表上启用流。还可以对现有表启用或禁用流，或更改流设置。DynamoDB Streams 可异步执行操作，因此在启用流的情况下不会影响表的性能。

管理 DynamoDB Streams 最简单的方法是使用 AWS 管理控制台。

1. 登录 AWS 管理控制台，并打开 DynamoDB 控制台：[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)。

1. 在 DynamoDB 控制台控制面板上，选择**表**，然后选择现有表。

1. 选择**导出和流式传输**选项卡。

1. 在 **DynamoDB 流详细信息**部分，选择**开启**。

1. 在**开启 DynamoDB 流**页面中，选择在修改表中的数据时将写入流中的信息：
   + **仅键** — 仅所修改项目的键属性。
   + **新映像** — 修改后的整个项目。
   + **旧映像** — 修改前的整个项目。
   + **新旧映像** — 项目的新旧映像。

   根据需要进行设置后，选择**开启流**。

1. （可选）要禁用现有流，请选择 **DynamoDB 流详细信息**下的**关闭**。

您还可以使用 `CreateTable` 或 `UpdateTable` API 操作来启用或修改流。`StreamSpecification` 参数确定如何配置流：
+ `StreamEnabled` — 指定对表启用 (`true`) 或禁用 (`false`) 流。
+ `StreamViewType` — 指定在修改表中的数据时将写入流中的信息：
  + `KEYS_ONLY` — 仅所修改项目的键属性。
  + `NEW_IMAGE` — 整个项目在修改后的显示。
  + `OLD_IMAGE` — 整个项目在修改前的显示。
  + `NEW_AND_OLD_IMAGES` — 项目的新旧映像。

您可以随时启用或禁用流。但是，如果您尝试在已有流的表上启用流，则会收到 `ValidationException`。如果您尝试在没有流的表上禁用流，也会收到 `ValidationException`。

当您将 `StreamEnabled` 设置为 `true` 时，DynamoDB 将创建一个新流，并为其分配一个唯一的流描述符。如果您在表上禁用然后重新启用流，则将创建一个具有不同流描述符的新流。

每个流均由一个 Amazon 资源名称（ARN）进行唯一标识。以下是名为 `TestTable` 的 DynamoDB 表中一个流的示例 ARN。

```
arn:aws:dynamodb:us-west-2:111122223333:table/TestTable/stream/2015-05-11T21:21:33.291
```

要确定表的最新流描述符，请发出一个 DynamoDB `DescribeTable` 请求并在响应中查找 `LatestStreamArn` 元素。

**注意**  
一旦设置了流，就无法编辑 `StreamViewType`。如果您需要在设置流后对其进行更改，则必须禁用当前流并创建一个新的流。

## 读取和处理流
<a name="Streams.Processing"></a>

要读取和处理流，您的应用程序必须连接到 DynamoDB Streams 端点并发出 API 请求。

流由*流记录* 构成。每条流记录均代表流所在的 DynamoDB 表中的一个数据修改。每条流记录均分配有一个序列号，该序列号反映了将记录发布至流的顺序。

流记录将组织到群组或*分区* 中。每个分区可充当多条流记录的容器，并包含访问和迭代这些记录所需的信息。分区中的流记录将在 24 小时后自动删除。

分区是临时的：将根据需要自动创建和删除它们。此外，任何分区均可拆分为多个新分区；这也是自动进行的。（请注意，父分片还可能只有一个子分片。） 分区可能会在响应其父表上的高级写入活动时拆分，以便应用程序可以并行处理来自多个分区的记录。

如果您禁用流，将关闭已打开的分区。流中的数据在 24 小时内可继续读取。

由于分区存在沿袭 (父分区和子分区)，应用程序必须始终先处理父分区，然后再处理子分区。这可帮助确保流记录也会按正确顺序进行处理。（如果您使用 DynamoDB Streams Kinesis Adapter，则会为您进行处理。您的应用程序按正确顺序处理分片和流记录。它自动处理新分片或过期分片，以及在应用程序运行期间拆分的分片。有关更多信息，请参阅[使用 DynamoDB Streams Kinesis Adapter 处理流记录](Streams.KCLAdapter.md)。）

下图显示流、流中的分区和分区中的流记录之间的关系。

![\[DynamoDB Streams 结构。表示数据修改的流记录以分片的形式加以组织。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/streams-terminology.png)


**注意**  
如果您执行的 `PutItem` 或 `UpdateItem` 操作不更改项目中的任何数据，则 DynamoDB Streams *不会*为该操作编写流记录。

要访问流和处理其中的流记录，您必须执行以下操作：
+ 确定您要访问的流的唯一 ARN。
+ 确定流中的哪些分片包含您感兴趣的流记录。
+ 访问分片并检索所需的流记录。

**注意**  
从同一流的分片中同时读取的进程最多不得超过 2 个。读取器超过 2 个的分片可能会受到限制。

DynamoDB Streams API 提供以下操作以供应用程序使用：
+  `[ListStreams](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_ListStreams.html)` — 返回当前账户和端点的流描述符列表。(可选) 您可以只请求特定表名称的流描述符。
+ `[DescribeStream](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_DescribeStream.html)`：返回有关流的信息，包括流的当前状态、其 Amazon 资源名称（ARN）、其分片的构成及其相应的 DynamoDB 表。您可以选择使用 `ShardFilter` 字段来检索与父分片关联的现有子分片。
+ `[GetShardIterator](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_GetShardIterator.html)` — 返回一个描述分片中位置的*分片迭代器*。您可以请求该迭代器提供对流中最旧的点、最新的点或某个特定点的访问权。
+ `[GetRecords](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_streams_GetRecords.html)` — 返回来自给定分片中的流记录。您必须提供从 `GetShardIterator` 请求中返回的分区迭代器。

有关这些 API 操作的完整描述（包括示例请求和响应），请参阅[Amazon DynamoDB Streams API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Operations_Amazon_DynamoDB_Streams.html)。

### 分片发现
<a name="Streams.ShardDiscovery"></a>



使用两种强有力的方法在您的 DynamoDB 流中发现新的分片。作为 Amazon DynamoDB Streams 用户，您可以通过两种有效的方法来跟踪和识别新的分片：

**轮询整个流拓扑**  
使用 `DescribeStream` API 定期对流进行轮询。这将返回流中的所有分片，包括已创建的任何新分片。通过比较一段时间内的结果，您可以检测到新添加的分片。

**发现子分片**  
使用 `DescribeStream` API 及 `ShardFilter` 参数来查找分片的子集。通过在请求中指定父分片，DynamoDB Streams 将返回其直接子分片。当只需跟踪分片沿袭而不需要扫描整个流时，这种方法很有用。  
使用来自 DynamoDB Streams 的数据的应用程序可以使用此 `ShardFilter` 参数，高效地从读取已关闭的分片过渡到其子分片，从而避免重复调用 `DescribeStream` API 来检索和遍历所有已关闭和打开的分片的分片映射。这有助于在父分片关闭后快速发现子分片，从而提高流处理应用程序的响应速度和成本效益。

这两种方法都能让您随时掌握 DynamoDB Streams 不断演变的结构，确保您不会错过任何关键的数据更新或分片修改。

### DynamoDB Streams 的数据留存限制
<a name="Streams.DataRetention"></a>

DynamoDB Streams 中所有数据的生命周期为 24 小时。您可以检索和分析任意给定表过去 24 小时的活动。但是，24 小时之前的数据可能会随时被修剪（删除）。

如果您对某个表禁用一个流，该流中的数据仍在 24 小时内可读。此时间过后，数据将过期，并且流记录将自动被删除。没有手动删除现有流的机制。您必须等待直至保留期限过期（24 小时），然后将删除所有流记录。

# DynamoDB Streams 和生存时间
<a name="time-to-live-ttl-streams"></a>

您可以通过在表中启用 Amazon DynamoDB Streams 并处理已过期项目的流记录来备份或者处理按[生存时间](TTL.md)（TTL）删除的项目。有关更多信息，请参阅 [读取和处理流](Streams.md#Streams.Processing)。

流记录包含用户身份字段`Records[<index>].userIdentity`。

在过期后被生存时间过程删除的项目包含以下字段：
+ `Records[<index>].userIdentity.type`

  `"Service"`
+ `Records[<index>].userIdentity.principalId`

  `"dynamodb.amazonaws.com"`

**注意**  
在全局表中使用 TTL 时，执行 TTL 的区域将设置 `userIdentity` 字段。复制删除操作时，不会在其它区域设置此字段。

以下 JSON 显示单个流记录的相关部分。

```
"Records": [
    {
        ...

        "userIdentity": {
            "type": "Service",
            "principalId": "dynamodb.amazonaws.com"
        }

        ...

    }
]
```

## 使用 DynamoDB Streams 和 Lambda 归档已删除 TTL 的项目
<a name="streams-archive-ttl-deleted-items"></a>

结合使用 [DynamoDB 生存时间（TTL）](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html)、[DynamoDB Streams](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html) 和 [AWS Lambda](https://aws.amazon.com/lambda/) 有助于简化数据归档、降低 DynamoDB 存储成本并降低代码复杂性。使用 Lambda 作为流使用者提供了许多优势，最明显的是与 Kinesis Client Library (KCL) 等其他使用者相比，降低了成本。当通过 Lambda 来使用事件时，对 DynamoDB 流的 `GetRecords` API 调用不向您收费，并且 Lambda 可以通过识别流事件中的 JSON 模式来提供事件筛选。借助事件模式内容筛选，您可以定义多达五个不同的筛选条件来控制将哪些事件发送到 Lambda 进行处理。这有助于减少对 Lambda 函数的调用、简化代码并降低总体成本。

尽管 DynamoDB Streams 包含所有数据修改，例如 `Create`、`Modify` 和 `Remove` 操作，但这可能导致不必要地调用归档 Lambda 函数。例如，假设一个每小时有 200 万项数据修改的表流入流中，但其中不到 5％ 的数据修改是将在 TTL 流程中过期而需要归档的项目删除。使用 [Lambda 事件源筛选条件](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)，Lambda 函数每小时只调用 100,000 次。事件筛选的结果是，您只需为所需的调用付费。在没有事件筛选的情况下，您需要为获得的 200 万次调用付费。

事件筛选应用于 [Lambda 事件源映射](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventsourcemapping.html)，它是一个从选定事件（DynamoDB 流）读取并调用 Lambda 函数的资源。在下图中，您可以看到 Lambda 函数如何通过流和事件筛选条件使用已删除生存时间的项目。

![\[通过 TTL 流程删除的项目会启动使用流和事件筛选条件的 Lambda 函数。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/streams-lambda-ttl.png)


### DynamoDB 生存时间事件筛选条件模式
<a name="ttl-event-filter-pattern"></a>

将以下 JSON 添加到源映射[筛选标准](https://docs.aws.amazon.com/lambda/latest/dg/API_FilterCriteria.html)仅允许对已删除 TTL 的项目调用 Lambda 函数：

```
{
    "Filters": [
        {
            "Pattern": { "userIdentity": { "type": ["Service"], "principalId": ["dynamodb.amazonaws.com"] } }
        }
    ]
}
```

### 创建 AWS Lambda 事件源映射
<a name="create-event-source-mapping"></a>

使用以下代码段创建筛选的事件源映射，您可以将其连接到表的 DynamoDB 流。每个代码块都包括事件筛选条件模式。

------
#### [ AWS CLI ]

```
aws lambda create-event-source-mapping \
--event-source-arn 'arn:aws:dynamodb:eu-west-1:012345678910:table/test/stream/2021-12-10T00:00:00.000' \
--batch-size 10 \
--enabled \
--function-name test_func \
--starting-position LATEST \
--filter-criteria '{"Filters": [{"Pattern": "{\"userIdentity\":{\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}"}]}'
```

------
#### [ Java ]

```
LambdaClient client = LambdaClient.builder()
        .region(Region.EU_WEST_1)
        .build();

Filter userIdentity = Filter.builder()
        .pattern("{\"userIdentity\":{\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}")
        .build();

FilterCriteria filterCriteria = FilterCriteria.builder()
        .filters(userIdentity)
        .build();

CreateEventSourceMappingRequest mappingRequest = CreateEventSourceMappingRequest.builder()
        .eventSourceArn("arn:aws:dynamodb:eu-west-1:012345678910:table/test/stream/2021-12-10T00:00:00.000")
        .batchSize(10)
        .enabled(Boolean.TRUE)
        .functionName("test_func")
        .startingPosition("LATEST")
        .filterCriteria(filterCriteria)
        .build();

try{
    CreateEventSourceMappingResponse eventSourceMappingResponse = client.createEventSourceMapping(mappingRequest);
    System.out.println("The mapping ARN is "+eventSourceMappingResponse.eventSourceArn());

}catch (ServiceException e){
    System.out.println(e.getMessage());
}
```

------
#### [ Node ]

```
const client = new LambdaClient({ region: "eu-west-1" });

const input = {
    EventSourceArn: "arn:aws:dynamodb:eu-west-1:012345678910:table/test/stream/2021-12-10T00:00:00.000",
    BatchSize: 10,
    Enabled: true,
    FunctionName: "test_func",
    StartingPosition: "LATEST",
    FilterCriteria: { "Filters": [{ "Pattern": "{\"userIdentity\":{\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}" }] }
}

const command = new CreateEventSourceMappingCommand(input);

try {
    const results = await client.send(command);
    console.log(results);
} catch (err) {
    console.error(err);
}
```

------
#### [ Python ]

```
session = boto3.session.Session(region_name = 'eu-west-1')
client = session.client('lambda')

try:
    response = client.create_event_source_mapping(
        EventSourceArn='arn:aws:dynamodb:eu-west-1:012345678910:table/test/stream/2021-12-10T00:00:00.000',
        BatchSize=10,
        Enabled=True,
        FunctionName='test_func',
        StartingPosition='LATEST',
        FilterCriteria={
            'Filters': [
                {
                    'Pattern': "{\"userIdentity\":{\"type\":[\"Service\"],\"principalId\":[\"dynamodb.amazonaws.com\"]}}"
                },
            ]
        }
    )
    print(response)
except Exception as e:
    print(e)
```

------
#### [ JSON ]

```
{
  "userIdentity": {
     "type": ["Service"],
     "principalId": ["dynamodb.amazonaws.com"]
   }
}
```

------

# 使用 DynamoDB Streams Kinesis Adapter 处理流记录
<a name="Streams.KCLAdapter"></a>

使用 Amazon Kinesis Adapter 是使用来自 Amazon DynamoDB 的流的建议方法。DynamoDB Streams API 有意与 Kinesis Data Streams 的 API 类似。在这两种服务中，数据流都由分片组成，分片是流记录的容器。这两种服务的 API 都包含 `ListStreams`、`DescribeStream`、`GetShards` 和 `GetShardIterator` 操作。（虽然这些 DynamoDB Streams 操作与 Kinesis Data Streams 中的对应操作类似，但它们并不完全相同。）

作为 DynamoDB Streams 用户，您可以使用 KCL 中找到的设计模式来处理 DynamoDB Streams 分片和流记录。若要执行此操作，请使用 DynamoDB Streams Kinesis Adapter。Kinesis Adapter 实现 Kinesis Data Streams 接口，以便 KCL 可用于使用和处理来自 DynamoDB Streams 的记录。有关如何设置和安装 DynamoDB Streams Kinesis Adapter 的说明，请参阅 [GitHub 存储库](https://github.com/awslabs/dynamodb-streams-kinesis-adapter)。

您可以使用 Kinesis 客户端库 (KCL) 为 Kinesis Data Streams 编写应用程序。KCL 提供低级 Kinesis Data Streams API 之上的有用抽象来简化编码。有关 KCL 的更多信息，请参阅 *Amazon Kinesis Data Streams 开发人员指南*中的[使用 Kinesis 客户端库开发使用者](https://docs.aws.amazon.com/kinesis/latest/dev/developing-consumers-with-kcl.html)。

DynamoDB 建议将 KCL 版本 3.x 与适用于 Java 的 AWS SDK v2.x 一起使用。在过渡期间，当前 DynamoDB Streams Kinesis Adapter 版本 1.x 与 AWS SDK for 适用于 Java 的 AWS SDK v1.x 将按照 [AWS SDK 和工具维护政策](https://docs.aws.amazon.com/sdkref/latest/guide/maint-policy.html)，在其整个生命周期内继续按预期得到全面支持。

**注意**  
Amazon Kinesis Client Library（KCL）版本 1.x 和 2.x 已过时。KCL 1.x 将于 2026 年 1 月 30 日终止支持。我们强烈建议您在 2026 年 1 月 30 日之前，将使用版本 1.x 的 KCL 应用程序迁移到最新的 KCL 版本。要查找最新的 KCL 版本，请访问 GitHub 上的 [Amazon Kinesis Client Library](https://github.com/awslabs/amazon-kinesis-client) 页面。有关最新 KCL 版本的信息，请参阅 [Use Kinesis Client Library](https://docs.aws.amazon.com/streams/latest/dev/kcl.html)。有关从 KCL 1.x 迁移到 KCL 3.x 的信息，请参阅“从 KCL 1.x 迁移到 KCL 3.x”。

以下图表显示了这些库之间的交互方式。

![\[通过 DynamoDB Streams、Kinesis Data Streams 和 KCL 之间的交互处理 DynamoDB Streams 记录。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/streams-kinesis-adapter.png)


有了 DynamoDB Streams Kinesis Adapter，您可以开始针对 KCL 接口进行开发，使 API 调用无缝定向到 DynamoDB Streams 端点。

应用程序启动后，调用 KCL 来实例化工作进程。必须为工作进程提供应用程序的配置信息，如流描述符和 AWS 凭证，以及您提供的记录处理器类的名称。在记录处理器中运行代码时，工作进程执行以下任务：
+ 连接到流
+ 枚举流中的分片
+ 检查并枚举流中已关闭父分片的子分片
+ 协调与其他工作程序的分片关联（如果有）
+ 为其管理的每个分片实例化记录处理器
+ 从流中提取记录
+ 在高吞吐量期间扩展 GetRecords API 调用速率（如果配置了追赶模式）
+ 将记录推送到对应的记录处理器
+ 对已处理记录进行检查点操作
+ 在工作程序实例计数更改时均衡分片与工作程序的关联
+ 在分片被拆分时平衡分片与工作程序的关联

KCL 适配器支持追赶模式，这是一种自动调整调用速率的功能，用于处理临时增加的吞吐量。当流处理滞后超过可配置的阈值（默认为一分钟）时，追赶模式会按可配置值（默认 3 倍）扩展 GetRecords API 调用频率，以更快地检索记录，然后在滞后下降后恢复正常。这在高吞吐量时段非常有用，在这段时间里，DynamoDB 写入活动可能会使用默认轮询速率让使用者不堪重负。可以通过 `catchupEnabled` 配置参数启用追赶模式（默认为 false）。

**注意**  
有关此处列出的 KCL 概念的说明，请参阅 *Amazon Kinesis Data Streams 开发人员指南*中的[使用 Kinesis 客户端库开发使用者](https://docs.aws.amazon.com/kinesis/latest/dev/developing-consumers-with-kcl.html)。  
有关将流与 AWS Lambda 配合使用的更多信息，请参阅 [DynamoDB Streams 和 AWS Lambda 触发器](Streams.Lambda.md)

# 从 KCL 1.x 迁移到 KCL 3.x
<a name="streams-migrating-kcl"></a>

## 概述
<a name="migrating-kcl-overview"></a>

本指南提供有关将使用者应用程序从 KCL 1.x 迁移到 KCL 3.x 的说明。由于 KCL 1.x 和 KCL 3.x 之间的架构差异，迁移需要更新多个组件以确保兼容性。

与 KCL 3.x 相比，KCL 1.x 使用不同的类和接口。必须先将记录处理器、记录处理器工厂和工作线程类迁移到 KCL 3.x 兼容格式，然后按照将 KCL 1.x 迁移到 KCL 3.x 的迁移步骤进行操作。

## 迁移步骤
<a name="migration-steps"></a>

**Topics**
+ [步骤 1：迁移记录处理器](#step1-record-processor)
+ [步骤 2：迁移记录处理器工厂](#step2-record-processor-factory)
+ [步骤 3：迁移工作线程](#step3-worker-migration)
+ [步骤 4：KCL 3.x 配置概述和建议](#step4-configuration-migration)
+ [步骤 5：从 KCL 2.x 迁移到 KCL 3.x](#step5-kcl2-to-kcl3)

### 步骤 1：迁移记录处理器
<a name="step1-record-processor"></a>

以下示例显示了为 KCL 1.x DynamoDB Streams Kinesis Adapter 实现的记录处理器：

```
package com.amazonaws.kcl;

import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException;
import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorCheckpointer;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IShutdownNotificationAware;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.ShutdownReason;
import com.amazonaws.services.kinesis.clientlibrary.types.InitializationInput;
import com.amazonaws.services.kinesis.clientlibrary.types.ProcessRecordsInput;
import com.amazonaws.services.kinesis.clientlibrary.types.ShutdownInput;

public class StreamsRecordProcessor implements IRecordProcessor, IShutdownNotificationAware {
    @Override
    public void initialize(InitializationInput initializationInput) {
        //
        // Setup record processor
        //
    }

    @Override
    public void processRecords(ProcessRecordsInput processRecordsInput) {
        for (Record record : processRecordsInput.getRecords()) {
            String data = new String(record.getData().array(), Charset.forName("UTF-8"));
            System.out.println(data);
            if (record instanceof RecordAdapter) {
                // record processing and checkpointing logic
            }
        }
    }

    @Override
    public void shutdown(ShutdownInput shutdownInput) {
        if (shutdownInput.getShutdownReason() == ShutdownReason.TERMINATE) {
            try {
                shutdownInput.getCheckpointer().checkpoint();
            } catch (ShutdownException | InvalidStateException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void shutdownRequested(IRecordProcessorCheckpointer checkpointer) {
        try {
            checkpointer.checkpoint();
        } catch (ShutdownException | InvalidStateException e) {
            //
            // Swallow exception
            //
            e.printStackTrace();
        }
    }
}
```

**迁移 RecordProcessor 类**

1. 将接口从 `com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor` 和 `com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IShutdownNotificationAware` 更改为 `com.amazonaws.services.dynamodbv2.streamsadapter.processor.DynamoDBStreamsShardRecordProcessor`，如下所示：

   ```
   // import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
   // import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IShutdownNotificationAware;
   
   import com.amazonaws.services.dynamodbv2.streamsadapter.processor.DynamoDBStreamsShardRecordProcessor;
   ```

1. 更新 `initialize` 和 `processRecords` 方法的导入语句：

   ```
   // import com.amazonaws.services.kinesis.clientlibrary.types.InitializationInput;
   import software.amazon.kinesis.lifecycle.events.InitializationInput;
   
   // import com.amazonaws.services.kinesis.clientlibrary.types.ProcessRecordsInput;
   import com.amazonaws.services.dynamodbv2.streamsadapter.model.DynamoDBStreamsProcessRecordsInput;
   ```

1. 使用以下新方法替换 `shutdownRequested` 方法：`leaseLost`、`shardEnded` 和 `shutdownRequested`。

   ```
   //    @Override
   //    public void shutdownRequested(IRecordProcessorCheckpointer checkpointer) {
   //        //
   //        // This is moved to shardEnded(...) and shutdownRequested(ShutdownReauestedInput)
   //        //
   //        try {
   //            checkpointer.checkpoint();
   //        } catch (ShutdownException | InvalidStateException e) {
   //            //
   //            // Swallow exception
   //            //
   //            e.printStackTrace();
   //        }
   //    }
   
       @Override
       public void leaseLost(LeaseLostInput leaseLostInput) {
   
       }
   
       @Override
       public void shardEnded(ShardEndedInput shardEndedInput) {
           try {
               shardEndedInput.checkpointer().checkpoint();
           } catch (ShutdownException | InvalidStateException e) {
               //
               // Swallow the exception
               //
               e.printStackTrace();
           }
       }
   
       @Override
       public void shutdownRequested(ShutdownRequestedInput shutdownRequestedInput) {
           try {
               shutdownRequestedInput.checkpointer().checkpoint();
           } catch (ShutdownException | InvalidStateException e) {
               //
               // Swallow the exception
               //
               e.printStackTrace();
           }
       }
   ```

下面是记录处理器类的更新版本：

```
package com.amazonaws.codesamples;

import software.amazon.kinesis.exceptions.InvalidStateException;
import software.amazon.kinesis.exceptions.ShutdownException;
import software.amazon.kinesis.lifecycle.events.InitializationInput;
import software.amazon.kinesis.lifecycle.events.LeaseLostInput;
import com.amazonaws.services.dynamodbv2.streamsadapter.model.DynamoDBStreamsProcessRecordsInput;
import software.amazon.kinesis.lifecycle.events.ShardEndedInput;
import software.amazon.kinesis.lifecycle.events.ShutdownRequestedInput;
import software.amazon.dynamodb.streamsadapter.processor.DynamoDBStreamsShardRecordProcessor;
import software.amazon.dynamodb.streamsadapter.adapter.DynamoDBStreamsKinesisClientRecord;
import com.amazonaws.services.dynamodbv2.streamsadapter.processor.DynamoDBStreamsShardRecordProcessor;
import com.amazonaws.services.dynamodbv2.streamsadapter.adapter.DynamoDBStreamsClientRecord;
import software.amazon.awssdk.services.dynamodb.model.Record;

public class StreamsRecordProcessor implements DynamoDBStreamsShardRecordProcessor {

    @Override
    public void initialize(InitializationInput initializationInput) {
        
    }

    @Override
    public void processRecords(DynamoDBStreamsProcessRecordsInput processRecordsInput) {
        for (DynamoDBStreamsKinesisClientRecord record: processRecordsInput.records())
            Record ddbRecord = record.getRecord();
            // processing and checkpointing logic for the ddbRecord
        }
    }

    @Override
    public void leaseLost(LeaseLostInput leaseLostInput) {
        
    }

    @Override
    public void shardEnded(ShardEndedInput shardEndedInput) {
        try {
            shardEndedInput.checkpointer().checkpoint();
        } catch (ShutdownException | InvalidStateException e) {
            //
            // Swallow the exception
            //
            e.printStackTrace();
        }
    }

    @Override
    public void shutdownRequested(ShutdownRequestedInput shutdownRequestedInput) {
        try {
            shutdownRequestedInput.checkpointer().checkpoint();
        } catch (ShutdownException | InvalidStateException e) {
            //
            // Swallow the exception
            //
            e.printStackTrace();
        }
    }
}
```

**注意**  
DynamoDB Streams Kinesis Adapter 现在使用 SDKv2 记录模型。在 SDKv2 中，复杂的 `AttributeValue` 对象（`BS`、`NS`、`M`、`L`、`SS`）从不会返回 null。使用 `hasBs()`、`hasNs()`、`hasM()`、`hasL()`、`hasSs()` 方法来验证这些值是否存在。

### 步骤 2：迁移记录处理器工厂
<a name="step2-record-processor-factory"></a>

记录处理器工厂负责在获得租约时创建记录处理器。下面是 KCL 1.x 工厂的示例：

```
package com.amazonaws.codesamples;

import software.amazon.dynamodb.AmazonDynamoDB;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessorFactory;

public class StreamsRecordProcessorFactory implements IRecordProcessorFactory {
    
    @Override
    public IRecordProcessor createProcessor() {
        return new StreamsRecordProcessor(dynamoDBClient, tableName);
    }
}
```

**迁移到 `RecordProcessorFactory`**
+ 将已实施的接口从 `com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessorFactory` 更改为 `software.amazon.kinesis.processor.ShardRecordProcessorFactory`，如下所示：

  ```
  // import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessor;
  import software.amazon.kinesis.processor.ShardRecordProcessor;
  
  // import com.amazonaws.services.kinesis.clientlibrary.interfaces.v2.IRecordProcessorFactory;
  import software.amazon.kinesis.processor.ShardRecordProcessorFactory;
  
  // public class TestRecordProcessorFactory implements IRecordProcessorFactory {
  public class StreamsRecordProcessorFactory implements ShardRecordProcessorFactory {
  
  Change the return signature for createProcessor.
  
  // public IRecordProcessor createProcessor() {
  public ShardRecordProcessor shardRecordProcessor() {
  ```

下面是 3.0 中的记录处理器工厂的示例：

```
package com.amazonaws.codesamples;

import software.amazon.kinesis.processor.ShardRecordProcessor;
import software.amazon.kinesis.processor.ShardRecordProcessorFactory;

public class StreamsRecordProcessorFactory implements ShardRecordProcessorFactory {

    @Override
    public ShardRecordProcessor shardRecordProcessor() {
        return new StreamsRecordProcessor();
    }
}
```

### 步骤 3：迁移工作线程
<a name="step3-worker-migration"></a>

在 KCL 版本 3.0 中，名为**调度器**的新类取代了**工作线程**类。下面是 KCL 1.x 工作线程的示例：

```
final KinesisClientLibConfiguration config = new KinesisClientLibConfiguration(...)
final IRecordProcessorFactory recordProcessorFactory = new RecordProcessorFactory();
final Worker worker = StreamsWorkerFactory.createDynamoDbStreamsWorker(
        recordProcessorFactory,
        workerConfig,
        adapterClient,
        amazonDynamoDB,
        amazonCloudWatchClient);
```

**迁移工作程序**

1. 将 `import` 类的 `Worker` 语句更改为 `Scheduler` 和 `ConfigsBuilder` 类的导入语句。

   ```
   // import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker;
   import software.amazon.kinesis.coordinator.Scheduler;
   import software.amazon.kinesis.common.ConfigsBuilder;
   ```

1. 导入 `StreamTracker` 并将 `StreamsWorkerFactory` 的导入更改为 `StreamsSchedulerFactory`。

   ```
   import software.amazon.kinesis.processor.StreamTracker;
   // import software.amazon.dynamodb.streamsadapter.StreamsWorkerFactory;
   import software.amazon.dynamodb.streamsadapter.StreamsSchedulerFactory;
   ```

1. 选择从中启动应用程序的位置。它可以为 `TRIM_HORIZON` 或 `LATEST`。

   ```
   import software.amazon.kinesis.common.InitialPositionInStream;
   import software.amazon.kinesis.common.InitialPositionInStreamExtended;
   ```

1. 创建一个 `StreamTracker` 实例。

   ```
   StreamTracker streamTracker = StreamsSchedulerFactory.createSingleStreamTracker(
           streamArn,
           InitialPositionInStreamExtended.newInitialPosition(InitialPositionInStream.TRIM_HORIZON)
   );
   ```

1. 创建 `AmazonDynamoDBStreamsAdapterClient` 对象。

   ```
   import software.amazon.dynamodb.streamsadapter.AmazonDynamoDBStreamsAdapterClient; 
   import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
   import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
   
   ...
   
   AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();
   
   AmazonDynamoDBStreamsAdapterClient adapterClient = new AmazonDynamoDBStreamsAdapterClient(
           credentialsProvider, awsRegion);
   ```

1. 创建 `ConfigsBuilder` 对象。

   ```
   import software.amazon.kinesis.common.ConfigsBuilder;
   
   ...
   ConfigsBuilder configsBuilder = new ConfigsBuilder(
                   streamTracker,
                   applicationName,
                   adapterClient,
                   dynamoDbAsyncClient,
                   cloudWatchAsyncClient,
                   UUID.randomUUID().toString(),
                   new StreamsRecordProcessorFactory());
   ```

1. 使用 `ConfigsBuilder` 创建 `Scheduler`，如以下示例所示：

   ```
   import java.util.UUID;
   
   import software.amazon.awssdk.regions.Region;
   import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
   import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
   import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
   
   import software.amazon.kinesis.common.KinesisClientUtil;
   import software.amazon.kinesis.coordinator.Scheduler;
   
   ...
   
                   
   DynamoDbAsyncClient dynamoClient = DynamoDbAsyncClient.builder().region(region).build();
   CloudWatchAsyncClient cloudWatchClient = CloudWatchAsyncClient.builder().region(region).build();
   
                   
   DynamoDBStreamsPollingConfig pollingConfig = new DynamoDBStreamsPollingConfig(adapterClient);
   pollingConfig.idleTimeBetweenReadsInMillis(idleTimeBetweenReadsInMillis);
   
   // Use ConfigsBuilder to configure settings
   RetrievalConfig retrievalConfig = configsBuilder.retrievalConfig();
   retrievalConfig.retrievalSpecificConfig(pollingConfig);
   
   CoordinatorConfig coordinatorConfig = configsBuilder.coordinatorConfig();
   coordinatorConfig.clientVersionConfig(CoordinatorConfig.ClientVersionConfig.CLIENT_VERSION_CONFIG_COMPATIBLE_WITH_2X);
                   
   Scheduler scheduler = StreamsSchedulerFactory.createScheduler(
                   configsBuilder.checkpointConfig(),
                   coordinatorConfig,
                   configsBuilder.leaseManagementConfig(),
                   configsBuilder.lifecycleConfig(),
                   configsBuilder.metricsConfig(),
                   configsBuilder.processorConfig(),
                   retrievalConfig,
                   adapterClient
           );
   ```

**重要**  
`CLIENT_VERSION_CONFIG_COMPATIBLE_WITH_2X` 设置在 KCL v3 与 KCL v1 的 DynamoDB Streams Kinesis Adapter 之间保持兼容性，而未在 KCL v2 和 v3 之间保持兼容性。

### 步骤 4：KCL 3.x 配置概述和建议
<a name="step4-configuration-migration"></a>

有关 KCL 1.x 之后引入的与 KCL 3.x 相关的配置的详细描述，请参阅 [KCL configurations](https://docs.aws.amazon.com//streams/latest/dev/kcl-configuration.html) 和 [KCL migration client configuration](https://docs.aws.amazon.com//streams/latest/dev/kcl-migration.html#client-configuration)。

**重要**  
在 KCL 3.x 及更高版本中，我们建议不要直接创建 `checkpointConfig`、`coordinatorConfig`、`leaseManagementConfig`、`metricsConfig`、`processorConfig` 和 `retrievalConfig` 的对象，而是使用 `ConfigsBuilder` 设置配置，来避免出现调度器初始化问题。`ConfigsBuilder` 提供了更灵活且更易于维护的方式配置 KCL 应用程序。

#### 在 KCL 3.x 中具有更新默认值的配置
<a name="kcl3-configuration-overview"></a>

`billingMode`  
在 KCL 版本 1.x 中，`billingMode` 的默认值设置为 `PROVISIONED`。但在 KCL 版本 3.x 中，默认 `billingMode` 为 `PAY_PER_REQUEST`（按需模式）。我们建议您对租约表使用按需容量模式，以便根据您的使用情况自动调整容量。有关对租约表使用预置容量的指导，请参阅 [Best practices for the lease table with provisioned capacity mode](https://docs.aws.amazon.com//streams/latest/dev/kcl-migration-lease-table.html)。

`idleTimeBetweenReadsInMillis`  
在 KCL 版本 1.x 中，`idleTimeBetweenReadsInMillis` 的默认值设置为 1000（或 1 秒）。KCL 版本 3.x 将 i`dleTimeBetweenReadsInMillis` 的默认值设置为 1500（或 1.5 秒），但 Amazon DynamoDB Streams Kinesis Adapter 将默认值改写为 1000（或 1 秒）。

#### KCL 3.x 中的新配置
<a name="kcl3-new-configs"></a>

`leaseAssignmentIntervalMillis`  
此配置定义在新发现的分片开始处理之前的时间间隔，计算方法为 1.5 × `leaseAssignmentIntervalMillis`。如果未显式配置此设置，则时间间隔默认为 1.5 × `failoverTimeMillis`。处理新分片包括扫描租约表并在租约表上查询全局二级索引（GSI）。降低 `leaseAssignmentIntervalMillis` 会增加这些扫描和查询操作的频率，从而导致 DynamoDB 成本更高。建议将此值设置为 2000（即 2 秒），以尽可能减少处理新分片的延迟。

`shardConsumerDispatchPollIntervalMillis`  
此配置定义了分片使用者用于触发状态转换的连续轮询之间的间隔。在 KCL 版本 1.x 中，此行为由 `idleTimeInMillis` 参数控制，该参数未作为可配置的设置公开。在 KCL 版本 3.x 中，我们建议将此配置设置为与 KCL 版本 1.x 设置中用于 ` idleTimeInMillis` 的值相匹配。

### 步骤 5：从 KCL 2.x 迁移到 KCL 3.x
<a name="step5-kcl2-to-kcl3"></a>

为确保平稳过渡并与最新的 Kinesis Client Library（KCL）版本兼容，请按照迁移指南的 [upgrading from KCL 2.x to KCL 3.x](https://docs.aws.amazon.com//streams/latest/dev/kcl-migration-from-2-3.html#kcl-migration-from-2-3-worker-metrics) 说明中的步骤 5-8 进行操作。

有关常见的 KCL 3.x 故障排除问题，请参阅 [Troubleshooting KCL consumer applications](https://docs.aws.amazon.com//streams/latest/dev/troubleshooting-consumers.html)。

# 回滚至先前 KCL 版本
<a name="kcl-migration-rollback"></a>

本主题介绍如何将使用者应用程序回滚到先前 KCL 版本。回滚过程由两个步骤组成：

1. 运行 [KCL Migration Tool](https://github.com/awslabs/amazon-kinesis-client/blob/master/amazon-kinesis-client/scripts/KclMigrationTool.py)。

1. 重新部署以前的 KCL 版本代码。

## 步骤 1：运行 KCL 迁移工具
<a name="kcl-migration-rollback-step1"></a>

当需要回滚到先前 KCL 版本时，必须运行 KCL 迁移工具。此工具可执行两个重要任务：
+ 它在 DynamoDB 中的租约表上移除一个名为工作线程指标表的元数据表和全局二级索引。这些构件由 KCL 3.x 创建，但在回滚到先前版本时并不需要。
+ 它使所有工作线程均在与 KCL 1.x 兼容的模式下运行，并开始使用先前 KCL 版本中使用的负载均衡算法。如果 KCL 3.x 中的新负载均衡算法存在问题，这将立即缓解问题。

**重要**  
DynamoDB 中的协调器状态表必须存在，并且在迁移、回滚和前滚过程中不得删除。

**注意**  
重要的是，使用者应用程序中的所有工作线程在给定时间均使用相同的负载均衡算法。KCL 迁移工具可确保 KCL 3.x 使用者应用程序中的所有工作线程都切换到 KCL 1.x 兼容模式，以便在应用程序回滚到先前 KCL 版本期间，所有工作线程都运行相同的负载均衡算法。

您可以在 [KCL GitHub 存储库](https://github.com/awslabs/amazon-kinesis-client/tree/master)的 scripts 目录中下载 [KCL Migration Tool](https://github.com/awslabs/amazon-kinesis-client/blob/master/amazon-kinesis-client/scripts/KclMigrationTool.py)。从具有相应权限的工作线程或主机运行脚本，以写入协调器状态表、工作线程指标表和租约表。确保为 KCL 使用者应用程序配置了适当的 [IAM permissions](https://docs.aws.amazon.com/streams/latest/dev/kcl-iam-permissions.html)。使用指定的命令对每个 KCL 应用程序仅运行一次脚本：

```
python3 ./KclMigrationTool.py --region region --mode rollback [--application_name applicationName] [--lease_table_name leaseTableName] [--coordinator_state_table_name coordinatorStateTableName] [--worker_metrics_table_name workerMetricsTableName]
```

### 参数
<a name="kcl-migration-rollback-parameters"></a>

`--region`  
将*区域*替换为您的 AWS 区域。

`--application_name`  
如果您为 DynamoDB 元数据表（租约表、协调器状态表和工作线程指标表）使用默认名称，则需要此参数。如果您为这些表指定了自定义名称，则可以忽略此参数。将 *applicationName* 替换为实际的 KCL 应用程序名称。如果未提供自定义名称，该工具将使用此名称来派生默认表名称。

`--lease_table_name`  
如果您在 KCL 配置中为租约表设置了自定义名称，则需要此参数。如果您使用的是默认表名称，则可以忽略此参数。将 *leaseTableName* 替换为您为租约表指定的自定义表名称。

`--coordinator_state_table_name`  
如果您在 KCL 配置中为协调器状态表设置了自定义名称，则需要此参数。如果您使用的是默认表名称，则可以忽略此参数。将 *coordinatorStateTableName* 替换为您为协调器状态表指定的自定义表名称。

`--worker_metrics_table_name`  
如果您在 KCL 配置中为工作线程指标表设置了自定义名称，则需要此参数。如果您使用的是默认表名称，则可以忽略此参数。将 *workerMetricsTableName* 替换为您为工作线程指标表指定的自定义表名称。

## 步骤 2：使用先前 KCL 版本重新部署代码
<a name="kcl-migration-rollback-step2"></a>

**重要**  
在 KCL 迁移工具生成的输出中提及版本 2.x 的任何内容都应解释为指的是 KCL 版本 1.x。运行该脚本不会执行完全回滚，它只会将负载均衡算法切换到 KCL 版本 1.x 中使用的算法。

运行 KCL 迁移工具来进行回滚后，您将看到以下消息之一：

消息 1  
“回滚已完成。应用程序正在运行 2x 兼容功能。请使用先前 KCL 版本部署代码，以回滚到先前的应用程序二进制文件。”  
**所需操作：**这意味着工作线程正在 KCL 1.x 兼容模式下运行。使用先前 KCL 版本将代码重新部署到工作线程。

消息 2  
“回滚已完成。KCL 应用程序正在运行 3x 功能，并将回滚到 2x 兼容功能。如果您在短时间内看不到缓解，请使用先前 KCL 版本部署代码，回滚到先前的应用程序二进制文件。”  
**所需操作：**这意味着工作线程正在 KCL 3.x 模式下运行，KCL 迁移工具已将所有工作线程切换到 KCL 1.x 兼容模式。使用先前 KCL 版本将代码重新部署到工作线程。

消息 3  
“应用程序已经回滚。任何可以删除的 KCLv3 资源都被清理以免产生费用，直至应用程序可以通过迁移进行前滚。”  
**所需操作：**这意味着工作线程已经回滚到在 KCL 1.x 兼容模式下运行。使用先前 KCL 版本将代码重新部署到工作线程。

# 回滚后前滚到 KCL 3.x
<a name="kcl-migration-rollforward"></a>

本主题介绍如何在回滚后将使用者应用程序前滚到 KCL 3.x。当您需要前滚时，必须完成一个由两步组成的过程：

1. 运行 [KCL Migration Tool](https://github.com/awslabs/amazon-kinesis-client/blob/master/amazon-kinesis-client/scripts/KclMigrationTool.py)。

1. 使用 KCL 3.x 部署代码。

## 步骤 1：运行 KCL 迁移工具
<a name="kcl-migration-rollforward-step1"></a>

使用以下命令运行 KCL 迁移工具，以便前滚到 KCL 3.x：

```
python3 ./KclMigrationTool.py --region region --mode rollforward [--application_name applicationName] [--coordinator_state_table_name coordinatorStateTableName]
```

### 参数
<a name="kcl-migration-rollforward-parameters"></a>

`--region`  
将*区域*替换为您的 AWS 区域。

`--application_name`  
如果您为协调器状态表使用默认名称，则需要此参数。如果您已为协调器状态表指定了自定义名称，则可以忽略此参数。将 *applicationName* 替换为实际的 KCL 应用程序名称。如果未提供自定义名称，该工具将使用此名称来派生默认表名称。

`--coordinator_state_table_name`  
如果您在 KCL 配置中为协调器状态表设置了自定义名称，则需要此参数。如果您使用的是默认表名称，则可以忽略此参数。将 *coordinatorStateTableName* 替换为您为协调器状态表指定的自定义表名称。

在前滚模式下运行迁移工具后，KCL 会创建 KCL 3.x 所需的以下 DynamoDB 资源：
+ 租约表上的全局二级索引
+ 工作线程指标表

## 步骤 2：使用 KCL 3.x 部署代码
<a name="kcl-migration-rollforward-step2"></a>

运行 KCL 迁移工具以进行前滚后，使用 KCL 3.x 将代码部署到工作线程。要完成迁移，请参阅 [Step 8: Complete the migration](https://docs.aws.amazon.com/streams/latest/dev/kcl-migration-from-2-3.html#kcl-migration-from-2-3-finish)。

# 演练：DynamoDB Streams Kinesis Adapter
<a name="Streams.KCLAdapter.Walkthrough"></a>

本节是使用 Amazon Kinesis Client Library 和 Amazon DynamoDB Streams Kinesis Adapter 的 Java 应用程序的演练。此应用程序演示了数据复制示例，其中将一个表中的写入活动应用于另一个表，并且两个表中的内容保持同步。有关源代码，请参阅 [完成程序：DynamoDB Streams Kinesis Adapter](Streams.KCLAdapter.Walkthrough.CompleteProgram.md)。

此程序执行以下操作：

1. 创建名为 `KCL-Demo-src` 和 `KCL-Demo-dst` 的两个 DynamoDB 表。每个表上均启用一个流。

1. 通过添加、更新和删除项目在源表中生成更新活动。这会导致数据写入表的流中。

1. 从流中读取记录、将记录重新构造为 DynamoDB 请求并将请求应用于目标表。

1. 扫描源表和目标表，以确保其内容一致。

1. 通过删除表进行清除。

以下各节将描述这些步骤，本演练结尾将显示完整的应用程序。

**Topics**
+ [第 1 步：创建 DynamoDB 表](#Streams.KCLAdapter.Walkthrough.Step1)
+ [第 2 步：在源表中生成更新活动](#Streams.KCLAdapter.Walkthrough.Step2)
+ [第 3 步：处理流](#Streams.KCLAdapter.Walkthrough.Step3)
+ [第 4 步：确保两个表具有相同的内容](#Streams.KCLAdapter.Walkthrough.Step4)
+ [第 5 步：清理](#Streams.KCLAdapter.Walkthrough.Step5)
+ [完成程序：DynamoDB Streams Kinesis Adapter](Streams.KCLAdapter.Walkthrough.CompleteProgram.md)

## 第 1 步：创建 DynamoDB 表
<a name="Streams.KCLAdapter.Walkthrough.Step1"></a>

第一步是创建两个 DynamoDB 表，一个源表和一个目标表。源表的流上的 `StreamViewType` 为 `NEW_IMAGE`。这意味着无论何时修改此表中的项目，项目“之后”的映像都将写入到流中。这样一来，流将跟踪表上的所有写入活动。

以下示例显示用于创建两个表的代码。

```
java.util.List<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>();
attributeDefinitions.add(new AttributeDefinition().withAttributeName("Id").withAttributeType("N"));

java.util.List<KeySchemaElement> keySchema = new ArrayList<KeySchemaElement>();
keySchema.add(new KeySchemaElement().withAttributeName("Id").withKeyType(KeyType.HASH)); // Partition
                                                                                         // key

ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput().withReadCapacityUnits(2L)
    .withWriteCapacityUnits(2L);

StreamSpecification streamSpecification = new StreamSpecification();
streamSpecification.setStreamEnabled(true);
streamSpecification.setStreamViewType(StreamViewType.NEW_IMAGE);
CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(tableName)
    .withAttributeDefinitions(attributeDefinitions).withKeySchema(keySchema)
    .withProvisionedThroughput(provisionedThroughput).withStreamSpecification(streamSpecification);
```

## 第 2 步：在源表中生成更新活动
<a name="Streams.KCLAdapter.Walkthrough.Step2"></a>

下一步是在源表上生成某些写入活动。在此活动发生时，源表的流也会近乎实时更新。

此应用程序通过调用用于写入数据的 `PutItem`、`UpdateItem` 和 `DeleteItem` API 操作的方法来定义帮助程序类。以下代码示例演示如何使用这些方法。

```
StreamsAdapterDemoHelper.putItem(dynamoDBClient, tableName, "101", "test1");
StreamsAdapterDemoHelper.updateItem(dynamoDBClient, tableName, "101", "test2");
StreamsAdapterDemoHelper.deleteItem(dynamoDBClient, tableName, "101");
StreamsAdapterDemoHelper.putItem(dynamoDBClient, tableName, "102", "demo3");
StreamsAdapterDemoHelper.updateItem(dynamoDBClient, tableName, "102", "demo4");
StreamsAdapterDemoHelper.deleteItem(dynamoDBClient, tableName, "102");
```

## 第 3 步：处理流
<a name="Streams.KCLAdapter.Walkthrough.Step3"></a>

现在，此程序开始处理流。DynamoDB Streams Kinesis Adapter 充当 KCL 和 DynamoDB Streams 端点之间的透明层，以便代码可充分利用 KCL 而不必进行低级 DynamoDB Streams 调用。此程序执行以下任务：
+ 它通过符合 KCL 接口定义的方法 (`StreamsRecordProcessor`、`initialize` 和 `processRecords`) 定义记录处理器类 `shutdown`。`processRecords` 方法包含从源表的流中进行读取以及对目标表进行写入时所需的逻辑。
+ 它定义了记录处理器类的类工厂 (`StreamsRecordProcessorFactory`)。这是使用 KCL 的 Java 程序所需的。
+ 它实例化一个新的 KCL `Worker`，它与类工厂关联。
+ 当记录处理完成时，它会关闭 `Worker`。

或者，在 Streams KCL Adapter 配置中启用追赶模式，以便在流处理滞后超过一分钟（默认值）时，自动将 GetRecords API 调用速率扩展 3 倍（默认值），从而有助于流使用者处理表中的高吞吐量峰值。

要了解有关 KCL 接口定义的详细信息，请参阅 *Amazon Kinesis Data Streams 开发人员指南*的[使用 Kinesis 客户端库开发使用者](https://docs.aws.amazon.com/kinesis/latest/dev/developing-consumers-with-kcl.html)。

以下代码示例演示了 `StreamsRecordProcessor` 中的主循环。`case` 语句基于流记录中显示的 `OperationType` 来确定要执行的操作。

```
for (Record record : records) {
    String data = new String(record.getData().array(), Charset.forName("UTF-8"));
    System.out.println(data);
    if (record instanceof RecordAdapter) {
                software.amazon.dynamodb.model.Record streamRecord = ((RecordAdapter) record)
                    .getInternalObject();

                switch (streamRecord.getEventName()) {
                    case "INSERT":
                    case "MODIFY":
                        StreamsAdapterDemoHelper.putItem(dynamoDBClient, tableName,
                            streamRecord.getDynamodb().getNewImage());
                        break;
                    case "REMOVE":
                        StreamsAdapterDemoHelper.deleteItem(dynamoDBClient, tableName,
                            streamRecord.getDynamodb().getKeys().get("Id").getN());
                }
    }
    checkpointCounter += 1;
    if (checkpointCounter % 10 == 0) {
        try {
            checkpointer.checkpoint();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

## 第 4 步：确保两个表具有相同的内容
<a name="Streams.KCLAdapter.Walkthrough.Step4"></a>

此时，源表和目标表的内容是同步的。此应用程序针对两个表发送 `Scan` 请求以验证其内容是否实质相同。

`DemoHelper` 类包含调用低级 `ScanTable` API 的 `Scan` 方法。下例说明具体用法。

```
if (StreamsAdapterDemoHelper.scanTable(dynamoDBClient, srcTable).getItems()
    .equals(StreamsAdapterDemoHelper.scanTable(dynamoDBClient, destTable).getItems())) {
    System.out.println("Scan result is equal.");
}
else {
    System.out.println("Tables are different!");
}
```

## 第 5 步：清理
<a name="Streams.KCLAdapter.Walkthrough.Step5"></a>

演示完成后，此应用程序将删除源表和目标表。请看下面的代码示例。甚至在删除两个表后，其流也可在自动删除后的最多 24 个小时内保持可用。

```
dynamoDBClient.deleteTable(new DeleteTableRequest().withTableName(srcTable));
dynamoDBClient.deleteTable(new DeleteTableRequest().withTableName(destTable));
```

# 完成程序：DynamoDB Streams Kinesis Adapter
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram"></a>

下文是执行 [演练：DynamoDB Streams Kinesis Adapter](Streams.KCLAdapter.Walkthrough.md) 所述任务的完整 Java 程序。当您运行该程序时，将显示与以下内容类似的输出。

```
Creating table KCL-Demo-src
Creating table KCL-Demo-dest
Table is active.
Creating worker for stream: arn:aws:dynamodb:us-west-2:111122223333:table/KCL-Demo-src/stream/2015-05-19T22:48:56.601
Starting worker...
Scan result is equal.
Done.
```

**重要**  
 要运行此程序，请确保客户端应用程序可以使用策略来访问 DynamoDB 和 Amazon CloudWatch。有关更多信息，请参阅 [适用于 DynamoDB 的基于身份的策略](security_iam_service-with-iam.md#security_iam_service-with-iam-id-based-policies)。

源代码包括四个 `.java` 文件。要构建此程序，请添加以下依赖项，其中包括作为传递依赖项的 Amazon Kinesis Client Library（KCL）3.x 和适用于 Java 的 AWS SDK v2：

------
#### [ Maven ]

```
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>dynamodb-streams-kinesis-adapter</artifactId>
    <version>2.1.0</version>
</dependency>
```

------
#### [ Gradle ]

```
implementation 'com.amazonaws:dynamodb-streams-kinesis-adapter:2.1.0'
```

------

源文件为：
+ `StreamsAdapterDemo.java`
+ `StreamsRecordProcessor.java`
+ `StreamsRecordProcessorFactory.java`
+ `StreamsAdapterDemoHelper.java`

## StreamsAdapterDemo.java
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram.StreamsAdapterDemo"></a>

```
package com.amazonaws.codesamples;

import com.amazonaws.services.dynamodbv2.streamsadapter.AmazonDynamoDBStreamsAdapterClient;
import com.amazonaws.services.dynamodbv2.streamsadapter.StreamsSchedulerFactory;
import com.amazonaws.services.dynamodbv2.streamsadapter.polling.DynamoDBStreamsPollingConfig;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.kinesis.common.ConfigsBuilder;
import software.amazon.kinesis.common.InitialPositionInStream;
import software.amazon.kinesis.common.InitialPositionInStreamExtended;
import software.amazon.kinesis.coordinator.Scheduler;
import software.amazon.kinesis.processor.ShardRecordProcessorFactory;
import software.amazon.kinesis.processor.StreamTracker;
import software.amazon.kinesis.retrieval.RetrievalConfig;

public class StreamsAdapterDemo {

    private static DynamoDbAsyncClient dynamoDbAsyncClient;
    private static CloudWatchAsyncClient cloudWatchAsyncClient;
    private static AmazonDynamoDBStreamsAdapterClient amazonDynamoDbStreamsAdapterClient;

    private static String tablePrefix = "KCL-Demo";
    private static String streamArn;

    private static Region region = Region.US_EAST_1;
    private static AwsCredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();

    public static void main( String[] args ) throws Exception {
        System.out.println("Starting demo...");
        dynamoDbAsyncClient = DynamoDbAsyncClient.builder()
                .credentialsProvider(credentialsProvider)
                .region(region)
                .build();
        cloudWatchAsyncClient = CloudWatchAsyncClient.builder()
                .credentialsProvider(credentialsProvider)
                .region(region)
                .build();
        amazonDynamoDbStreamsAdapterClient = new AmazonDynamoDBStreamsAdapterClient(credentialsProvider, region);

        String srcTable = tablePrefix + "-src";
        String destTable = tablePrefix + "-dest";

        setUpTables();

        StreamTracker streamTracker = StreamsSchedulerFactory.createSingleStreamTracker(streamArn,
                InitialPositionInStreamExtended.newInitialPosition(InitialPositionInStream.TRIM_HORIZON));

        ShardRecordProcessorFactory shardRecordProcessorFactory =
                new StreamsAdapterDemoProcessorFactory(dynamoDbAsyncClient, destTable);

        ConfigsBuilder configsBuilder = new ConfigsBuilder(
                streamTracker,
                "streams-adapter-demo",
                amazonDynamoDbStreamsAdapterClient,
                dynamoDbAsyncClient,
                cloudWatchAsyncClient,
                "streams-demo-worker",
                shardRecordProcessorFactory
        );

        DynamoDBStreamsPollingConfig pollingConfig = new DynamoDBStreamsPollingConfig(amazonDynamoDbStreamsAdapterClient);
        RetrievalConfig retrievalConfig = configsBuilder.retrievalConfig();
        retrievalConfig.retrievalSpecificConfig(pollingConfig);

        System.out.println("Creating scheduler for stream " + streamArn);
        Scheduler scheduler = StreamsSchedulerFactory.createScheduler(
                configsBuilder.checkpointConfig(),
                configsBuilder.coordinatorConfig(),
                configsBuilder.leaseManagementConfig(),
                configsBuilder.lifecycleConfig(),
                configsBuilder.metricsConfig(),
                configsBuilder.processorConfig(),
                retrievalConfig,
                amazonDynamoDbStreamsAdapterClient
        );

        System.out.println("Starting scheduler...");
        Thread t = new Thread(scheduler);
        t.start();

        Thread.sleep(250000);

        System.out.println("Stopping scheduler...");
        scheduler.shutdown();
        t.join();

        if (StreamsAdapterDemoHelper.scanTable(dynamoDbAsyncClient, srcTable).items()
                .equals(StreamsAdapterDemoHelper.scanTable(dynamoDbAsyncClient, destTable).items())) {
            System.out.println("Scan result is equal.");
        } else {
            System.out.println("Tables are different!");
        }

        System.out.println("Done.");
        cleanupAndExit(0);
    }

    private static void setUpTables() {
        String srcTable = tablePrefix + "-src";
        String destTable = tablePrefix + "-dest";
        streamArn = StreamsAdapterDemoHelper.createTable(dynamoDbAsyncClient, srcTable);
        StreamsAdapterDemoHelper.createTable(dynamoDbAsyncClient, destTable);

        awaitTableCreation(srcTable);

        performOps(srcTable);
    }

    private static void awaitTableCreation(String tableName) {
        Integer retries = 0;
        Boolean created = false;
        while (!created && retries < 100) {
            DescribeTableResponse result = StreamsAdapterDemoHelper.describeTable(dynamoDbAsyncClient, tableName);
            created = result.table().tableStatusAsString().equals("ACTIVE");
            if (created) {
                System.out.println("Table is active.");
                return;
            } else {
                retries++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
        }
        System.out.println("Timeout after table creation. Exiting...");
        cleanupAndExit(1);
    }

    private static void performOps(String tableName) {
        StreamsAdapterDemoHelper.putItem(dynamoDbAsyncClient, tableName, "101", "test1");
        StreamsAdapterDemoHelper.updateItem(dynamoDbAsyncClient, tableName, "101", "test2");
        StreamsAdapterDemoHelper.deleteItem(dynamoDbAsyncClient, tableName, "101");
        StreamsAdapterDemoHelper.putItem(dynamoDbAsyncClient, tableName, "102", "demo3");
        StreamsAdapterDemoHelper.updateItem(dynamoDbAsyncClient, tableName, "102", "demo4");
        StreamsAdapterDemoHelper.deleteItem(dynamoDbAsyncClient, tableName, "102");
    }

    private static void cleanupAndExit(Integer returnValue) {
        String srcTable = tablePrefix + "-src";
        String destTable = tablePrefix + "-dest";
        dynamoDbAsyncClient.deleteTable(DeleteTableRequest.builder().tableName(srcTable).build());
        dynamoDbAsyncClient.deleteTable(DeleteTableRequest.builder().tableName(destTable).build());
        System.exit(returnValue);
    }
}
```

## StreamsRecordProcessor.java
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram.StreamsRecordProcessor"></a>

```
package com.amazonaws.codesamples;

import com.amazonaws.services.dynamodbv2.streamsadapter.adapter.DynamoDBStreamsClientRecord;
import com.amazonaws.services.dynamodbv2.streamsadapter.model.DynamoDBStreamsProcessRecordsInput;
import com.amazonaws.services.dynamodbv2.streamsadapter.processor.DynamoDBStreamsShardRecordProcessor;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.Record;
import software.amazon.kinesis.exceptions.InvalidStateException;
import software.amazon.kinesis.exceptions.ShutdownException;
import software.amazon.kinesis.lifecycle.events.InitializationInput;
import software.amazon.kinesis.lifecycle.events.LeaseLostInput;
import software.amazon.kinesis.lifecycle.events.ShardEndedInput;
import software.amazon.kinesis.lifecycle.events.ShutdownRequestedInput;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class StreamsRecordProcessor implements DynamoDBStreamsShardRecordProcessor {

    private Integer checkpointCounter;

    private final DynamoDbAsyncClient dynamoDbAsyncClient;
    private final String tableName;

    public StreamsRecordProcessor(DynamoDbAsyncClient dynamoDbAsyncClient, String tableName) {
        this.dynamoDbAsyncClient = dynamoDbAsyncClient;
        this.tableName = tableName;
    }

    @Override
    public void initialize(InitializationInput initializationInput) {
        this.checkpointCounter = 0;
    }

    @Override
    public void processRecords(DynamoDBStreamsProcessRecordsInput dynamoDBStreamsProcessRecordsInput) {
        for (DynamoDBStreamsClientRecord record: dynamoDBStreamsProcessRecordsInput.records()) {
            String data = new String(record.data().array(), StandardCharsets.UTF_8);
            System.out.println(data);
            Record streamRecord = record.getRecord();

            switch (streamRecord.eventName()) {
                case INSERT:
                case MODIFY:
                    StreamsAdapterDemoHelper.putItem(dynamoDbAsyncClient, tableName,
                            streamRecord.dynamodb().newImage());
                case REMOVE:
                    StreamsAdapterDemoHelper.deleteItem(dynamoDbAsyncClient, tableName,
                            streamRecord.dynamodb().keys().get("Id").n());
            }
            checkpointCounter += 1;
            if (checkpointCounter % 10 == 0) {
                try {
                    dynamoDBStreamsProcessRecordsInput.checkpointer().checkpoint();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void leaseLost(LeaseLostInput leaseLostInput) {
        System.out.println("Lease Lost");
    }

    @Override
    public void shardEnded(ShardEndedInput shardEndedInput) {
        try {
            shardEndedInput.checkpointer().checkpoint();
        } catch (ShutdownException | InvalidStateException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void shutdownRequested(ShutdownRequestedInput shutdownRequestedInput) {
        try {
            shutdownRequestedInput.checkpointer().checkpoint();
        } catch (ShutdownException | InvalidStateException e) {
            e.printStackTrace();
        }
    }
}
```

## StreamsRecordProcessorFactory.java
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram.StreamsRecordProcessorFactory"></a>

```
package com.amazonaws.codesamples;

import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.kinesis.processor.ShardRecordProcessor;
import software.amazon.kinesis.processor.ShardRecordProcessorFactory;

public class StreamsAdapterDemoProcessorFactory implements ShardRecordProcessorFactory {
    private final String tableName;
    private final DynamoDbAsyncClient dynamoDbAsyncClient;

    public StreamsAdapterDemoProcessorFactory(DynamoDbAsyncClient asyncClient, String tableName) {
        this.tableName = tableName;
        this.dynamoDbAsyncClient = asyncClient;
    }

    @Override
    public ShardRecordProcessor shardRecordProcessor() {
        return new StreamsRecordProcessor(dynamoDbAsyncClient, tableName);
    }
}
```

## StreamsAdapterDemoHelper.java
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram.StreamsAdapterDemoHelper"></a>

```
package com.amazonaws.codesamples;

import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.BillingMode;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.OnDemandThroughput;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
import software.amazon.awssdk.services.dynamodb.model.StreamSpecification;
import software.amazon.awssdk.services.dynamodb.model.StreamViewType;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class StreamsAdapterDemoHelper {

    /**
     * @return StreamArn
     */
    public static String createTable(DynamoDbAsyncClient client, String tableName) {
        List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
        attributeDefinitions.add(AttributeDefinition.builder()
                .attributeName("Id")
                .attributeType("N")
                .build());

        List<KeySchemaElement> keySchema = new ArrayList<>();
        keySchema.add(KeySchemaElement.builder()
                .attributeName("Id")
                .keyType(KeyType.HASH) // Partition key
                .build());

        StreamSpecification streamSpecification = StreamSpecification.builder()
                .streamEnabled(true)
                .streamViewType(StreamViewType.NEW_IMAGE)
                .build();

        CreateTableRequest createTableRequest = CreateTableRequest.builder()
                .tableName(tableName)
                .attributeDefinitions(attributeDefinitions)
                .keySchema(keySchema)
                .billingMode(BillingMode.PAY_PER_REQUEST)
                .streamSpecification(streamSpecification)
                .build();

        try {
            System.out.println("Creating table " + tableName);
            CreateTableResponse result = client.createTable(createTableRequest).join();
            return result.tableDescription().latestStreamArn();
        } catch (Exception e) {
            if (e.getCause() instanceof ResourceInUseException) {
                System.out.println("Table already exists.");
                return describeTable(client, tableName).table().latestStreamArn();
            }
            throw e;
        }
    }

    public static DescribeTableResponse describeTable(DynamoDbAsyncClient client, String tableName) {
        return client.describeTable(DescribeTableRequest.builder()
                        .tableName(tableName)
                        .build())
                .join();
    }

    public static ScanResponse scanTable(DynamoDbAsyncClient dynamoDbClient, String tableName) {
        return dynamoDbClient.scan(ScanRequest.builder()
                        .tableName(tableName)
                        .build())
                .join();
    }

    public static void putItem(DynamoDbAsyncClient dynamoDbClient, String tableName, String id, String val) {
        Map<String, AttributeValue> item = new HashMap<>();
        item.put("Id", AttributeValue.builder().n(id).build());
        item.put("attribute-1", AttributeValue.builder().s(val).build());

        putItem(dynamoDbClient, tableName, item);
    }

    public static void putItem(DynamoDbAsyncClient dynamoDbClient, String tableName,
                               Map<String, AttributeValue> items) {
        PutItemRequest putItemRequest = PutItemRequest.builder()
                .tableName(tableName)
                .item(items)
                .build();
        dynamoDbClient.putItem(putItemRequest).join();
    }

    public static void updateItem(DynamoDbAsyncClient dynamoDbClient, String tableName, String id, String val) {
        Map<String, AttributeValue> key = new HashMap<>();
        key.put("Id", AttributeValue.builder().n(id).build());

        Map<String, String> expressionAttributeNames = new HashMap<>();
        expressionAttributeNames.put("#attr2", "attribute-2");

        Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
        expressionAttributeValues.put(":val", AttributeValue.builder().s(val).build());

        UpdateItemRequest updateItemRequest = UpdateItemRequest.builder()
                .tableName(tableName)
                .key(key)
                .updateExpression("SET #attr2 = :val")
                .expressionAttributeNames(expressionAttributeNames)
                .expressionAttributeValues(expressionAttributeValues)
                .build();

        dynamoDbClient.updateItem(updateItemRequest).join();
    }

    public static void deleteItem(DynamoDbAsyncClient dynamoDbClient, String tableName, String id) {
        Map<String, AttributeValue> key = new HashMap<>();
        key.put("Id", AttributeValue.builder().n(id).build());

        DeleteItemRequest deleteItemRequest = DeleteItemRequest.builder()
                .tableName(tableName)
                .key(key)
                .build();
        dynamoDbClient.deleteItem(deleteItemRequest).join();
    }
}
```

# DynamoDB Streams 低级 API：Java 示例
<a name="Streams.LowLevel.Walkthrough"></a>

**注意**  
本页上的代码并不详尽，不会处理使用 Amazon DynamoDB Streams 的所有场景。建议利用 Kinesis Client Library (KCL) 通过 Amazon Kinesis Adapter 使用 DynamoDB 中的流记录，如 [使用 DynamoDB Streams Kinesis Adapter 处理流记录](Streams.KCLAdapter.md) 中所述。

本节包含一个演示 DynamoDB Streams 的实际运用的 Java 程序。此程序执行以下操作：

1. 创建一个启用了流的 DynamoDB 表。

1. 描述此表的流设置。

1. 修改表中的数据。

1. 描述流中的分片。

1. 从分片中读取流记录。

1. 获取子分片并继续读取记录。

1. 清理。

当您运行此程序时，将显示与以下内容类似的输出。

```
Testing Streams Demo
Creating an Amazon DynamoDB table TestTableForStreams with a simple primary key: Id
Waiting for TestTableForStreams to be created...
Current stream ARN for TestTableForStreams: arn:aws:dynamodb:us-east-2:123456789012:table/TestTableForStreams/stream/2018-03-20T16:49:55.208
Stream enabled: true
Update view type: NEW_AND_OLD_IMAGES

Performing write activities on TestTableForStreams
Processing item 1 of 100
Processing item 2 of 100
Processing item 3 of 100
...
Processing item 100 of 100
Shard: {ShardId: shardId-1234567890-...,SequenceNumberRange: {StartingSequenceNumber: 100002572486797508907,},}
    Shard iterator: EjYFEkX2a26eVTWe...
        StreamRecord(ApproximateCreationDateTime=2025-04-09T13:11:58Z, Keys={Id=AttributeValue(S=4)}, NewImage={Message=AttributeValue(S=New Item!), Id=AttributeValue(S=4)}, SequenceNumber=2000001584047545833909, SizeBytes=22, StreamViewType=NEW_AND_OLD_IMAGES)
        StreamRecord(ApproximateCreationDateTime=2025-04-09T13:11:58Z, Keys={Id=AttributeValue(S=4)}, NewImage={Message=AttributeValue(S=This is an updated item), Id=AttributeValue(S=4)}, OldImage={Message=AttributeValue(S=New Item!), Id=AttributeValue(S=4)}, SequenceNumber=2100003604869767892701, SizeBytes=55, StreamViewType=NEW_AND_OLD_IMAGES)
        StreamRecord(ApproximateCreationDateTime=2025-04-09T13:11:58Z, Keys={Id=AttributeValue(S=4)}, OldImage={Message=AttributeValue(S=This is an updated item), Id=AttributeValue(S=4)}, SequenceNumber=2200001099771112898434, SizeBytes=36, StreamViewType=NEW_AND_OLD_IMAGES)
...
Deleting the table...
Table StreamsDemoTable deleted.
Demo complete
```

**Example 示例**  

```
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
import software.amazon.awssdk.services.dynamodb.model.BillingMode;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeStreamRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeStreamResponse;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.GetRecordsRequest;
import software.amazon.awssdk.services.dynamodb.model.GetRecordsResponse;
import software.amazon.awssdk.services.dynamodb.model.GetShardIteratorRequest;
import software.amazon.awssdk.services.dynamodb.model.GetShardIteratorResponse;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.Record;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.Shard;
import software.amazon.awssdk.services.dynamodb.model.ShardFilter;
import software.amazon.awssdk.services.dynamodb.model.ShardFilterType;
import software.amazon.awssdk.services.dynamodb.model.ShardIteratorType;
import software.amazon.awssdk.services.dynamodb.model.StreamSpecification;
import software.amazon.awssdk.services.dynamodb.model.TableDescription;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.streams.DynamoDbStreamsClient;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;

public class StreamsLowLevelDemo {


    public static void main(String[] args) {
        final String usage = "Testing Streams Demo";
        try {
            System.out.println(usage);

            String tableName = "StreamsDemoTable";
            String key = "Id";
            System.out.println("Creating an Amazon DynamoDB table " + tableName + " with a simple primary key: " + key);
            Region region = Region.US_WEST_2;
            DynamoDbClient ddb = DynamoDbClient.builder()
                    .region(region)
                    .build();

            DynamoDbStreamsClient ddbStreams = DynamoDbStreamsClient.builder()
                    .region(region)
                    .build();
            DescribeTableRequest describeTableRequest = DescribeTableRequest.builder()
                    .tableName(tableName)
                    .build();
            TableDescription tableDescription = null;
            try{
                tableDescription = ddb.describeTable(describeTableRequest).table();
            }catch (Exception e){
                System.out.println("Table " + tableName + " does not exist.");
                tableDescription = createTable(ddb, tableName, key);
            }

            // Print the stream settings for the table
            String streamArn = tableDescription.latestStreamArn();
           
            StreamSpecification streamSpec = tableDescription.streamSpecification();
            System.out.println("Current stream ARN for " + tableDescription.tableName() + ": " +
                   streamArn);
            System.out.println("Stream enabled: " + streamSpec.streamEnabled());
            System.out.println("Update view type: " + streamSpec.streamViewType());
            System.out.println();
            // Generate write activity in the table
            System.out.println("Performing write activities on " + tableName);
            int maxItemCount = 100;
            for (Integer i = 1; i <= maxItemCount; i++) {
                System.out.println("Processing item " + i + " of " + maxItemCount);
                // Write a new item
                putItemInTable(key, i, tableName, ddb);
                // Update the item
                updateItemInTable(key, i, tableName, ddb);
                // Delete the item
                deleteDynamoDBItem(key, i, tableName, ddb);
            }

            // Process Stream
            processStream(streamArn, maxItemCount, ddb, ddbStreams, tableName);

            // Delete the table
            System.out.println("Deleting the table...");
            DeleteTableRequest deleteTableRequest = DeleteTableRequest.builder()
                    .tableName(tableName)
                    .build();
            ddb.deleteTable(deleteTableRequest);
            System.out.println("Table " + tableName + " deleted.");
            System.out.println("Demo complete");
            ddb.close();
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }
    }

    private static void processStream(String streamArn, int maxItemCount, DynamoDbClient ddb, DynamoDbStreamsClient ddbStreams, String tableName) {
        // Get all the shard IDs from the stream. Note that DescribeStream returns
        // the shard IDs one page at a time.
        String lastEvaluatedShardId = null;
        do {
            DescribeStreamRequest describeStreamRequest = DescribeStreamRequest.builder()
                    .streamArn(streamArn)
                    .exclusiveStartShardId(lastEvaluatedShardId).build();
            DescribeStreamResponse describeStreamResponse = ddbStreams.describeStream(describeStreamRequest);

            List<Shard> shards = describeStreamResponse.streamDescription().shards();

            // Process each shard on this page

            fetchShardsAndReadRecords(streamArn, maxItemCount, ddbStreams, shards);

            // If LastEvaluatedShardId is set, then there is
            // at least one more page of shard IDs to retrieve
            lastEvaluatedShardId = describeStreamResponse.streamDescription().lastEvaluatedShardId();

        } while (lastEvaluatedShardId != null);

    }

    private static void fetchShardsAndReadRecords(String streamArn, int maxItemCount, DynamoDbStreamsClient ddbStreams, List<Shard> shards) {
        for (Shard shard : shards) {
            String shardId = shard.shardId();
            System.out.println("Shard: " + shard);

            // Get an iterator for the current shard
            GetShardIteratorRequest shardIteratorRequest = GetShardIteratorRequest.builder()
                    .streamArn(streamArn).shardId(shardId)
                    .shardIteratorType(ShardIteratorType.TRIM_HORIZON).build();

            GetShardIteratorResponse getShardIteratorResult = ddbStreams.getShardIterator(shardIteratorRequest);

            String currentShardIter = getShardIteratorResult.shardIterator();

            // Shard iterator is not null until the Shard is sealed (marked as READ_ONLY).
            // To prevent running the loop until the Shard is sealed, we process only the
            // items that were written into DynamoDB and then exit.
            int processedRecordCount = 0;
            while (currentShardIter != null && processedRecordCount < maxItemCount) {
                // Use the shard iterator to read the stream records
                GetRecordsRequest getRecordsRequest = GetRecordsRequest.builder()
                        .shardIterator(currentShardIter).build();
                GetRecordsResponse getRecordsResult = ddbStreams.getRecords(getRecordsRequest);
                List<Record> records = getRecordsResult.records();
                for (Record record : records) {
                    System.out.println("        " + record.dynamodb());
                }
                processedRecordCount += records.size();
                currentShardIter = getRecordsResult.nextShardIterator();
            }
            if (currentShardIter == null){
                System.out.println("Shard has been fully processed. Shard iterator is null.");
                System.out.println("Fetch the child shard to continue processing instead of bulk fetching all shards");
                DescribeStreamRequest describeStreamRequestForChildShards = DescribeStreamRequest.builder()
                        .streamArn(streamArn)
                        .shardFilter(ShardFilter.builder()
                                .type(ShardFilterType.CHILD_SHARDS)
                                .shardId(shardId).build())
                        .build();
                DescribeStreamResponse describeStreamResponseChildShards = ddbStreams.describeStream(describeStreamRequestForChildShards);
                fetchShardsAndReadRecords(streamArn, maxItemCount, ddbStreams, describeStreamResponseChildShards.streamDescription().shards());
            }
        }
    }

    private static void putItemInTable(String key, Integer i, String tableName, DynamoDbClient ddb) {
        Map<String, AttributeValue> item = new HashMap<>();
        item.put(key, AttributeValue.builder()
                .s(i.toString())
                .build());
        item.put("Message", AttributeValue.builder()
                .s("New Item!")
                .build());
        PutItemRequest request = PutItemRequest.builder()
                .tableName(tableName)
                .item(item)
                .build();
        ddb.putItem(request);
    }

    private static void updateItemInTable(String key, Integer i, String tableName, DynamoDbClient ddb) {

        HashMap<String, AttributeValue> itemKey = new HashMap<>();
        itemKey.put(key, AttributeValue.builder()
                .s(i.toString())
                .build());


        HashMap<String, AttributeValueUpdate> updatedValues = new HashMap<>();
        updatedValues.put("Message", AttributeValueUpdate.builder()
                .value(AttributeValue.builder().s("This is an updated item").build())
                .action(AttributeAction.PUT)
                .build());

        UpdateItemRequest request = UpdateItemRequest.builder()
                .tableName(tableName)
                .key(itemKey)
                .attributeUpdates(updatedValues)
                .build();
        ddb.updateItem(request);
    }

    public static void deleteDynamoDBItem(String key, Integer i, String tableName, DynamoDbClient ddb) {
        HashMap<String, AttributeValue> keyToGet = new HashMap<>();
        keyToGet.put(key, AttributeValue.builder()
                .s(i.toString())
                .build());

        DeleteItemRequest deleteReq = DeleteItemRequest.builder()
                .tableName(tableName)
                .key(keyToGet)
                .build();
        ddb.deleteItem(deleteReq);
    }

    public static TableDescription createTable(DynamoDbClient ddb, String tableName, String key) {
        DynamoDbWaiter dbWaiter = ddb.waiter();
        StreamSpecification streamSpecification = StreamSpecification.builder()
                .streamEnabled(true)
                .streamViewType("NEW_AND_OLD_IMAGES")
                .build();
        CreateTableRequest request = CreateTableRequest.builder()
                .attributeDefinitions(AttributeDefinition.builder()
                        .attributeName(key)
                        .attributeType(ScalarAttributeType.S)
                        .build())
                .keySchema(KeySchemaElement.builder()
                        .attributeName(key)
                        .keyType(KeyType.HASH)
                        .build())
                .billingMode(BillingMode.PAY_PER_REQUEST) //  DynamoDB automatically scales based on traffic.
                .tableName(tableName)
                .streamSpecification(streamSpecification)
                .build();

        TableDescription newTable;
        try {
            CreateTableResponse response = ddb.createTable(request);
            DescribeTableRequest tableRequest = DescribeTableRequest.builder()
                    .tableName(tableName)
                    .build();
                    
            System.out.println("Waiting for " + tableName + " to be created...");

            // Wait until the Amazon DynamoDB table is created.
            WaiterResponse<DescribeTableResponse> waiterResponse = dbWaiter.waitUntilTableExists(tableRequest);
            waiterResponse.matched().response().ifPresent(System.out::println);
            newTable = response.tableDescription();
            return newTable;

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        return null;
    }



}
```

# DynamoDB Streams 和 AWS Lambda 触发器
<a name="Streams.Lambda"></a>

Amazon DynamoDB 与 AWS Lambda 集成，使您能够创建*触发器*，自动响应 DynamoDB Streams 中的事件的代码片段。利用触发器，您可以创建应对 DynamoDB 表中的数据修改的应用程序。

**Topics**
+ [教程 1：对 Amazon DynamoDB 使用筛选器处理所有事件，以及对 AWS Lambda 使用 AWS CLI 进行处理](Streams.Lambda.Tutorial.md)
+ [教程 2：对 DynamoDB 和 Lambda 使用筛选器来处理部分事件。](Streams.Lambda.Tutorial2.md)
+ [将 DynamoDB Streams 与 Lambda 配合使用的最佳实践](Streams.Lambda.BestPracticesWithDynamoDB.md)

如果您在表中启用 DynamoDB Streams，则可以将流 Amazon 资源名称（ARN）与您编写的 AWS Lambda 函数关联起来。然后，对该 DynamoDB 表执行的所有变更操作都可以作为流中的项目捕获。例如，您可以设置触发器。这样，在修改了表中的某个项目时，该表的流中会立即出现一条新记录。

**注意**  
如果您将两个以上的 Lambda 函数订阅到一个 DynamoDB 流，则可能会发生读取节流。

[AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html) 服务每秒轮询四次流来查找新记录。在有新的流记录可用时，将同步调用您的 Lambda 函数。同一个 DynamoDB 流上最多只能有两个 Lambda 函数订阅。如果您将两个以上的 Lambda 函数订阅到同一个 DynamoDB 流，则可能会发生读取节流。

Lambda 函数可以发送通知、启动工作流或执行您指定的任意操作。您可以编写一个仅将每个流记录复制到持久性存储（如 Amazon S3 文件网关 (Amazon S3)）中的 Lambda 函数，从而为您表中的写入活动创建永久审计跟踪。或者，假设您有一个写入到 `GameScores` 表的移动游戏应用程序。每当更新 `TopScore` 表的 `GameScores` 属性时，一个相应的流记录将被写入该表的流。然后，此事件会触发一个 Lambda 函数，该函数会在社交媒体网络上发布一条祝贺消息。此函数也可以编写为忽略以下任何流记录：不是对 `GameScores` 的更新，或者未修改 `TopScore` 属性。

如果您的函数返回错误，则 Lambda 将重试批处理，直到它成功处理或数据过期。还可以将 Lambda 配置为以较小批处理进行重试、限制重试次数、在记录变得过旧时丢弃以及其它选项。

作为性能最佳实践，Lambda 函数需要短时间运行。为避免引入不必要的处理延迟，它也不应执行复杂的逻辑。特别是对于高速流，最好是触发异步后处理 Step Function 工作流，而不是长时间运行的 Lambda 函数。

 您可以通过在 DynamoDB 流上配置基于资源的策略来跨不同的 AWS 账户使用 Lambda 触发器，以授予对 Lambda 函数的跨账户读取权限。要了解有关如何配置流以支持跨账户访问的更多信息，请参阅《DynamoDB 开发人员指南》中的[与跨账户 AWS Lambda 函数共享访问权限](rbac-cross-account-access.md#shared-access-cross-acount-lambda)。

有关 AWS Lambda 的更多信息，请参阅《AWS Lambda 开发人员指南》[https://docs.aws.amazon.com/lambda/latest/dg/](https://docs.aws.amazon.com/lambda/latest/dg/)。

# 教程 1：对 Amazon DynamoDB 使用筛选器处理所有事件，以及对 AWS Lambda 使用 AWS CLI 进行处理
<a name="Streams.Lambda.Tutorial"></a>

 

在本教程中，您将创建 AWS Lambda 触发器以处理来自 DynamoDB 表的流。

**Topics**
+ [第 1 步：创建一个启用了流的 DynamoDB 表](#Streams.Lambda.Tutorial.CreateTable)
+ [第 2 步：创建一个 Lambda 执行角色](#Streams.Lambda.Tutorial.CreateRole)
+ [第 3 步：创建一个 Amazon SNS 主题](#Streams.Lambda.Tutorial.SNSTopic)
+ [第 4 步：创建并测试一个 Lambda 函数](#Streams.Lambda.Tutorial.LambdaFunction)
+ [第 5 步：创建并测试一个触发器](#Streams.Lambda.Tutorial.CreateTrigger)

本教程的场景就是 Woofer 这个简单的社交网络。Woofer 用户使用发送给其他 Woofer 用户的 *bark*（短文本消息）进行通信。下图显示了此应用程序的组件和工作流。

![\[DynamoDB 表、流记录、Lambda 函数和 Amazon SNS 主题的 Woofer 应用程序工作流。\]](http://docs.aws.amazon.com/zh_cn/amazondynamodb/latest/developerguide/images/StreamsAndTriggers.png)


1. 用户将项目写入 DynamoDB 表 (`BarkTable`)。表中的每个项目代表一个 bark。

1. 写入新的流记录，体现添加到 `BarkTable` 中的新项目。

1. 新的流记录触发 AWS Lambda 函数 (`publishNewBark`)。

1. 如果流记录指示新项目已添加到 `BarkTable`，则 Lambda 函数会从流记录读取数据并将消息发布到 Amazon Simple Notification Service (Amazon SNS) 中的主题。

1. Amazon SNS 主题的订阅者收到消息。（在本教程中，唯一的订阅者是一个电子邮件地址。）

**开始前的准备工作**  
本教程使用 AWS Command Line Interface AWS CLI。如果您尚未配置，请按照 [AWS Command Line Interface 用户指南](https://docs.aws.amazon.com/cli/latest/userguide/)中的说明安装和配置 AWS CLI。

## 第 1 步：创建一个启用了流的 DynamoDB 表
<a name="Streams.Lambda.Tutorial.CreateTable"></a>

在此步骤中，您将创建 DynamoDB 表 (`BarkTable`) 以存储来自 Woofer 用户的所有 bark。主键由 `Username`（分区键）和 `Timestamp`（排序键）组成。这两个属性的类型为字符串。

`BarkTable` 启用了流。在本教程后面的部分中，您通过将 AWS Lambda 函数与流关联来创建触发器。

1. 输入以下命令以创建表。

   ```
   aws dynamodb create-table \
       --table-name BarkTable \
       --attribute-definitions AttributeName=Username,AttributeType=S AttributeName=Timestamp,AttributeType=S \
       --key-schema AttributeName=Username,KeyType=HASH  AttributeName=Timestamp,KeyType=RANGE \
       --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
       --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES
   ```

1. 在输出中，查找 `LatestStreamArn`。

   ```
   ...
   "LatestStreamArn": "arn:aws:dynamodb:region:accountID:table/BarkTable/stream/timestamp
   ...
   ```

   记录 `region` 和 `accountID`，因为您在本教程接下来的步骤中需要这些信息。

## 第 2 步：创建一个 Lambda 执行角色
<a name="Streams.Lambda.Tutorial.CreateRole"></a>

在此步骤中，您将创建 AWS Identity and Access Management (IAM) 角色 (`WooferLambdaRole`) 并向其分配权限。此角色将由您在[第 4 步：创建并测试一个 Lambda 函数](#Streams.Lambda.Tutorial.LambdaFunction)中创建的 Lambda 函数使用。

您还将为角色创建策略。策略包含 Lambda 函数在运行时需要的所有权限。

1. 使用以下内容创建名为 `trust-relationship.json` 的文件。

------
#### [ JSON ]

****  

   ```
   {
      "Version":"2012-10-17",		 	 	 
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "lambda.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
   ```

------

1. 输入以下命令来创建 `WooferLambdaRole`。

   ```
   aws iam create-role --role-name WooferLambdaRole \
       --path "/service-role/" \
       --assume-role-policy-document file://trust-relationship.json
   ```

1. 使用以下内容创建名为 `role-policy.json` 的文件。（将 `region` 和 `accountID` 替换为您的 AWS 区域和帐户 ID。）

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": [
           {
               "Effect": "Allow",
               "Action": [
                   "logs:CreateLogGroup",
                   "logs:CreateLogStream",
                   "logs:PutLogEvents"
               ],
               "Resource": "arn:aws:logs:us-east-1:111122223333:*"
           },
           {
               "Effect": "Allow",
               "Action": [
                   "dynamodb:DescribeStream",
                   "dynamodb:GetRecords",
                   "dynamodb:GetShardIterator",
                   "dynamodb:ListStreams"
               ],
               "Resource": "arn:aws:dynamodb:us-east-1:111122223333:table/BarkTable/stream/*"
           },
           {
               "Effect": "Allow",
               "Action": [
                   "sns:Publish"
               ],
               "Resource": [
                   "*"
               ]
           }
       ]
   }
   ```

------

   策略有四个语句，允许 `WooferLambdaRole` 执行以下操作：
   + 运行 Lambda 函数 (`publishNewBark`)。您将在本教程的后面部分中创建函数。
   + 访问 Amazon CloudWatch Logs Lambda 函数在运行时将诊断信息写入 CloudWatch Logs。
   + 从 `BarkTable` 的 DynamoDB 流读取数据。
   + 向 Amazon SNS 发布消息。

1. 输入以下命令以将策略附加到 `WooferLambdaRole`。

   ```
   aws iam put-role-policy --role-name WooferLambdaRole \
       --policy-name WooferLambdaRolePolicy \
       --policy-document file://role-policy.json
   ```

## 第 3 步：创建一个 Amazon SNS 主题
<a name="Streams.Lambda.Tutorial.SNSTopic"></a>

在此步骤中，您将创建 Amazon SNS 主题 (`wooferTopic`) 并使用电子邮件地址订阅该主题。您的 Lambda 函数使用此主题发布来自 Woofer 用户的新 bark。

1. 输入以下命令以创建新 Amazon SNS 主题。

   ```
   aws sns create-topic --name wooferTopic
   ```

1. 输入以下命令以使用电子邮件地址订阅 `wooferTopic`。（使用您的 AWS 区域和账户 ID 替换 `region` 和 `accountID`，并使用有效的电子邮件地址替换 `example@example.com`。）

   ```
   aws sns subscribe \
       --topic-arn arn:aws:sns:region:accountID:wooferTopic \
       --protocol email \
       --notification-endpoint example@example.com
   ```

1. Amazon SNS 将向您的电子邮件地址发送确认邮件。选择该邮件中的**确认订阅**链接以完成订阅过程。

## 第 4 步：创建并测试一个 Lambda 函数
<a name="Streams.Lambda.Tutorial.LambdaFunction"></a>

在此步骤中，您将创建 AWS Lambda 函数 (`publishNewBark`) 以处理来自 `BarkTable` 的流记录。

`publishNewBark` 函数仅处理与 `BarkTable` 中的新项目对应的流事件。该函数从此类事件读取数据，然后调用 Amazon SNS 以发布该事件。

1. 使用以下内容创建名为 `publishNewBark.js` 的文件。将 `region` 和 `accountID` 替换为您的 AWS 区域和帐户 ID。

   ```
   'use strict';
   var AWS = require("aws-sdk");
   var sns = new AWS.SNS();
   
   exports.handler = (event, context, callback) => {
   
       event.Records.forEach((record) => {
           console.log('Stream record: ', JSON.stringify(record, null, 2));
   
           if (record.eventName == 'INSERT') {
               var who = JSON.stringify(record.dynamodb.NewImage.Username.S);
               var when = JSON.stringify(record.dynamodb.NewImage.Timestamp.S);
               var what = JSON.stringify(record.dynamodb.NewImage.Message.S);
               var params = {
                   Subject: 'A new bark from ' + who,
                   Message: 'Woofer user ' + who + ' barked the following at ' + when + ':\n\n ' + what,
                   TopicArn: 'arn:aws:sns:region:accountID:wooferTopic'
               };
               sns.publish(params, function(err, data) {
                   if (err) {
                       console.error("Unable to send message. Error JSON:", JSON.stringify(err, null, 2));
                   } else {
                       console.log("Results from sending message: ", JSON.stringify(data, null, 2));
                   }
               });
           }
       });
       callback(null, `Successfully processed ${event.Records.length} records.`);
   };
   ```

1. 创建包含 `publishNewBark.js` 的 zip 文件。如果您有 zip 命令行实用程序，则可以输入以下命令来完成此操作。

   ```
   zip publishNewBark.zip publishNewBark.js
   ```

1. 当您创建 Lambda 函数时，为 `WooferLambdaRole` 指定您在 [第 2 步：创建一个 Lambda 执行角色](#Streams.Lambda.Tutorial.CreateRole) 中创建的 Amazon 资源名称（ARN）。输入以下命令检索此 ARN。

   ```
   aws iam get-role --role-name WooferLambdaRole
   ```

   在输出中，查找 `WooferLambdaRole` 的 ARN。

   ```
   ...
   "Arn": "arn:aws:iam::region:role/service-role/WooferLambdaRole"
   ...
   ```

   输入以下命令以创建 Lambda 函数。将 *roleARN* 替换为 `WooferLambdaRole` 的 ARN。

   ```
   aws lambda create-function \
       --region region \
       --function-name publishNewBark \
       --zip-file fileb://publishNewBark.zip \
       --role roleARN \
       --handler publishNewBark.handler \
       --timeout 5 \
       --runtime nodejs16.x
   ```

1. 现在测试 `publishNewBark`，验证它可以正常使用。为此，您将提供类似于来自 DynamoDB Streams 的真实记录的输入。

   使用以下内容创建名为 `payload.json` 的文件。将 `region` 和 `accountID` 替换为您的 AWS 区域和账户 ID。

   ```
   {
       "Records": [
           {
               "eventID": "7de3041dd709b024af6f29e4fa13d34c",
               "eventName": "INSERT",
               "eventVersion": "1.1",
               "eventSource": "aws:dynamodb",
               "awsRegion": "region",
               "dynamodb": {
                   "ApproximateCreationDateTime": 1479499740,
                   "Keys": {
                       "Timestamp": {
                           "S": "2016-11-18:12:09:36"
                       },
                       "Username": {
                           "S": "John Doe"
                       }
                   },
                   "NewImage": {
                       "Timestamp": {
                           "S": "2016-11-18:12:09:36"
                       },
                       "Message": {
                           "S": "This is a bark from the Woofer social network"
                       },
                       "Username": {
                           "S": "John Doe"
                       }
                   },
                   "SequenceNumber": "13021600000000001596893679",
                   "SizeBytes": 112,
                   "StreamViewType": "NEW_IMAGE"
               },
               "eventSourceARN": "arn:aws:dynamodb:region:account ID:table/BarkTable/stream/2016-11-16T20:42:48.104"
           }
       ]
   }
   ```

   输入以下命令以测试 `publishNewBark` 函数。

   ```
   aws lambda invoke --function-name publishNewBark --payload file://payload.json --cli-binary-format raw-in-base64-out output.txt
   ```

   如果测试成功，您将看到以下输出。

   ```
   {
       "StatusCode": 200,
       "ExecutedVersion": "$LATEST"
   }
   ```

   此外，`output.txt` 文件将包含以下文本。

   ```
   "Successfully processed 1 records."
   ```

   您还会在数分钟内收到一封新电子邮件。
**注意**  
AWS Lambda 将诊断信息写入 Amazon CloudWatch Logs。如果您的 Lambda 函数出现错误，可以使用这些诊断信息排除故障：  
打开 CloudWatch 控制台：[https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/)。
在导航窗格中，选择**日志**。
选择下列日志组：`/aws/lambda/publishNewBark`
选择最新日志流以查看函数输出（以及错误）。

## 第 5 步：创建并测试一个触发器
<a name="Streams.Lambda.Tutorial.CreateTrigger"></a>

在 [第 4 步：创建并测试一个 Lambda 函数](#Streams.Lambda.Tutorial.LambdaFunction) 中，您测试了 Lambda 函数以确保它正确运行。在此步骤中，关联 Lambda 函数 (`publishNewBark`) 与事件源（`BarkTable` 流），创建*触发器*。

1. 在创建触发器时，您需要为 `BarkTable` 流指定 ARN。输入以下命令检索此 ARN。

   ```
   aws dynamodb describe-table --table-name BarkTable
   ```

   在输出中，查找 `LatestStreamArn`。

   ```
   ...
    "LatestStreamArn": "arn:aws:dynamodb:region:accountID:table/BarkTable/stream/timestamp
   ...
   ```

1. 输入以下命令以创建触发器。使用实际流 ARN 替换 `streamARN`。

   ```
   aws lambda create-event-source-mapping \
       --region region \
       --function-name publishNewBark \
       --event-source streamARN  \
       --batch-size 1 \
       --starting-position TRIM_HORIZON
   ```

1. 测试触发器。键入以下命令以将项目添加到 `BarkTable`。

   ```
   aws dynamodb put-item \
       --table-name BarkTable \
       --item Username={S="Jane Doe"},Timestamp={S="2016-11-18:14:32:17"},Message={S="Testing...1...2...3"}
   ```

   您应在数分钟内收到一封新电子邮件。

1. 打开 DynamoDB 控制台并再将几个项目添加到 `BarkTable`。您必须为 `Username` 和 `Timestamp` 属性指定值。（您还应为 `Message` 指定值，虽然该值并非必需。） 对于添加到 `BarkTable` 中的每个项目，您应收到一封新电子邮件。

   Lambda 函数仅处理您添加到 `BarkTable` 的新项目。如果您在表中更新或删除项目，函数不执行任何操作。

**注意**  
AWS Lambda 将诊断信息写入 Amazon CloudWatch Logs。如果您的 Lambda 函数出现错误，可以使用这些诊断信息排除故障。  
通过以下网址打开 CloudWatch 控制台：[https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/)。
在导航窗格中，选择**日志**。
选择下列日志组：`/aws/lambda/publishNewBark`
选择最新日志流以查看函数输出（以及错误）。

# 教程 2：对 DynamoDB 和 Lambda 使用筛选器来处理部分事件。
<a name="Streams.Lambda.Tutorial2"></a>

在本教程中，您将创建 AWS Lambda 触发器以处理来自 DynamoDB 表的流中的部分事件。

**Topics**
+ [组合起来 – CloudFormation](#Streams.Lambda.Tutorial2.Cloudformation)
+ [组合起来 – CDK](#Streams.Lambda.Tutorial2.CDK)

通过 [Lambda 事件筛选](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)，您可以使用筛选表达式来控制 Lambda 将哪些事件发送给函数进行处理。每个 DynamoDB 流最多可以配置 5 个不同的筛选器。如果您使用的是批处理时段，则 Lambda 会对每个新事件应用筛选条件，以确定是否将其包括在当前批处理中。

筛选器通过名为 `FilterCriteria` 的结构来应用。`FilterCriteria` 的 3 个主要属性为 `metadata properties`、`data properties` 和 `filter patterns`。

DynamoDB Streams 事件的示例结构如下所示：

```
{
  "eventID": "c9fbe7d0261a5163fcb6940593e41797",
  "eventName": "INSERT",
  "eventVersion": "1.1",
  "eventSource": "aws:dynamodb",
  "awsRegion": "us-east-2",
  "dynamodb": {
    "ApproximateCreationDateTime": 1664559083.0,
    "Keys": {
      "SK": { "S": "PRODUCT#CHOCOLATE#DARK#1000" },
      "PK": { "S": "COMPANY#1000" }
    },
    "NewImage": {
      "quantity": { "N": "50" },
      "company_id": { "S": "1000" },
      "fabric": { "S": "Florida Chocolates" },
      "price": { "N": "15" },
      "stores": { "N": "5" },
      "product_id": { "S": "1000" },
      "SK": { "S": "PRODUCT#CHOCOLATE#DARK#1000" },
      "PK": { "S": "COMPANY#1000" },
      "state": { "S": "FL" },
      "type": { "S": "" }
    },
    "SequenceNumber": "700000000000888747038",
    "SizeBytes": 174,
    "StreamViewType": "NEW_AND_OLD_IMAGES"
  },
  "eventSourceARN": "arn:aws:dynamodb:us-east-2:111122223333:table/chocolate-table-StreamsSampleDDBTable-LUOI6UXQY7J1/stream/2022-09-30T17:05:53.209"
}
```

`metadata properties` 是事件对象的字段。在 DynamoDB Streams 中，`metadata properties` 是 `dynamodb` 或 `eventName` 这样的字段。

`data properties` 是事件主体的字段。要根据 `data properties` 进行筛选，请确保将它们包含在正确的键内的 `FilterCriteria` 中。对于 DynamoDB 事件源，数据键为 `NewImage` 或 `OldImage`。

最后，筛选条件规则将定义要应用到特定属性的筛选条件表达式。下面是一些示例：


| 比较运算符 | 示例 | 规则语法（部分） | 
| --- | --- | --- | 
|  Null  |  产品类型为 null  |  `{ "product_type": { "S": null } } `  | 
|  Empty  |  产品名称为空  |  `{ "product_name": { "S": [ ""] } } `  | 
|  Equals  |  州为佛罗里达州  |  `{ "state": { "S": ["FL"] } } `  | 
|  And  |  产品的州为佛罗里达州且产品类别为巧克力  |  `{ "state": { "S": ["FL"] } , "category": { "S": [ "CHOCOLATE"] } } `  | 
|  Or  |  产品的州为佛罗里达州或加利佛尼亚州  |  `{ "state": { "S": ["FL","CA"] } } `  | 
|  Not  |  产品的州不是佛罗里达州  |  `{"state": {"S": [{"anything-but": ["FL"]}]}}`  | 
|  Exists  |  存在自制产品  |  `{"homemade": {"S": [{"exists": true}]}}`  | 
|  不存在  |  不存在自制产品  |  `{"homemade": {"S": [{"exists": false}]}}`  | 
|  始于  |  COMPANY 以 PK 开头  |  `{"PK": {"S": [{"prefix": "COMPANY"}]}}`  | 

您最多可以为一个 Lambda 函数指定 5 个事件筛选模式。请注意，这 5 个事件中的每一个都将作为逻辑 OR 进行求值。因此，如果您配置了名为 `Filter_One` 和 `Filter_Two` 的两个筛选条件，则 Lambda 函数将执行 `Filter_One` OR `Filter_Two`。

**注意**  
在[Lambda 事件筛选](https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventfiltering.html)页面中，有一些用于筛选和比较数值的选项，但不适用于 DynamoDB 筛选事件，因为 DynamoDB 中的数字作为字符串存储。例如 ` "quantity": { "N": "50" }`，由于 `"N"` 属性，我们知道它是一个数字。

## 组合起来 – CloudFormation
<a name="Streams.Lambda.Tutorial2.Cloudformation"></a>

为了展示事件筛选功能的实际应用，下面提供了一个示例 CloudFormation 模板。此模板将生成一个简单的 DynamoDB 表，带有分区键 PK 和排序键 SK，并启用了 Amazon DynamoDB Streams。它将创建一个 Lambda 函数和一个简单的 Lambda 执行角色，允许将日志写入 Amazon Cloudwatch，并从 Amazon DynamoDB Stream 中读取事件。它还在 DynamoDB Streams 与 Lambda 函数之间添加事件源映射，因此每次在 Amazon DynamoDB Stream 中出现事件时都可以执行该函数。

```
AWSTemplateFormatVersion: "2010-09-09"

Description: Sample application that presents AWS Lambda event source filtering 
with Amazon DynamoDB Streams.

Resources:
  StreamsSampleDDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: "PK"
          AttributeType: "S"
        - AttributeName: "SK"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "PK"
          KeyType: "HASH"
        - AttributeName: "SK"
          KeyType: "RANGE"
      StreamSpecification:
        StreamViewType: "NEW_AND_OLD_IMAGES"
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17",		 	 	 
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: "/"
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: "2012-10-17",		 	 	 
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: arn:aws:logs:*:*:*
              - Effect: Allow
                Action:
                  - dynamodb:DescribeStream
                  - dynamodb:GetRecords
                  - dynamodb:GetShardIterator
                  - dynamodb:ListStreams
                Resource: !GetAtt StreamsSampleDDBTable.StreamArn

  EventSourceDDBTableStream:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      Enabled: True
      EventSourceArn: !GetAtt StreamsSampleDDBTable.StreamArn
      FunctionName: !GetAtt ProcessEventLambda.Arn
      StartingPosition: LATEST

  ProcessEventLambda:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.7
      Timeout: 300
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import logging

          LOGGER = logging.getLogger()
          LOGGER.setLevel(logging.INFO)

          def handler(event, context):
            LOGGER.info('Received Event: %s', event)
            for rec in event['Records']:
              LOGGER.info('Record: %s', rec)

Outputs:
  StreamsSampleDDBTable:
    Description: DynamoDB Table ARN created for this example
    Value: !GetAtt StreamsSampleDDBTable.Arn
  StreamARN:
    Description: DynamoDB Table ARN created for this example
    Value: !GetAtt StreamsSampleDDBTable.StreamArn
```

部署此 CloudFormation 模板后，您可以插入以下 Amazon DynamoDB 项目：

```
{
 "PK": "COMPANY#1000",
 "SK": "PRODUCT#CHOCOLATE#DARK",
 "company_id": "1000",
 "type": "",
 "state": "FL",
 "stores": 5,
 "price": 15,
 "quantity": 50,
 "fabric": "Florida Chocolates"
}
```

由于此 CloudFormation 模板中内嵌了简单的 Lambda 函数，您将在 Amazon CloudWatch 日志组中看到 Lambda 函数的事件，如下所示：

```
{
  "eventID": "c9fbe7d0261a5163fcb6940593e41797",
  "eventName": "INSERT",
  "eventVersion": "1.1",
  "eventSource": "aws:dynamodb",
  "awsRegion": "us-east-2",
  "dynamodb": {
    "ApproximateCreationDateTime": 1664559083.0,
    "Keys": {
      "SK": { "S": "PRODUCT#CHOCOLATE#DARK#1000" },
      "PK": { "S": "COMPANY#1000" }
    },
    "NewImage": {
      "quantity": { "N": "50" },
      "company_id": { "S": "1000" },
      "fabric": { "S": "Florida Chocolates" },
      "price": { "N": "15" },
      "stores": { "N": "5" },
      "product_id": { "S": "1000" },
      "SK": { "S": "PRODUCT#CHOCOLATE#DARK#1000" },
      "PK": { "S": "COMPANY#1000" },
      "state": { "S": "FL" },
      "type": { "S": "" }
    },
    "SequenceNumber": "700000000000888747038",
    "SizeBytes": 174,
    "StreamViewType": "NEW_AND_OLD_IMAGES"
  },
  "eventSourceARN": "arn:aws:dynamodb:us-east-2:111122223333:table/chocolate-table-StreamsSampleDDBTable-LUOI6UXQY7J1/stream/2022-09-30T17:05:53.209"
}
```

**筛选示例**
+ **仅限与给定州匹配的产品**

此示例修改了 CloudFormation 模板，使其包含一个筛选条件，用于匹配来自佛罗里达州的所有产品，缩写为“FL”。

```
EventSourceDDBTableStream:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      Enabled: True
      FilterCriteria:
        Filters:
          - Pattern: '{ "dynamodb": { "NewImage": { "state": { "S": ["FL"] } } } }'
      EventSourceArn: !GetAtt StreamsSampleDDBTable.StreamArn
      FunctionName: !GetAtt ProcessEventLambda.Arn
      StartingPosition: LATEST
```

重新部署堆栈后，可以将以下 DynamoDB 项目添加到表中。请注意，它不会出现在 Lambda 函数日志中，因为本示例中的产品来自加利佛尼亚州。

```
{
 "PK": "COMPANY#1000",
 "SK": "PRODUCT#CHOCOLATE#DARK#1000",
 "company_id": "1000",
 "fabric": "Florida Chocolates",
 "price": 15,
 "product_id": "1000",
 "quantity": 50,
 "state": "CA",
 "stores": 5,
 "type": ""
}
```
+ **仅限以 PK 和 SK 中某些值开头的项目**

此示例修改 CloudFormation 模板，使其包含以下条件：

```
EventSourceDDBTableStream:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      Enabled: True
      FilterCriteria:
        Filters:
          - Pattern: '{"dynamodb": {"Keys": {"PK": { "S": [{ "prefix": "COMPANY" }] },"SK": { "S": [{ "prefix": "PRODUCT" }] }}}}'
      EventSourceArn: !GetAtt StreamsSampleDDBTable.StreamArn
      FunctionName: !GetAtt ProcessEventLambda.Arn
      StartingPosition: LATEST
```

请注意 AND 条件要求条件位于模式内，其中键 PK 和 SK 位于同一个表达式中，以逗号分隔。

或者是以 PK 和 SK 开头的某些值，或者来自特定状态。

此示例修改 CloudFormation 模板，使其包含以下条件：

```
  EventSourceDDBTableStream:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 1
      Enabled: True
      FilterCriteria:
        Filters:
          - Pattern: '{"dynamodb": {"Keys": {"PK": { "S": [{ "prefix": "COMPANY" }] },"SK": { "S": [{ "prefix": "PRODUCT" }] }}}}'
          - Pattern: '{ "dynamodb": { "NewImage": { "state": { "S": ["FL"] } } } }'
      EventSourceArn: !GetAtt StreamsSampleDDBTable.StreamArn
      FunctionName: !GetAtt ProcessEventLambda.Arn
      StartingPosition: LATEST
```

请注意，OR 条件是通过在筛选条件部分引入新模式来添加的。

## 组合起来 – CDK
<a name="Streams.Lambda.Tutorial2.CDK"></a>

以下示例 CDK 项目 Formation 模板介绍了事件筛选功能。在使用此 CDK 项目之前，您需要[安装先决条件](https://docs.aws.amazon.com/cdk/v2/guide/work-with.html)，包括[运行准备脚本](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-python.html)。

**创建 CDK 项目**

首先通过在空目录中调用 `cdk init`，创建一个新的 AWS CDK 项目。

```
mkdir ddb_filters
cd ddb_filters
cdk init app --language python
```

`cdk init` 命令使用项目文件夹的名称来命名项目的各种元素，包括类、子文件夹和文件。文件夹名称中的所有连字符都将转换为下划线。否则，该名称应遵循 Python 标识符的格式。例如，名称不应以数字开头，也不能包含空格。

要使用新项目，请激活其虚拟环境。这允许将项目的依赖项安装在本地项目文件夹中，而不是全局安装。

```
source .venv/bin/activate
python -m pip install -r requirements.txt
```

**注意**  
您可将其视为用于激活虚拟环境的 Mac/Linux 命令。Python 模板包含一个批处理文件 `source.bat`，该文件允许在 Windows 上使用相同的命令。也可以使用传统的 Windows 命令 `.venv\Scripts\activate.bat`。如果您使用 AWS CDK Toolkit v1.70.0 或更早版本来初始化 AWS CDK 项目，则您的虚拟环境位于 `.env` 目录中，而不是 `.venv`。

**基本基础设施**

使用首选文本编辑器打开文件 `./ddb_filters/ddb_filters_stack.py`。此文件在您创建 AWS CDK 项目时自动生成。

接下来，添加函数 `_create_ddb_table` 和 `_set_ddb_trigger_function`。这些函数将在预置模式/按需模式下创建一个 DynamoDB 表，该表带有分区键 PK 和排序键 SK，并且默认启用了 Amazon DynamoDB Streams 以显示新映像和旧映像。

Lambda 函数将存储在文件夹 `lambda` 下的文件 `app.py` 中。此文件将稍后创建。它包含一个环境变量 `APP_TABLE_NAME`，这将成为此堆栈创建的 Amazon DynamoDB 表的名称。在同一个函数中，我们向 Lambda 函数授予流读取权限。最后，它将订阅 DynamoDB Streams 作为 Lambda 函数的事件源。

在 `__init__` 方法中文件的末尾，您将调用相应的构造以在堆栈中初始化它们。对于需要额外组件和服务的较大项目，最好在基础堆栈之外定义这些构造。

```
import os
import json

import aws_cdk as cdk
from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_dynamodb as dynamodb,
)
from constructs import Construct


class DdbFiltersStack(Stack):

    def _create_ddb_table(self):
        dynamodb_table = dynamodb.Table(
            self,
            "AppTable",
            partition_key=dynamodb.Attribute(
                name="PK", type=dynamodb.AttributeType.STRING
            ),
            sort_key=dynamodb.Attribute(
                name="SK", type=dynamodb.AttributeType.STRING),
            billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
            stream=dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
            removal_policy=cdk.RemovalPolicy.DESTROY,
        )

        cdk.CfnOutput(self, "AppTableName", value=dynamodb_table.table_name)
        return dynamodb_table

    def _set_ddb_trigger_function(self, ddb_table):
        events_lambda = _lambda.Function(
            self,
            "LambdaHandler",
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset("lambda"),
            handler="app.handler",
            environment={
                "APP_TABLE_NAME": ddb_table.table_name,
            },
        )

        ddb_table.grant_stream_read(events_lambda)

        event_subscription = _lambda.CfnEventSourceMapping(
            scope=self,
            id="companyInsertsOnlyEventSourceMapping",
            function_name=events_lambda.function_name,
            event_source_arn=ddb_table.table_stream_arn,
            maximum_batching_window_in_seconds=1,
            starting_position="LATEST",
            batch_size=1,
        )

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        ddb_table = self._create_ddb_table()
        self._set_ddb_trigger_function(ddb_table)
```

现在，我们将创建一个非常简单的 Lambda 函数，它将日志输出到 Amazon CloudWatch 中。为此，请创建一个名为 `lambda` 的新文件夹。

```
mkdir lambda
touch app.py
```

使用您常用的文本编辑器，将以下内容添加到 `app.py` 文件中：

```
import logging

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)


def handler(event, context):
    LOGGER.info('Received Event: %s', event)
    for rec in event['Records']:
        LOGGER.info('Record: %s', rec)
```

确保您位于 `/ddb_filters/` 文件夹中，键入以下命令创建示例应用程序：

```
cdk deploy
```

在某个时候，系统会要求您确认是否要部署解决方案。键入 `Y` 接受更改。

```
├───┼──────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${LambdaHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴──────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘

Do you wish to deploy these changes (y/n)? y

...

✨  Deployment time: 67.73s

Outputs:
DdbFiltersStack.AppTableName = DdbFiltersStack-AppTable815C50BC-1M1W7209V5YPP
Stack ARN:
arn:aws:cloudformation:us-east-2:111122223333:stack/DdbFiltersStack/66873140-40f3-11ed-8e93-0a74f296a8f6
```

部署更改后，打开 AWS 控制台并向表中添加一个项目。

```
{
 "PK": "COMPANY#1000",
 "SK": "PRODUCT#CHOCOLATE#DARK",
 "company_id": "1000",
 "type": "",
 "state": "FL",
 "stores": 5,
 "price": 15,
 "quantity": 50,
 "fabric": "Florida Chocolates"
}
```

现在，CloudWatch 日志应该包含此条目中的所有信息。

**筛选示例**
+ **仅限与给定州匹配的产品**

打开文件 `ddb_filters/ddb_filters/ddb_filters_stack.py` 并进行修改，使其包含与所有等于“FL”的产品相匹配的筛选条件。可以在第 45 行的 `event_subscription` 下方对其进行修改。

```
event_subscription.add_property_override(
    property_path="FilterCriteria",
    value={
        "Filters": [
            {
                "Pattern": json.dumps(
                    {"dynamodb": {"NewImage": {"state": {"S": ["FL"]}}}}
                )
            },
        ]
    },
)
```
+ **仅限以 PK 和 SK 中某些值开头的项目**

修改 Python 脚本以包含以下条件：

```
event_subscription.add_property_override(
    property_path="FilterCriteria",
    value={
        "Filters": [
            {
                "Pattern": json.dumps(
                    {
                        {
                            "dynamodb": {
                                "Keys": {
                                    "PK": {"S": [{"prefix": "COMPANY"}]},
                                    "SK": {"S": [{"prefix": "PRODUCT"}]},
                                }
                            }
                        }
                    }
                )
            },
        ]
    },
```
+ **或者是以 PK 和 SK 开头的某些值，或者来自特定状态。**

修改 Python 脚本以包含以下条件：

```
event_subscription.add_property_override(
    property_path="FilterCriteria",
    value={
        "Filters": [
            {
                "Pattern": json.dumps(
                    {
                        {
                            "dynamodb": {
                                "Keys": {
                                    "PK": {"S": [{"prefix": "COMPANY"}]},
                                    "SK": {"S": [{"prefix": "PRODUCT"}]},
                                }
                            }
                        }
                    }
                )
            },
            {
                "Pattern": json.dumps(
                    {"dynamodb": {"NewImage": {"state": {"S": ["FL"]}}}}
                )
            },
        ]
    },
)
```

请注意，向筛选数组添加更多元素时，将会添加 OR 条件。

**清除**

在工作目录的底部找到筛选器堆栈，然后执行 `cdk destroy`。系统将要求您确认删除资源：

```
cdk destroy
Are you sure you want to delete: DdbFiltersStack (y/n)? y
```

# 将 DynamoDB Streams 与 Lambda 配合使用的最佳实践
<a name="Streams.Lambda.BestPracticesWithDynamoDB"></a>

AWS Lambda 函数在*容器*中运行，这是与其他函数隔离的执行环境。在您首次运行某个函数时，AWS Lambda 创建新容器并开始执行该函数的代码。

Lambda 函数具有对每个调用执行一次的*处理程序*。该处理程序包含函数的主业务逻辑。例如，显示在 [第 4 步：创建并测试一个 Lambda 函数](Streams.Lambda.Tutorial.md#Streams.Lambda.Tutorial.LambdaFunction) 中的 Lambda 函数具有可处理 DynamoDB 流中记录的处理程序。

您也可以提供仅运行一次的初始化代码，在创建容器之后，但在 AWS Lambda 首次执行处理程序之前运行。[第 4 步：创建并测试一个 Lambda 函数](Streams.Lambda.Tutorial.md#Streams.Lambda.Tutorial.LambdaFunction) 中显示的 Lambda 函数具有导入适用于 Node.js 中的 JavaScript 的 SDK，然后为 Amazon SNS 创建客户端的初始化代码。这些对象只应在处理程序外部定义一次。

执行函数之后，AWS Lambda 可选择为后续的函数调用重用容器。在这种情况下，您的函数处理程序可能能够重用您在初始化代码中定义的资源。（请注意，您无法控制 AWS Lambda 保留容器的时间长度，也根本无法控制是否会重用容器。）

对于使用 AWS Lambda 的 DynamoDB 触发器，我们建议：
+ AWS 服务客户端应该在初始化代码而非处理程序中实例化。这样可允许 AWS Lambda 在容器的整个生命周期中重用现有连接。
+ 通常而言，您无需明确管理连接或实施连接池，因为 AWS Lambda 将为您管理它。

DynamoDB 流的 Lambda 使用者不能保证只传输一次，并且可能导致偶尔出现重复。确保您的 Lambda 函数代码是幂等的，以防止由于重复处理而出现意外问题。

有关更多信息，请参阅 *AWS Lambda 开发人员指南*中的[使用 AWS Lambda 函数的最佳实践](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html)。

# DynamoDB Streams 和 Apache Flink
<a name="StreamsApacheFlink.xml"></a>

可以通过 Apache Flink 使用 Amazon DynamoDB Streams 记录。借助[适用于 Apache Flink 的亚马逊托管服务](https://aws.amazon.com/managed-service-apache-flink/)，可以使用 Apache Flink 来实时转换和分析流数据。Apache Flink 是一个用于处理实时数据的开源流处理框架。适用于 Apache Flink 的 Amazon DynamoDB Streams 连接器可简化 Apache Flink 工作负载的构建和管理，并可让您将应用程序与其它 AWS 服务集成。

适用于 Apache Flink 的亚马逊托管服务有助于您快速构建端到端流处理应用程序，以用于日志分析、点击流分析、物联网（IoT）、广告技术、游戏等。四个最常见的用例是流式提取-转换-加载（ETL）、事件驱动型应用程序、响应式实时分析和数据流的交互式查询。有关从 Amazon DynamoDB Streams 写入 Apache Flink 的更多信息，请参阅 [Amazon DynamoDB Streams Connector](https://nightlies.apache.org/flink/flink-docs-master/docs/connectors/datastream/dynamodb/)。