

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用資料表、項目、查詢、掃描和索引
<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 SDKs 在 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>

若要建立相同的 `Music` 資料表，使用 DynamoDB 標準：不常存取資料表類別。

```
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 喪失用來加密資料表的客戶自管金鑰存取權，它仍會封存資料表。存檔涉及製作資料表的備份和刪除原始資料表。

## 列出資料表名稱
<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 標準-IA) 資料表類別針對以儲存為主要成本的資料表進行最佳化。例如，儲存不常存取資料的資料表 (例如應用程式日誌、舊社交媒體貼文、電子商務訂單歷史記錄以及過去遊戲成就) 都是適合標準-IA 資料表類別的選項。

每個 DynamoDB 資料表都與資料表類別相關聯。與資料表相關聯的所有次要索引都使用相同的資料表類別。您可以在建立資料表時設定資料表類別 （預設為 DynamoDB 標準），並使用 、 AWS 管理主控台 AWS CLI 或 AWS SDK 更新現有資料表的資料表類別。DynamoDB 也支援 AWS CloudFormation 針對單一區域資料表 （非全域資料表的資料表） 使用 管理資料表類別。每個資料表類別針對資料儲存以及讀取和寫入要求提供不同的定價。當為您的表選擇一個表類時，請記住下列事項：
+ DynamoDB 標準資料表類別提供比 DynamoDB 標準-IA 更低的輸送量成本，對於輸送量是主要成本的資料表來說，是最具成本效益的選項。
+ DynamoDB 標準 IA 資料表類別提供比 DynamoDB 標準更低的儲存成本，對於儲存成為主要成本的資料表來說，是最具成本效益的選項。當儲存超過使用 DynamoDB 標準資料表類別的資料表輸送量 (讀取和寫入) 成本的 50% 時，DynamoDB 標準-IA 資料表類別可協助您降低資料表總成本。
+ DynamoDB 標準 – IA 資料表提供與 DynamoDB 標準資料表相同的效能、耐用性和可用性。
+ 在 DynamoDB 標準資料表與 DynamoDB 標準 – IA 資料表類之間切換不需要變更應用程式的程式碼。不論您的資料表使用何種資料表類型，都可以使用相同的 DynamoDB API 和服務端點。
+ DynamoDB 標準 – IA 資料表與所有現有的 DynamoDB 功能相容，例如自動擴展、隨需模式、存留時間 (TTL)、隨需備份、時間點復原 (PITR) 和全域次要索引。

資料表最具成本效益的資料表類別取決於資料表預期的儲存體和輸送量使用模式。您可以使用成本和用量報告和 AWS Cost Explorer，查看資料表的歷史儲存和輸送量 AWS 成本和用量。使用此歷史資料來確定資料表最具成本效益的資料表類別。若要進一步了解如何使用 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>

您可以使用 *tags* 來標示 Amazon DynamoDB 資源。標籤可讓您以不同的方式分類您的資源，例如依據目的、所有者、環境或其他條件。標籤可協助您執行以下作業：
+ 根據您指派給資源的標籤來快速識別資源。
+ 請參閱依標籤細分的 AWS 帳單。
**注意**  
任何與標記資料表相關的本機次要索引 (LSI) 和全域次要索引 (GSI) 都會自動標上相同的標籤。目前無法標記 DynamoDB Streams 使用量。

Amazon EC2、Amazon S3、DynamoDB 等 AWS 服務支援標記。有效標記可讓您跨帶有特定標籤的服務來建立報告，以提供成本深入資訊。

若要開始使用標記，請執行以下操作：

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 KB 的大小上限。使用者指派的標籤名稱在成本分配報告中具有字首 `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 政策連接至使用者，以便存取這些操作。

1. 使用管理員使用者前往 [IAM 主控台](https://console.aws.amazon.com/iam/)。

1. 在左導覽選單中，選取 Policies (政策)。

1. 選取 Create Policy (建立政策)。

1. 將下列政策貼入至 JSON 編輯器。

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

****  

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

------

1. 完成協助程式並指派政策的名稱 (例如 `TagKeysAndValuesReadAccess`)。

1. 在左側導覽選單中，選擇 Users (使用者)。

1. 從清單中選取您通常用於存取 DynamoDB 主控台的使用者。

1. 選取 Add permissions (新增許可)。

1. 選取 Attach existing policies directly (直接連接現有政策)。

1. 從清單中選取您先前建立的政策。

1. 完成協助程式。

## 將標籤新增至新的或現有資料表 (AWS 管理主控台)
<a name="Tagging.Operations.using-console"></a>

您可以使用 DynamoDB 主控台，在建立新資料表時在其中新增標籤，或為現有的資料表新增、編輯或刪除標籤。

**在建立時為資源加上標籤 (主控台)**

1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在導覽窗格中，選擇 **Tables** (資料表)，然後選擇 **Create table** (建立資料表)。

1. 在 **Create DynamoDB table** (建立 DynamoDB 資料表) 頁面上，提供名稱和主索引鍵。在 **Tags** (標籤) 區段中，選擇 **Add new tag** (新增標籤)，然後輸入您要使用的標籤。

   如需標籤結構的相關資訊，請參閱 [DynamoDB 中的標記限制](Tagging.md#TaggingRestrictions)。

   如需建立資料表的詳細資訊，請參閱 [DynamoDB 資料表上的基本操作](WorkingWithTables.Basics.md)。

**為現有的資源加上標籤 (主控台)**

請在 [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在導覽窗格中，選擇 **Tables** (資料表)。

1. 在清單中選擇資料表，然後選擇 **Additional settings** (其他設定) 索引標籤。您可以新增、編輯或刪除 **Tags** (標籤) 頁面底部的區段。

## 將標籤新增至新的或現有資料表 (AWS CLI)
<a name="Tagging.Operations.using-cli"></a>

下列範例示範如何在建立資料表和索引時，使用 AWS CLI 指定標籤，以及標記現有資源。

**在建立時為資源加上標籤 (AWS CLI)**
+ 以下範例會建立新 `Movies` 資料表並使用 `blueTeam` 值來新增 `Owner` 標籤：

  ```
  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)**
+ 以下範例會為 `Movies` 資料表新增 `blueTeam` 值的 `Owner` 標籤：

  ```
  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產生的 tag. AWS defines、建立和套用此標籤。
+ 使用者定義的標籤。您可以定義、建立和套用這些標籤。

您必須分別啟用這兩種標籤，它們才會顯示在 Cost Explorer 或成本分配報告中。

 若要啟用 AWS產生的標籤：

1.  登入 AWS 管理主控台 並開啟 Billing and Cost Management 主控台，網址為 [https：//https://console.aws.amazon.com/billing/home\$1/。](https://console.aws.amazon.com/billing/home#/.)

1.  在導覽窗格中，選擇 **Cost Allocation Tags** (成本分配標籤)。

1.  在 **AWS-Generated Cost Allocation Tags** (AWS產生的成本分配標籤) 下，選擇 **Activate** (啟用)。

 若要啟用使用者定義的標籤：

1.  登入 AWS 管理主控台 並開啟 Billing and Cost Management 主控台，網址為 [https：//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 帳戶。

相同帳戶和多帳戶模型都支援多區域寫入、非同步複寫、last-writer-wins衝突解析，以及相同的計費模型。不過，它們在帳戶、許可、加密和資料表控管的管理方式上有所不同。

針對 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)。您應該盡可能使用 Global Tables 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 Fault Injection Service](https://docs.aws.amazon.com/resilience-hub/latest/userguide/testing.html) (AWS FIS) 整合，這是一種全受管服務，用於執行受控故障注入實驗，以改善應用程式的彈性。使用 AWS FIS，您可以：
+ 建立可定義特定失敗案例的實驗範本。
+ 透過模擬區域隔離 （即暫停複寫往返所選複本） 驗證應用程式彈性失敗，以在某個 AWS 區域發生中斷時測試錯誤處理、復原機制和多區域流量轉移行為。

例如，在美國東部 （維吉尼亞北部）、美國東部 （俄亥俄） 和美國西部 （奧勒岡） 具有複本的全域資料表中，您可以在美國東部 （俄亥俄） 執行實驗來測試區域隔離，而美國東部 （維吉尼亞北部） 和美國西部 （奧勒岡） 繼續正常操作。此項受控測試可協助您事先識別和解決潛在問題，以免影響正式作業工作負載。

如需 AWS FIS 支援動作的完整清單和[跨區域連線](https://docs.aws.amazon.com/fis/latest/userguide/cross-region-scenario.html)，以暫停區域之間的 DynamoDB 複寫，請參閱 [ FIS 使用者指南中的動作目標](https://docs.aws.amazon.com/fis/latest/userguide/action-sequence.html#action-targets)。 *AWS *

如需 AWS FIS 中可用 Amazon DynamoDB 全域資料表動作的相關資訊，請參閱 *AWS FIS 使用者指南*中的 [DynamoDB 全域資料表動作參考](https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html#dynamodb-actions-reference)。

若要開始執行故障注入實驗，請參閱 [AWS FIS 使用者指南中的規劃您的 FIS 實驗](https://docs.aws.amazon.com/fis/latest/userguide/getting-started-planning.html)。 AWS 

**注意**  
在 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 Stream](Streams.md) 讀取變更，並將變更套用至所有其他複本資料表，藉此複寫這些變更。因此，MREC 全域資料表中的所有複本預設會啟用 Streams，您無法在這些複本上將其停用。MREC 複寫程序可能會將短時間內多個變更合併為單一複寫寫入，導致每個複本的 Stream 包含略微不同的記錄。MREC 複本上的串流記錄會維持相同項目所有變更的順序，但不同項目變更的相對順序可能因複本而異。

如果您要撰寫應用程式來處理 Streams 記錄，藉此掌握全域資料表中特定區域而非其他區域發生的變更，您可以為每個項目新增一個屬性，以定義該項目發生變更所在的區域。您可以使用此屬性來篩選 Streams 記錄，以找出其他區域發生的變更，包括使用 Lambda 事件篩選條件，以僅調用 Lambda 函式找出特定區域的變更。

如果全域資料表已設定多區域強一致性 (MRSC)，其不會使用 DynamoDB Streams 進行複寫，因此 MRSC 複本預設不會啟用 Streams。您可以在 MRSC 複本上啟用 Streams。每個複本的 MRSC 複本 Stream 記錄都是相同的，包括 Stream 記錄排序。

## 交易
<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>

*全域資料表*是跨 AWS 區域複寫資料表資料的 DynamoDB 功能。

*複本資料表* (或複本) 是一種 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 會依據每個項目的最新內部時間戳記，使用修改來解決衝突，此方法稱為「最後一個寫入獲勝」衝突解決方法。所有複本的項目最終會收斂至上次寫入所建立的版本。

如果項目上次在發生讀取的區域中更新，則[高度一致性讀取操作](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html#DDB-GetItem-request-ConsistentRead)會傳回項目的最新版本，但如果項目上次在不同區域中更新，則可能會傳回過時的資料。條件式寫入會根據區域中項目的版本來評估條件運算式。

您可以將複本新增至現有 DynamoDB 資料表，來建立 MREC 全域資料表。新增複本不會影響現有單一區域 DynamoDB 資料表或全域資料表複本的效能。您可以將複本新增至 MREC 全域資料表，以擴展資料複寫的區域數量，或從 MREC 全域資料表中移除不再需要的複本。只要任何區域可以使用 DynamoDB，MREC 全域資料表皆可擁有這些區域的複本，且複本數量與 [AWS 分割區](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 全域資料表：美國區域集 （美國維吉尼亞北部、美國東部俄亥俄、美國西部奧勒岡）、歐洲區域集 （歐洲愛爾蘭、歐洲倫敦、歐洲巴黎、歐洲法蘭克福） 和 AP 區域集 （亞太區域東京、亞太區域首爾和亞太區域大阪）。MRSC 全域資料表不能跨越區域集 (例如，MRSC 全域資料表不能同時包含來自美國和歐洲區域集的複本)。

若要建立 MRSC 全域資料表，您可以將一個複本和一個見證或兩個複本新增至未含資料的現有 DynamoDB 資料表。將現有的單一區域資料表轉換為 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 模式：
+ 如果資料已在另一個區域中更新，應用程式可以容忍高度一致性讀取操作傳回過時資料。
+ 比起多區域讀取一致性，您更重視較低的寫入和高度一致性讀取延遲。
+ 您的多區域高可用性策略可以容忍大於 0 的 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 Fault Injection Service](https://docs.aws.amazon.com/resilience-hub/latest/userguide/testing.html) (AWS FIS) 整合，這是一種全受管服務，用於執行受控故障注入實驗，以改善應用程式的彈性。使用 AWS FIS，您可以：
+ 建立實驗範本來定義特定的失敗案例。
+ 透過模擬區域隔離 （即暫停複寫往返所選複本） 來注入失敗以驗證應用程式彈性，以在某個 AWS 區域發生中斷時測試錯誤處理、復原機制和多區域流量轉移行為。

例如，在美國東部 （維吉尼亞北部）、美國東部 （俄亥俄） 和美國西部 （奧勒岡） 具有複本的全域資料表中，您可以在美國東部 （俄亥俄） 執行實驗來測試區域隔離，而美國東部 （維吉尼亞北部） 和美國西部 （奧勒岡） 繼續正常操作。此項受控測試可協助您事先識別和解決潛在問題，以免影響正式作業工作負載。

如需 AWS FIS 支援動作的完整清單和[跨區域連線](https://docs.aws.amazon.com/fis/latest/userguide/cross-region-scenario.html)，以暫停區域之間的 DynamoDB 複寫，請參閱 [ FIS 使用者指南中的動作目標](https://docs.aws.amazon.com/fis/latest/userguide/action-sequence.html#action-targets)。 *AWS *

如需 AWS FIS 中可用 Amazon DynamoDB 全域資料表動作的相關資訊，請參閱 *AWS FIS 使用者指南*中的 [DynamoDB 全域資料表動作參考](https://docs.aws.amazon.com/fis/latest/userguide/fis-actions-reference.html#dynamodb-actions-reference)。

若要開始執行故障注入實驗，請參閱 [AWS FIS 使用者指南中的規劃您的 FIS 實驗](https://docs.aws.amazon.com/fis/latest/userguide/getting-started-planning.html)。 AWS 

**注意**  
在 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 Stream](Streams.md) 讀取變更，並將變更套用至所有其他複本資料表，藉此複寫這些變更。因此，MREC 全域資料表中的所有複本預設會啟用 Streams，您無法在這些複本上將其停用。MREC 複寫程序可能會將短時間內多個變更合併為單一複寫寫入，導致每個複本的 Stream 包含略微不同的記錄。MREC 複本上的 Streams 記錄一律會依項目排序，但複本之間的項目順序可能不同。

如果全域資料表已設定多區域強一致性 (MRSC)，其不會使用 DynamoDB Streams 進行複寫，因此 MRSC 複本預設不會啟用 Streams。您可以在 MRSC 複本上啟用 Streams。每個複本的 MRSC 複本 Stream 記錄都是相同的，包括 Stream 記錄排序。

如果您要撰寫應用程式來處理 Streams 記錄，藉此掌握全域資料表中特定區域而非其他區域發生的變更，您可以為每個項目新增一個屬性，以定義該項目發生變更所在的區域。您可以使用此屬性來篩選 Streams 記錄，以找出其他區域發生的變更，包括使用 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 模式中的 Streams 定義
+ 存留時間 (TTL)
+ 暖輸送量
+ 隨需最大寫入輸送量

下列設定會在各複本之間同步，但每個複本都可以覆寫：
+ 資料表的佈建讀取容量
+ 資料表的讀取自動擴展
+ GSI 的佈建讀取容量
+ GSI 的讀取自動擴展
+ 資料表類別
+ 隨需最大讀取輸送量

**注意**  
如果任何其他複本上的設定有所修改，則會變更可覆寫的設定值。例如，您有一個 MREC 全域資料表，其中包含美國東部 (維吉尼亞北部) 和美國西部 (奧勒岡)的複本。美國東部 (維吉尼亞北部) 複本已設定 200 RCUs 的佈建讀取輸送量。美國西部 (奧勒岡) 的複本已設定 100 RCUs 的佈建讀取輸送量覆寫。如果您將美國東部 (維吉尼亞北部) 複本上的佈建讀取輸送量設定從 200 RCUs 更新為 300 RCUs，則美國西部 (奧勒岡) 複本也會套用新的佈建讀取輸送量值。如此一來，美國西部 (奧勒岡) 複本的佈建讀取輸送量設定，會從 100 RCUs 的覆寫值變更為 300 RCUs 的新值。

各複本之間絕不會同步下列設定：
+ 刪除保護
+ 時間點復原
+ Tags (標籤)
+ 資料表的 CloudWatch Contributor Insights 啟用
+ GSI 的 CloudWatch Contributor Insights 啟用
+ Kinesis Data Streams 定義
+ 資源政策
+ MRSC 模式中的 Streams 定義

各複本之間的所有其他設定均不會同步。

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

全域資料表複本的寫入作業會繞過 DynamoDB Accelerator (DAX)，直接更新 DynamoDB。因此，DAX 快取可能會過時，因為寫入不會更新 DAX 快取。只有在快取 TTL 過期時，系統才會重新整理全域資料表複本設定的 DAX 快取。

## 管理全域資料表的考量
<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 區域提供具有非同步複寫的低延遲寫入。對一個區域中的項目所做的變更，通常會在一秒內複寫到所有其他區域。這使得 MREC 非常適合以低寫入延遲為優先，並且可以容忍不同區域在短期內可能傳回略有不同版本資料的應用程式。

您可以在可使用 DynamoDB 的任何 AWS 區域中，使用複本建立 MREC 全域資料表，並隨時新增或移除複本。下列範例示範如何在多個區域中以複本建立 MREC 全域資料表。

### 使用 DynamoDB 主控台建立 MREC 全域資料表
<a name="mrec-console"></a>

​請遵循下列步驟，使用 ​ AWS 管理主控台建立全域資料表​。下列範例使用美國及歐洲的複本資料表建立全域資料表。

1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在此範例中，請從導覽列的區域選擇器選擇**美國東部 (俄亥俄)**。

1. 在主控台左側的導覽窗格中，選擇 **Tables** (資料表)。

1. 選擇 **Create Table** (建立資料表)。

1. 在**建立資料表**頁面上：

   1. 對於 **Table name** (資料表名稱)，請輸入 **Music**。

   1. 對於 **Partition key (分割區索引鍵)**，請輸入 **Artist**。

   1. 對於**排序索引鍵**，請輸入 **SongTitle**。

   1. 保留其他預設設定，並選擇**建立資料表**。

      此新資料表會做為新全域資料表中的第一個複本資料表。這是您稍後新增其他複本資料表的原型。

1. 在資料表變成作用中之後：

   1. 從資料表清單選擇 **Music** 資料表。

   1. 選擇 **Global Tables** (全域資料表) 標籤。

   1. 選擇 **Create replica (建立複本)**。

1. 從**可用的複寫區域**下拉式清單選擇**美國西部 (奧勒岡) us-west-2**。

   主控台會確認選取區域中沒有同名的資料表。若有同名的資料表，您必須先刪除現有的資料表，才可在該區域中建立新的複本資料表。

1. 選擇 **Create replica (建立複本)**。這會啟動在美國西部 (奧勒岡) us-west-2 區域建立資料表的流程。

   **Music** 資料表 (以及任何其他複寫資料表) 的**全域資料表**標籤顯示資料表已於多個區域複寫。

1. 重複上述步驟來新增另一個區域，但選擇**歐洲 (法蘭克福) eu-central-1** 作為區域。

1. 若要測試複寫：

   1. 請務必 AWS 管理主控台 在美國東部 （俄亥俄） 區域使用 。

   1. 選擇**探索資料表項目**。

   1. 選擇 **Create item** (建立項目)。

   1. 為 **Artist** 輸入 **item\$11**，為 **SongTitle** 輸入 **Song Value 1**。

   1. 選擇 **Create item** (建立項目)。

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 2.x 的 SDK**  
使用 建立具有全域次要索引和 DynamoDB Streams 的資料表 AWS SDK for Java 2.x。  

```
    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 參考*》中的下列主題。
  + [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 全域資料表，並在美國西部 (奧勒岡) 區域中建立見證。

**注意**  
建立全域資料表之前，請驗證 Service Quotas 輸送量限制在所有目標區域中都一致，因為這是建立全域資料表的必要條件。如需全域資料表輸送量限制的詳細資訊，請參閱[全域資料表配額](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ServiceQuotas.html#gt-limits-throughput)。

### 使用 DynamoDB 主控台建立 MRSC 全域資料表
<a name="mrsc_console"></a>

​請遵循下列步驟，使用 ​ AWS 管理主控台建立 MRSC 全域資料表。

1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 從導覽列的區域選擇器選擇[支援](V2globaltables_HowItWorks.md#V2globaltables_HowItWorks.consistency-modes) MRSC 全域資料表的區域，例如 **us-east-2**。

1. 在導覽窗格中，選擇 **Tables** (資料表)。

1. 選擇 **Create Table** (建立資料表)。

1. 在**建立資料表**頁面上：

   1. 對於 **Table name** (資料表名稱)，請輸入 **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 2.x 的 SDK**  
使用 建立準備好進行 MRSC 轉換的區域資料表 AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
使用 將區域資料表轉換為具有複本和見證的 MRSC AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
使用 描述 MRSC 全域資料表組態 AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
使用 從 MRSC 複本讀取具有一致讀取的項目 AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
使用 搭配 MRSC 保證執行條件式更新 AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
等待 MRSC 複本和見證使用 變成作用中 AWS SDK for Java 2.x。  

```
    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;
        }
    }
```
使用 清除 MRSC 複本和見證 AWS SDK for Java 2.x。  

```
    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();
        }
    }
```
使用 完成 MRSC 工作流程示範 AWS SDK for Java 2.x。  

```
    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 參考*》中的下列主題。
  + [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 全域資料表依賴服務連結角色 (SLRs) 來管理跨區域複寫和自動擴展功能。

您只需要為每個 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)。此角色會為您管理跨區域複寫。

將資源型政策套用至複本時，請確定您不會拒絕 中定義的任何許可`AWSServiceRoleForDynamoDBReplicationPolicy`給 SLR 委託人，因為這會中斷複寫。如果您拒絕必要的 SLR 許可權限，將停止與受影響複本之間的複寫，而複本資料表狀態將變更為 `REPLICATION_NOT_AUTHORIZED`。
+ 對於多區域最終一致性 (MREC) 全域資料表，如果複本保持 `REPLICATION_NOT_AUTHORIZED` 狀態超過 20 小時，則複本會不可逆地轉換為單一區域 DynamoDB 資料表。
+ 對於多區域強式一致性 (MRSC) 全域資料表，拒絕必要的許可會導致 `AccessDeniedException`進行寫入和強式一致讀取操作。如果複本停留在 `REPLICATION_NOT_AUTHORIZED` 狀態超過七天，複本會永久無法存取，而且寫入和高度一致的讀取操作會繼續失敗並發生錯誤。複本刪除等部分管理操作將會成功。

### 自動擴展服務連結角色
<a name="globaltables-autoscaling-slr"></a>

設定佈建容量模式的全域資料表時，必須為全域資料表設定自動擴展。DynamoDB Auto Scaling 服務會使用 AWS Application Auto Scaling 服務動態調整全域資料表複本上的佈建輸送量容量。Application Auto Scaling 服務會建立名為 的服務連結角色 (SLR)[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)。當您第一次設定 DynamoDB 資料表的自動擴展時，會在 AWS 您的帳戶中自動建立此服務連結角色。它允許 Application Auto Scaling 管理的佈建資料表容量並建立 CloudWatch 警示。

 將資源型政策套用至複本時，請確定您不會拒絕 中定義的任何許可[https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSApplicationAutoscalingDynamoDBTablePolicy.html](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSApplicationAutoscalingDynamoDBTablePolicy.html)給 Application Auto Scaling SLR 主體，因為這會中斷自動擴展功能。

### 服務連結角色的 IAM 政策範例
<a name="globaltables-example-slr"></a>

具有下列條件的 IAM 政策不會影響 DynamoDB 複寫 SLR 和 AWS Auto Scaling 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>

下列身分型政策可讓您跨三個區域建立名為「使用者」的 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>

下列身分型政策可讓您建立名為「使用者」的 DynamoDB MRSC 全域資料表，其中包含 us-east-1 和 us-east-2 中的複本，以及 us-west-2 中的見證，包括建立所需的 DynamoDB 複寫服務連結角色。

------
#### [ 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`

  請注意，存留時間 (TTL) 僅支援使用多區域最終一致性 (MREC) 設定的全域資料表。如需全域資料表如何使用 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>

此身分型政策可讓您跨三個區域刪除名為「使用者」的 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 帳戶，以簡化成本歸因

如需詳細資訊，請參閱[使用多個 AWS 帳戶的優點](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 控制平面 APIs 進行設定。

建立多帳戶全域資料表時，客戶必須為每個區域複本設定 `GlobalTableSettingsReplicationMode = ENABLED` 。這可確保在一個區域中所做的組態變更會自動傳播到參與全域資料表的所有其他區域。

您可以在建立資料表後啟用設定複寫。這支援將資料表最初建立為區域資料表，稍後升級至多帳戶全域資料表的情況。

**同步設定**

下表設定一律會同步多帳戶全域資料表中所有複本：

**注意**  
與相同帳戶全域資料表不同，多帳戶全域資料表不允許對這些設定進行每個區域覆寫。唯一的例外是，允許覆寫讀取自動擴展政策 （資料表和 GSIs)，因為它們是單獨的外部資源。
+ 容量模式 (佈建容量或隨需)
+ 資料表佈建的讀取和寫入容量
+ 資料表讀取和寫入自動擴展
+ 本機次要索引 (LSI) 定義
+ 全域次要索引 (GSI) 定義
+ GSI 佈建的讀取和寫入容量
+ GSI 讀取和寫入自動擴展
+ MREC 模式中的 Streams 定義
+ 存留時間 (TTL)
+ 暖輸送量
+ 隨需最大讀取和寫入輸送量

**非同步設定**

下列設定不會在複本之間同步，且必須針對每個區域中的每個複本資料表獨立設定。
+ 資料表類別
+ 伺服器端加密 (SSE) 類型
+ 時間點復原
+ 伺服器端加密 (SSE) KMS 金鑰 ID
+ 刪除保護
+ Kinesis Data Streams (KDSD)
+ Tags (標籤)
+ 資源政策
+ 資料表 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>

本節提供step-by-step說明。 DynamoDB AWS 

## 使用 DynamoDB 主控台建立多帳戶全域資料表
<a name="create-ma-gt-console"></a>

請依照下列步驟，使用 建立多帳戶全域資料表 AWS 管理主控台。下列範例會在美國建立具有複本資料表的全域資料表。

1. 登入 AWS 管理主控台 ，並在 [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)：// 開啟第一個帳戶的 DynamoDB 主控台 （例如 *111122223333*)。

1. 在此範例中，請從導覽列的區域選擇器選擇**美國東部 (俄亥俄)**。

1. 在主控台左側的導覽窗格中，選擇 **Tables** (資料表)。

1. 選擇 **Create Table** (建立資料表)。

1. 在**建立資料表**頁面上：

   1. 對於 **Table name** (資料表名稱)，請輸入 **MusicTable**。

   1. 對於 **Partition key (分割區索引鍵)**，請輸入 **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. 登入 AWS 管理主控台 ，並在 [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/)：// 開啟第二個帳戶的 DynamoDB 主控台 （例如 *444455556666*)。

1. 在此範例中，從導覽列的區域選擇器中選擇**美國東部 （維吉尼亞北部）**。

1. 主控台會確認選取區域中沒有同名的資料表。若有同名的資料表，您必須先刪除現有的資料表，才可在該區域中建立新的複本資料表。

1. 在**建立資料表**附近的下拉式清單中，選擇**從另一個帳戶建立**

1. 在**從另一個帳戶建立資料表**頁面上：

   1. 新增 **arn:aws:dynamodb:us-east-2:*111122223333*:table/MusicTable**做為來源資料表的資料表 Arn。

   1. 在**複本資料表 ARNs** 中，再次新增來源資料表的 ARN**arn:aws:dynamodb:us-east-2:*111122223333*:table/MusicTable**。如果多帳戶全域資料表中已有多個複本，您必須將每個現有複本新增至ReplicaTableARN。

   1. 保留其他預設設定，然後選擇**提交**。

1. 音樂資料表 **（以及任何其他複本資料表） 的全域**資料表索引標籤會顯示資料表已在多個區域中複寫。

1. 若要測試複寫：

   1. 您可以使用此資料表存在複本的任何區域

   1. 選擇**探索資料表項目**。

   1. 選擇 **Create item** (建立項目)。

   1. 為 **Artist** 輸入 **item\$11**，為 **SongTitle** 輸入 **Song Value 1**。

   1. 選擇 **Create item** (建立項目)。

   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 多帳戶全域資料表。您可以了解允許跨區域跨帳戶複寫和自動擴展的資源型政策和服務連結角色 (SLR)，以及建立、更新和刪除全域資料表所需的 IAM 許可，以取得多區域最終一致性 (MREC) 資料表。您也會了解 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` 許可來控制，但我們也依賴服務連結角色 (SLRs) 來管理特定跨帳戶跨區域複寫和自動擴展功能。這些角色每個 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 主體，因為這可能會干擾設定管理，如果複本或 GSIs 之間的輸送量不相符，則可能會影響複寫。如果您拒絕必要的 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 Auto Scaling 服務會使用 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 和資料表名稱值。

以下是我們在下列各節涵蓋的step-by-step主題：
+ 建立多帳戶全域資料表並新增複本
+ 更新多帳戶全域資料表
+ 刪除全域資料表和移除複本

### 建立全域資料表並新增複本
<a name="globaltables_MA_creating"></a>

#### 建立全域資料表的許可
<a name="globaltables_MA_creating_permissions"></a>

當新的複本新增至區域資料表以形成多帳戶全域資料表或現有多帳戶全域資料表時，執行動作的 IAM 主體必須經過所有現有成員的授權。所有現有成員都必須在其資料表政策中授予下列許可，才能成功新增複本：
+ `dynamodb:AssociateTableReplica` - 此許可允許資料表加入全域資料表設定。這是啟用初始建立複寫關係的基礎許可。

此精確控制僅允許授權帳戶參與全域資料表設定。

#### 建立全域資料表的 IAM 政策範例
<a name="globaltables_MA_creating_examples"></a>

##### 2 個複本設定的範例 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 的管理員必須將相符政策連接至ReplicaB，並在建立資料表呼叫中將對應的許可反向授予帳戶 A，以建立參考複本 A 做為來源資料表的複本 B。

------
#### [ 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"
                    ]
                }
            }
        }
    ]
}
```

------

##### 3 個複本設定的範例 IAM 政策
<a name="globaltables_MA_3replica_example"></a>

在此設定中，帳戶 A、帳戶 ReplicaB 和帳戶 C 分別有 3 個複本ReplicaA A、複本 B 和ReplicaC C。複本 A 是第一個複本，其開頭為區域資料表，然後將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做為來源的複本 （複本 B)。複本 B 具有下列政策，允許所有成員之間複寫，並允許帳戶 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 受管金鑰。

使用 CMKs多帳戶全域資料表需要每個複本的金鑰政策，才能授予 DynamoDB 複寫服務主體 (`replication.dynamodb.amazonaws.com`) 存取金鑰以進行複寫和設定管理的許可。需要以下許可：
+ `kms:Decrypt`
+ `kms:ReEncrypt*`
+ `kms:GenerateDataKey*`
+ `kms:DescribeKey`

**Important (重要)**

DynamoDB 需要存取複本的加密金鑰才能刪除複本。如果您想要停用或刪除用於加密複本的客戶受管金鑰，因為您要刪除複本，您應該先刪除複本，等待從複寫群組中移除資料表，方法是呼叫其中一個複本中的描述，然後停用或刪除金鑰。

如果您停用或撤銷 DynamoDB 對用於加密複本之客戶受管金鑰的存取權，複本的複寫將停止，複本狀態將變更為 `INACCESSIBLE_ENCRYPTION_CREDENTIALS`。如果複本保持 `INACCESSIBLE_ENCRYPTION_CREDENTIALS` 狀態超過 20 小時，複本會不可復原地轉換為單一區域 DynamoDB 資料表。

### 範例 AWS KMS 政策
<a name="globaltables_MA_kms_example"></a>

此 AWS KMS 政策允許 DynamoDB 存取複本 A 和 B 之間的兩個複寫 AWS KMS 金鑰。每個帳戶中連接至 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)，其中每次寫入都會收取一個 WRU，最多 1KB
+ 佈建容量模式的寫入容量單位 (WCU)，其中一個 WCU 每秒提供一次寫入，最多 1 KB

您將複本資料表新增至現有單一區域資料表來建立全域資料表時，該單一區域資料表會成為複本資料表，亦即用於計費寫入資料表的單位也會變更。複本資料表的寫入操作會使用下列單位計費：
+ 隨需容量模式的複寫寫入請求單位 (rWRU)，其中每個複本資料表會收取一個 rWRU，每個寫入最多 1KB
+ 佈建容量模式的複寫寫入容量單位 (rWCU)，其中每個複本資料表的一個 WCU 每秒提供一次寫入，最多 1 KB

全域次要索引 (GSI) 更新的計費單位與單一區域 DynamoDB 資料表相同，即使 GSI 的基礎資料表是複本資料表。GSI 更新操作會使用下列單位計費：
+ 隨需容量模式的寫入請求單位 (WRU)，其中每次寫入都會收取一個 WRU，最多 1KB
+ 佈建容量模式的寫入容量單位 (WCU)，其中一個 WCU 每秒提供一次寫入，最多 1 KB

複寫寫入單位 (rWCU 和 rWRU) 定價與單一區域寫入單位 (WCU 和 WRU) 相同。跨區域資料傳輸費用適用於全域資料表，因為資料會跨區域複寫。複寫寫入 (rWCU 或 rWRU) 費用會在包含全域資料表複本資料表的每個區域中產生。

單一區域資料表和複本資料表的讀取操作會使用以下單位：
+ 隨需容量模式的讀取請求單位 (RRU)，其中每個高度一致性讀取會收取一個 RRU，最多 4KB
+ 佈建資料表的讀取容量單位 (RCU)，其中一個 RCU 每秒提供一次高度一致性讀取，最多 4KB

## 一致性模式和計費
<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 資料表。您將 100 個 1KB 項目寫入 Table\$1A。對這些單一區域寫入操作而言，每寫入 1KB 將收取 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 區域中。您將 150 個 1KB 項目寫入 us-west-2 區域中的複本資料表。您的第 2 天費用為：
+ 在 us-west-2 區域中用於複寫寫入的 150 個 rWRU
+ 在 us-east-2 區域中用於複寫寫入的 150 個 rWRU

第 2 天收取的總請求單位數：**300 個 rWRU**。

**第 3 天 - 新增全域次要索引：**您將全域次要索引 (GSI) 新增至 us-east-2 區域中的複本資料表，該複本資料表會從基底 (複本) 資料表投射所有屬性。全域資料表會自動在 us-west-2 區域中的複本資料表建立 GSI。您將 200 個新的 1KB 記錄寫入 us-west-2 區域中的複本資料表。您的第 3 天費用為：
+ • 在 us-west-2 區域中用於複寫寫入的 200 個 rWRU
+ • 在 us-west-2 區域中用於 GSI 更新的 200 個 WRU
+ • 在 us-east-2 區域中用於複寫寫入的 200 個 rWRU
+ • 在 us-east-2 區域中用於 GSI 更新的 200 個 WRU

第 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` 命令。如果輸出的 `GlobalTableVersion` 屬性含 "2019.11.21" 值，則資料表為 2019.11.21 版 (目前版本) 全域資料表複本。

`describe-table` 範例 CLI 命令：

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

(簡略版) 輸出的 `GlobalTableVersion` 屬性包含 "2019.11.21" 值，因此這個資料表是 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. 在 [https://console.aws.amazon.com/dynamodb/home](https://console.aws.amazon.com/dynamodb/home) 開啟 DynamoDB 主控台。

1. 在主控台左側的導覽窗格中，選擇 **Tables** (資料表)。

1. 選擇您要識別全域資料表版本的資料表。

1. 選擇 **Global Tables** (全域資料表) 標籤。

   *摘要*區段會顯示所使用的全域資料表版本。

## 舊版和目前版本之間的行為差異
<a name="DiffLegacyVsCurrent"></a>

下列清單說明全域資料表舊版和目前版本之間的行為差異。
+ 相較於 2017.11.29 版 (舊版)，2019.11.21 版 (目前版本) 進行數個 DynamoDB 操作時耗用的寫入容量較少，因此，對大多數客戶來說更具成本效益。這些 DynamoDB 操作的差異如下：
  + 針對區域中的 1KB 項目調用 [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) 並複寫到其他區域時，2017.11.29 版 (舊版) 每個區域需要 2 個 rWRU，但 2019.11.21 版 (目前版本) 只需要 1 個 rWRU。
  + 針對 1KB 項目調用 [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) 時，2017.11.29 版 (舊版) 來源區域需要 2 個 rWRU，且每個目的地區域需要 1 個 rWRU，但在 2019.11.21 版 (目前版本) 的來源和目的地區域都只需要 1 個 rWRU。
  + 針對 1KB 項目調用 [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) 時，2017.11.29 版 (舊版) 來源區域需要 1 個 rWRU，且每個目的地區域需要 2 個 rWRU，但在 2019.11.21 版 (目前版本) 的來源或目的地區域都只需要 1 個 rWRU。

  下表列出針對兩個區域中 1KB 項目 2017.11.29 版 (舊版) 和 2019.11.21 版 (目前版本) 資料表耗用的 rWRU。    
[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/V2globaltables_versions.html)
+ 2017.11.29 版 （舊版） 僅適用於 11 AWS 區域。不過，2019.11.21 版 (目前版本) 可在所有 AWS 區域中使用。
+ 您可以先建立一組空白區域資料表，然後調用 [CreateGlobalTable](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CreateGlobalTable.html) API 構成全域資料表，藉此建立 2017.11.29 版 (舊版) 全域資料表。您可以調用 [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_tw/amazondynamodb/latest/developerguide/V2globaltables_versions.html)

### 升級到 2019.11.21 版(目前版本)
<a name="V2globaltables_versions.upgrade"></a>

請執行下列步驟，使用 AWS 管理主控台升級 DynamoDB 全域資料表版本。

**將全域資料表升級至 2019.11.21 版 (目前版本)**

1. 在 [https://console.aws.amazon.com/dynamodb/home](https://console.aws.amazon.com/dynamodb/home) 開啟 DynamoDB 主控台。

1. 在主控台左側的導覽窗格中，選擇**資料表**，然後選取您要升級至 2019.11.21 版 (目前版本) 的全域資料表。

1. 選擇 **Global Tables** (全域資料表) 標籤。

1. 選擇 **Update version** (更新版本)。  
![\[主控台螢幕擷取畫面，其中顯示 Update (更新) 版本按鈕。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/GlobalTables-upgrade.png)

1. 閱讀並同意新的要求，然後選擇 **Update version** (更新版本)。

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備份建立的複本備份可以跨區域自動複寫，以獲得更高的彈性。在選擇備份和 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 SDKs的範例程式碼。

**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 個有意義位數。前後的零會截去。數字大小大約是 *(屬性名稱的 UTF-8 編碼位元組數目) \$1 (每兩個有效位數 1 位元組) \$1 (1 位元組)*。
+ 必須先以 base64 格式編碼二進位值，才能將它傳送至 DynamoDB，但使用值的原始位元組長度來計算大小。二進制屬性大小是 *(屬性名稱的 UTF-8 編碼位元組數目) \$1 (原始位元組數目)。*
+ Null 屬性或布林值屬性的大小是 *(屬性名稱的 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*，表示若項目尚未存在，它會自動建立項目。
+ `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` 請求包含兩個 PUT 請求和四個刪除請求，則 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 範例會將`Price`產品的 增加 5。在此範例中，在更新計數器之前已知該項目存在。因為 `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_tw/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_tw/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"}
}
```

Alice 的更新會成功，因為條件評估的結果為 true。

接下來，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` 請求將項目的 `Price` 增加 3，但只有在目前的 `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 中，您可以使用*表達式*指定要從項目讀取的屬性、在滿足條件時寫入資料、指定如何更新項目、定義查詢，以及篩選查詢的結果。

本資料表說明基本表達式語法及可用的表達式類型。


| 表達式類型 | Description | 
| --- | --- | 
| 投射表達式 | 使用 GetItem、Query 或 Scan 等操作時，投影表達式會識別您要從項目擷取的屬性。 | 
| 條件表達式 | 使用 PutItem、UpdateItem 和 DeleteItem 等操作時，條件表達式會判定應修改的項目。 | 
| 更新表達式 | 更新表達式會指定 UpdateItem 如何修改項目的屬性，例如設定純量值或將元素從清單或映射中刪除。 | 
| 索引鍵條件表達式 | 索引鍵條件表達式會判定查詢要讀取哪些資料表或索引的項目。 | 
| 篩選條件表達式 | 篩選條件表達式會判定要傳回哪些 Query 結果的項目。所有其他結果皆會捨棄。 | 

如需表達式語法的相關資訊，以及每種表達式類型的詳細資訊，請參閱下列各節。

**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] 代表清單中的第一個元素，[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 允許您使用點字元和其他特殊字元，例如連字號 (「-」) 作為屬性名稱的一部分。這在某些情況下可能會模棱兩可。為了示範，假設您想要從 `ProductCatalog` 項目擷取 `Safety.Warning` 屬性 (請參閱[在 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 會依預設傳回所有項目屬性。若只要取得部分而非全部屬性，請使用投射表達式。

*投射表達式*是識別所需屬性的字串。若要擷取單一屬性，請指定其名稱。若為多個屬性，則必須以逗號分隔名稱。

以下是根據 [在 DynamoDB 中使用表達式時參考項目屬性](Expressions.Attributes.md) 中的 `ProductCatalog` 項目而來的一些投射表達式範例：
+ 單一最上層屬性。

  `Title `
+ 三個最上層屬性。DynamoDB 會擷取整個 `Color` 設定。

  `Title, Price, Color`
+ 四個最上層屬性。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` 關鍵字。您可以依任意順序在更新表達式中包含其中任何子句。不過，每個動作關鍵字都只能出現一次。

在每個子句內，會有以逗號分隔的一或多個動作。每個動作都會代表資料修改。

本節中的範例是以[在 DynamoDB 中使用投影表達式](Expressions.ProjectionExpressions.md)中所顯示的 `ProductCatalog` 項目為基礎。

以下主題涵蓋了 `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] 代表清單中的第一個元素、[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.FiveStar`) 不存在時更新巢狀屬性 （例如 `ProductReviews`)，DynamoDB 會傳回 `ValidationException`，並顯示*「更新表達式中提供的文件路徑無效，無法更新」訊息。*  
建立稍後更新巢狀對應屬性的項目時，請初始化父屬性的空對應。例如：  

```
{
    "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` 的*開頭*。若要執行此作業，請切換 `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` 屬性現在會包含五個元素，順序如下：`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`)，讓它包含五個元素：  
+ `[0]`—`Chisel`
+ `[1]`—`Hammer`
+ `[2]`—`Nails`
+ `[3]`—`Screwdriver`
+ `[4]`—`Hacksaw`
下列 AWS Command Line Interface (AWS CLI) 範例`Nails`會從清單中刪除 `Hammer`和 。  

```
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` 或集合資料類型。
+ *value* 元素是您要新增至屬性的數字 (針對 `Number` 資料類型)，或要附加至屬性的集合 (針對集合類型)。

```
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* 元素是屬性的文件路徑。屬性必須是集合資料類型。
+ *subset* 是您要從 *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 動作。此操作會將「釘子」新增到 `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 SDKs程式設計](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>

在以下語法摘要中，*operand* 可以是以下項目：
+ 最上層屬性名稱，例如 `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>

使用下列函數可判斷項目中是否具有某屬性，或評估某屬性的值。這些函數名稱區分大小寫。針對巢狀屬性，您必須提供其完整文件路徑。


****  

| 函式 | Description | 
| --- | --- | 
|  `attribute_exists (path)`  | 如果項目包含 `path` 指定的屬性，則為 true。 範例：檢查 `Product` 資料表中的項目是否具有側視圖。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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_tw/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_tw/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_tw/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_tw/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_tw/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_tw/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html) 表達式屬性值 `:v_sub` 是 `Company` 的預留位置。 範例：檢查產品是否提供紅色款式。 [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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_tw/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html)  如果屬性的類型為 `Binary`，`size` 會傳回屬性值中的位元組數目。 範例：假設 `ProductCatalog` 項目具有名為 `VideoClip` 的二進制屬性，其中包含使用中產品的短片。以下表達式會檢查 `VideoClip` 是否超過 64,000 個位元組。表達式屬性值 `:v_sub` 是 `64000` 的預留位置。[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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_tw/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_tw/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) 範例。這些範例是以 [在 DynamoDB 中使用表達式時參考項目屬性](Expressions.Attributes.md) 中引進的 `ProductCatalog` 資料表為基礎。此資料表的分割區索引鍵是 `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)` AND `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 會傳回以下錯誤訊息：The conditional request failed (條件式請求失敗)。

**注意**  
如需 `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，則操作會成功；否則，操作會失敗。

只有在產品沒有 `Price` 屬性時，以下範例才會使用 `attribute_not_exists` 來刪除產品。

```
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，則操作會成功；否則，操作會失敗。

下列範例只有在具有字串集合類型的 `Color` 屬性的情況下，才會使用 `attribute_type` 刪除產品。

```
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，則操作會成功；否則，操作會失敗。

只有在 `Color` 字串集合具有含特定值的元素時，下列範例才會使用 `contains` 刪除產品。

```
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，則操作會成功；否則，操作會失敗。

只有在 `VideoClip` 二進制屬性大於 `64000` 位元組時，下列範例才會使用 `size` 刪除產品。

```
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 過期時間戳記。時間戳記必須以秒精細程度以 [Unix epoch 時間格式](https://en.wikipedia.org/wiki/Unix_time)儲存為[數字](HowItWorks.NamingRulesDataTypes.md#HowItWorks.DataTypes)資料類型。TTL 程序會忽略 TTL 屬性不是數字類型的項目。每次建立或更新項目時，您可計算其過期時間，並將結果儲存在 TTL 屬性中。

具有有效且已過期 TTL 屬性的項目，可能會在過期後數天內由系統自動刪除。您仍可更新尚未刪除的過期項目，例如變更或移除其 TTL 屬性。在更新過期項目時，建議使用條件運算式，以確保該項目未在此期間被刪除。使用篩選運算式，從[掃描](Scan.md#Scan.FilterExpression)與[查詢](Query.FilterExpression.md)結果中排除過期項目。

被刪除的項目運作方式與一般刪除操作所刪除的項目相同。項目刪除後，會以「服務刪除」的形式送入 DynamoDB Streams，而非「使用者刪除」，並會從本機次要索引與全域次要索引中移除，與其他刪除操作相同。

如果您使用 [Global Tables version 2019.11.21 (目前版本)](GlobalTables.md)，且同時啟用 TTL 功能，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) 中啟用 TTL，或使用 [Amazon DynamoDB API 參考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/)與任何支援的 AWS SDKs。在所有分割區啟用 TTL 約需一小時。

## 使用 AWS 主控台啟用 DynamoDB TTL
<a name="time-to-live-ttl-how-to-enable-console"></a>

1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 選擇 **Tables (資料表)**，然後選擇您想要修改的資料表。

1. 在**附加設定**分頁的**存留時間 (TTL)**區段中，選取**開啟**以啟用 TTL。

1. 在資料表上啟用 TTL 時，DynamoDB 會要求您識別服務在判斷項目是否符合過期資格時將尋找的特定屬性名稱。如下所示的 TTL 屬性名稱區分大小寫，且必須與讀寫操作中定義的屬性一致。若不一致，過期項目將無法被刪除。若要重新命名 TTL 屬性，您必須先停用 TTL，然後使用新的屬性名稱重新啟用。停用後，TTL 仍會在約 30 分鐘內持續處理刪除作業。還原資料表後，必須重新設定 TTL。  
![\[DynamoDB 用於判定項目是否符合過期條件的區分大小寫 TTL 屬性名稱。\]](http://docs.aws.amazon.com/zh_tw/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 及 AWS CLI，將項目新增至已設定存留時間屬性的 `TTLExample` 資料表。

   ```
   EXP=`date -d '+5 days' +%s`
   aws dynamodb put-item --table-name "TTLExample" --item '{"id": {"N": "1"}, "ttl": {"N": "'$EXP'"}}'
   ```

此範例建立的過期時間為從目前的日期開始加 5 天。然後，將過期時間轉換成 Epoch 時間格式，最終將項目新增到 "`TTLExample`" 表。

**注意**  
 設定存留時間過期數值的其中一種方式，是計算要新增到過期時間的秒數。例如，5 天等於 432,000 秒。但是通常建議從日期開始操作。

取得目前時間的 Epoch 時間格式非常容易，如以下範例所示。
+ Linux 終端機：`date +%s`
+ Python：`import time; int(time.time())`
+ Java：`System.currentTimeMillis() / 1000L`
+ JavaScript：`Math.floor(Date.now() / 1000)`

## 使用 啟用 DynamoDB TTL CloudFormation
<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 天。

計算出的過期時間必須採用 epoch 格式 (以秒為單位)。要符合過期與刪除條件，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 天。接著，將時間轉換為 epoch 格式，並以整數型別儲存在 TTL 屬性中。

下列程式碼範例示範如何建立含有 TTL 的項目。

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

**適用於 Java 2.x 的 SDK**  

```
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 參考*》中的 [PutItem](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/PutItem)。

------
#### [ JavaScript ]

**適用於 JavaScript (v3) 的 SDK**  

```
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 參考*》中的 [PutItem](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/PutItemCommand)。

------
#### [ Python ]

**適用於 Python (Boto3) 的 SDK**  

```
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 的詳細資訊，請參閱《*適用於 Python (Boto3) 的AWS SDK API 參考*》中的 [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 2.x 的 SDK**  
更新資料表中現有 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 參考](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateItem)》中的 *UpdateItem*。

------
#### [ JavaScript ]

**適用於 JavaScript (v3) 的 SDK**  

```
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 參考](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/UpdateItemCommand)》中的 *UpdateItem*。

------
#### [ Python ]

**適用於 Python (Boto3) 的 SDK**  

```
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 的詳細資訊，請參閱《*適用於 Python (Boto3) 的AWS SDK API 參考*》中的 [UpdateItem](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/UpdateItem)。

------

本介紹中的 TTL 範例示範了一種方法，可確保僅保留最近更新的項目於資料表中。更新的項目其存留期會延長，而建立後未更新的項目將在到期後自動刪除，不產生額外成本，有助於減少儲存並維持資料表整潔。

# 操作過期項目與存留時間 (TTL)
<a name="ttl-expired-items"></a>

可從讀取與寫入操作中篩選出待刪除的過期項目。當過期資料已失效且不應再使用時，這項設計特別實用。若未進行篩選，這些項目將持續出現在讀取與寫入操作中，直到由背景程序刪除。

**注意**  
在刪除前，這些項目仍計入儲存與讀取成本。

TTL 刪除可在 DynamoDB 串流中識別，但僅限於刪除發生的區域。被複寫至全域資料表其他區域的 TTL 刪除，無法在那些區域的 DynamoDB 串流中識別。

## 從讀取操作中篩選過期項目
<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` 以符合 epoch 時間格式。

下列程式碼範例示範如何查詢 TTL 項目。

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

**適用於 Java 2.x 的 SDK**  
查詢篩選表達式，以使用 在 DynamoDB 資料表中收集 TTL 項目 AWS SDK for Java 2.x。  

```
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 參考*》中的 [Query](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/Query)。

------
#### [ JavaScript ]

**適用於 JavaScript (v3) 的 SDK**  
查詢篩選表達式，以使用 在 DynamoDB 資料表中收集 TTL 項目 適用於 JavaScript 的 AWS SDK。  

```
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 參考*》中的 [Query](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/QueryCommand)。

------
#### [ Python ]

**適用於 Python 的 SDK (Boto3)**  
查詢篩選表達式，以使用 在 DynamoDB 資料表中收集 TTL 項目 適用於 Python (Boto3) 的 AWS SDK。  

```
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 的詳細資訊，請參閱《*適用於 Python (Boto3) 的AWS SDK API 參考*》中的 [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 2.x 的 SDK**  
使用條件更新資料表中現有的 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 參考](https://docs.aws.amazon.com/goto/SdkForJavaV2/dynamodb-2012-08-10/UpdateItem)》中的 *UpdateItem*。

------
#### [ JavaScript ]

**適用於 JavaScript (v3) 的 SDK**  
使用條件更新資料表中現有的 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 參考](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/dynamodb/command/UpdateItemCommand)》中的 *UpdateItem*。

------
#### [ Python ]

**適用於 Python (Boto3) 的 SDK**  
使用條件更新資料表中現有的 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 的詳細資訊，請參閱《*適用於 Python (Boto3) 的AWS SDK API 參考*》中的 [UpdateItem](https://docs.aws.amazon.com/goto/boto3/dynamodb-2012-08-10/UpdateItem)。

------

## 在 DynamoDB 串流中識別已刪除的項目
<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` 值的項目，因為 `KeyConditionExpression` 中不包含排序索引鍵 (`Subject`)。  

```
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 範例會查詢特定 `ForumName`（分割區索引鍵） 和 `Subject`（排序索引鍵） 的`Thread`資料表。在找到的項目中，只會傳回最受歡迎的討論主題：換句話說，只會傳回具有超過一定 `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` 值，並用它做為新 `Query` 要求的 `ExclusiveStartKey` 參數。

1. 執行新的 `Query` 請求。

1. 前往步驟 1。

換句話說，`LastEvaluatedKey` 回應的 `Query` 應做為下一個 `ExclusiveStartKey` 請求的 `Query` 使用。若 `LastEvaluatedKey` 回應中沒有 `Query` 元素，表示您已擷取到結果的最終頁。如果 `LastEvaluatedKey` 不是空的，則不一定意味著結果集中有更多資料。檢查 `LastEvaluatedKey` 是否為空，是確定您是否已到達結果集末頁的唯一方式。

您可以使用 AWS CLI 來檢視此行為。會重複 AWS CLI 傳送低階`Query`請求至 DynamoDB，直到結果`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 然後， 向 DynamoDB 發出另一個`Query`請求。此請求和回應模式會持續到最終回應出現為止。

```
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 SDKs 會處理低階 DynamoDB 回應 （包括是否存在 `LastEvaluatedKey`)，並提供用於分頁`Query`結果的各種抽象概念。例如，適用於 Java 的開發套件文件介面會提供 `java.util.Iterator` 支援，讓您可一次處理一個結果。  
如需各種程式設計語言的程式碼範例，請參閱《[Amazon DynamoDB 入門指南](https://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/)》和所需語言的 AWS 軟體開發套件文件。

您還可以透過限制結果集的項目數 (使用 `Query` 操作的 `Limit` 參數) 來減少頁面大小。

如需使用 DynamoDB 進行查詢的詳細資訊，請參閱 [在 DynamoDB 中查詢資料表](Query.md)。

# 在 DynamoDB 中使用查詢操作的其他方面
<a name="Query.Other"></a>

本節涵蓋 DynamoDB 查詢操作的其他層面，包括限制結果大小、計算掃描項目與傳回項目、監控讀取容量耗用量，以及控制讀取一致性。

## 限制結果集的項目數
<a name="Query.Limit"></a>

您可以使用 `Query` 操作來限制其讀取的項目數。若要執行此作業，請將 `Limit` 參數設為您希望的最大項目數。

例如，假設您 `Query` 一份資料表，將 `Limit` 值設為 `6` 且不使用篩選條件表達式。`Query` 結果會包含資料表中符合請求索引鍵條件表達式的前六個項目。

現在假設您在 `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` 回應都包含經該特定 `Query` 請求處理過的項目 `ScannedCount` 和 `Count`。若要取得所有 `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` 結果會包含資料表中的前六個項目。

現在假設您在 `Scan` 中新增一個篩選條件表達式。在本案例中，DynamoDB 會將篩選條件表達式套用至傳回的六個項目，捨棄那些不符的項目。最終的 `Scan` 結果會包含 6 個或以下項目，視篩選的項目數而定。

## 為結果編製分頁
<a name="Scan.Pagination"></a>

DynamoDB 會對 `Scan` 操作的結果進行*分頁*。透過編製分頁，`Scan` 結果會分成數個大小為 1 MB (或更小) 的資料「頁」。應用程式可以處理結果的第一頁、第二頁，以此類推。

單一 `Scan` 只會傳回符合 1 MB 大小限制的結果集。

為判斷是否有更多結果，並且一次擷取一頁結果，應用程式應執行下列作業：

1. 檢查低層級 `Scan` 結果：
   + 若結果包含 `LastEvaluatedKey` 元素，請接著進行步驟 2。
   + 若結果中*沒有* `LastEvaluatedKey`，就表示再也沒有要擷取的項目。

1. 建構新的 `Scan` 請求，和前一個請求使用相同的參數。但這一次採用步驟 1 的 `LastEvaluatedKey` 值，並用它做為新 `Scan` 要求的 `ExclusiveStartKey` 參數。

1. 執行新的 `Scan` 請求。

1. 前往步驟 1。

換句話說，`LastEvaluatedKey` 回應的 `Scan` 應做為下一個 `ExclusiveStartKey` 請求的 `Scan` 使用。若 `Scan` 回應中沒有 `LastEvaluatedKey` 元素，表示您已擷取到結果的最終頁。(檢查是否沒有 `LastEvaluatedKey` 是確定您是否已到達結果集末頁的唯一方式)。

您可以使用 AWS CLI 來檢視此行為。會重複 AWS CLI 傳送低階`Scan`請求至 DynamoDB，直到結果`LastEvaluatedKey`中不再出現 為止。請考慮下列掃描整個`Movies`資料表，但僅傳回特定類型電影 AWS CLI 的範例。

```
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 然後， 向 DynamoDB 發出另一個`Scan`請求。此請求和回應模式會持續到最終回應出現為止。

```
2017-07-07 12:19:17,830 - MainThread - botocore.parsers - DEBUG - Response body:
b'{"Count":1,"Items":[{"title":{"S":"WarGames"}}],"ScannedCount":6}'
```

若沒有 `LastEvaluatedKey`，就表示已不再有要擷取的項目。

**注意**  
 AWS SDKs 會處理低階 DynamoDB 回應 （包括是否存在 `LastEvaluatedKey`)，並提供用於分頁`Scan`結果的各種抽象概念。例如，適用於 Java 的開發套件文件介面會提供 `java.util.Iterator` 支援，讓您可一次處理一個結果。  
如需各種程式設計語言的程式碼範例，請參閱《[Amazon DynamoDB 入門指南](https://docs.aws.amazon.com/amazondynamodb/latest/gettingstartedguide/)》和所需語言的 AWS 軟體開發套件文件。

## 計算結果中的項目
<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` 回應都包含經該特定 `Scan` 請求處理過的項目 `ScannedCount` 和 `Count`。若要取得所有 `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)，在資料表備份或複寫案例中很實用。首先使用 `ConsistentRead` 設為 true 的 `Scan`，取得和資料表資料一致的副本。在 `Scan` 期間，DynamoDB Streams 會紀錄資料表發生的所有額外寫入活動。`Scan` 完成後，您可將串流的寫入活動套用至資料表。

**注意**  
與將 `ConsistentRead` 保留預設值 (`false`) 相較，將 `ConsistentRead` 設成 `true` 的 `Scan` 操作會使用兩倍的讀取容量單位。

## 平行掃描
<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_tw/amazondynamodb/latest/developerguide/images/ParallelScan.png)




在此圖中，應用程式產生三個執行緒，並為每個執行緒指派一個數字。(區段從零開始，所以第一個數字永遠為 0。) 每個執行緒都會發出 `Scan` 請求、將 `Segment` 設定為指定號碼，並將 `TotalSegments` 設定為 3。每個執行緒都會掃描其指定的區段，一次擷取 1 MB 的資料，並將資料傳回至應用程式的主執行緒。

DynamoDB 透過將雜湊函數套用至每個項目的分割區索引鍵，將項目指派給*區段*。對於指定的`TotalSegments`值，具有相同分割區索引鍵的所有項目一律會指派給相同的 `Segment`。這表示在*項目 1*、*項目 2* 和*項目 3* 全部共用 `pk="account#123"`（但具有不同的排序索引鍵） 的表格中，這些項目將由相同的工作者處理，無論排序索引鍵值或*項目集合*的大小為何。

由於*區段*指派僅以分割區索引鍵雜湊為基礎，因此區段可能分佈不均勻。某些區段可能不包含任何項目，而其他區段則可能包含具有大型項目集合的許多分割區索引鍵。因此，增加區段總數並不能保證更快的掃描效能，特別是當分割區索引鍵未均勻分佈到金鑰空間時。

`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 Command Line Interface，並使用 PartiQL 的 AWS 管理主控台、NoSQL Workbench 和 DynamoDB APIs 執行臨機操作查詢。

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 規範和核心查詢語言的教學課程，請參閱 [PartiQL 文件](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
+ The AWS Command Line Interface (AWS CLI)
+ DynamoDB API

如需使用這些方法存取 DynamoDB 的相關資訊，請參閱[存取 DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AccessingDynamoDB.html)。

# DynamoDB 專用 PartiQL 入門
<a name="ql-gettingstarted"></a>

本節說明如何從 Amazon DynamoDB 主控台、 AWS Command Line Interface (AWS CLI) 和 DynamoDB API 使用 DynamoDB 專用 PartiQL。

在下列範例中，[DynamoDB 入門](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStartedDynamoDB.html)教學課程中所定義的 DynamoDB 資料表是先決條件。

如需有關使用 DynamoDB 主控台 AWS Command Line Interface或 DynamoDB APIs 存取 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) 來建置 [DynamoDB 專用 PartiQL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.html) 陳述式，請選擇位於 NoSQL Workbench for DynamoDB [Operation Builder](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.querybuilder.operationbuilder.html) 右上角的 **PartiQL operations** (PartiQL 操作)。

------
#### [ Console ]

![\[PartiQL 編輯器介面，顯示在 Music 資料表上執行查詢操作的結果。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/partiqlgettingstarted.png)


1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在主控台左側的導覽窗格中，選擇 **PartiQL editor** (PartiQL 編輯器)。

1. 選擇 **Music** (音樂) 資料表。

1. 選擇 **Query table** (查詢資料表)。此動作會產生不會導致完整資料表掃描的查詢。

1. 使用字串值 `Acme Band` 取代 `partitionKeyValue`。使用字串值 `Happy Day` 取代 `sortKeyValue`。

1. 選擇 **Run** (執行) 按鈕。

1. 您可以選擇 **Table view** (資料表檢視) 或 **JSON view** (JSON 檢視) 按鈕來檢視查詢的結果。

------
#### [ NoSQL workbench ]

![\[NoSQL Workbench 介面。它會顯示 PartiQL SELECT 陳述式，您可以在 Music 資料表上執行。\]](http://docs.aws.amazon.com/zh_tw/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. 使用 UPDATE 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. 使用刪除 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>

下表列出您可搭配 DynamoDB 專用 PartiQL 使用的資料類型。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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)。

# 適用於 DynamoDB 的 PartiQL 陳述式
<a name="ql-reference.statements"></a>

Amazon DynamoDB 支援下列 PartiQL 陳述式。

**注意**  
DynamoDB 不支援所有 PartiQL 陳述式。  
此參考提供使用 AWS CLI 或 APIs 手動執行之 PartiQL 陳述式的基本語法和使用範例。

*資料操作語言* (DML) 是一組用來管理 DynamoDB 資料表中資料的 PartiQL 陳述式。您可使用 DML 陳述式新增、修改或刪除資料表中的資料。

支援下列 DML 和查詢語言陳述式：
+ [適用於 DynamoDB 的 PartiQL Select 陳述式](ql-reference.select.md)
+ [適用於 DynamoDB 的 PartiQL Update 陳述式](ql-reference.update.md)
+ [適用於 DynamoDB 的 PartiQL Insert 陳述式](ql-reference.insert.md)
+ [適用於 DynamoDB 的 PartiQL Delete 陳述式](ql-reference.delete.md)

DynamoDB 專用 PartiQL 亦支援 [使用 DynamoDB 專用 PartiQL 執行交易](ql-reference.multiplestatements.transactions.md) 和 [使用 DynamoDB 專用 PartiQL 執行批次操作](ql-reference.multiplestatements.batching.md)。

# 適用於 DynamoDB 的 PartiQL Select 陳述式
<a name="ql-reference.select"></a>

在 Amazon DynamoDB 中，使用 `SELECT` 陳述式從資料表檢索資料。

如果 WHERE 子句中未提供與分割區索引鍵相等或 IN 條件，使用 `SELECT` 陳述式可能會導致完整的資料表掃描。掃描操作會檢查每個項目的要求值，並可能會在單一操作中耗用大型資料表或索引的佈建輸送量。

如果您想避免在 PartiQL 中進行完整的資料表掃描，則可以：
+ 確保 [WHERE 子句條件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ql-reference.select.html#ql-reference.select.parameters)會據此進行相應設定，撰寫您的 `SELECT` 陳述式便不會導致完整的資料表掃描。
+ 請使用《DynamoDB 開發人員指南》中 [範例：允許在 DynamoDB 專用 PartiQL 中執行選取陳述式並拒絕完整的資料表掃描陳述式](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)
+ [Parameters](#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] , ...]
```

## Parameters
<a name="ql-reference.select.parameters"></a>

***表達式***  
(必要) 從 `*` 萬用字元形成的投影，或來自結果集中一或多個屬性名稱或文件路徑的投影清單。一個表達式可以包含對 [搭配 DynamoDB 使用 PartiQL 函數](ql-functions.md) 的呼叫或由 [適用於 DynamoDB 的 PartiQL 算術、比較和邏輯運算子](ql-operators.md) 修改的欄位。

***資料表***  
(必要) 要查詢的資料表名稱。

***索引***  
(選用) 要查詢的索引名稱。  
查詢索引時，必須在資料表名稱和索引名稱上加上雙引號。  

```
SELECT * 
FROM "TableName"."IndexName"
```

***condition***  
(選用) 查詢的選取條件。  
若要確保 `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
```

下列查詢會針對具有特定分割區索引鍵、`OrderID` 和值的 `Orders` 資料表，使用 OR 運算子傳回其中的所有項目。

```
SELECT OrderID, Total
FROM "Orders"
WHERE OrderID = 1 OR OrderID = 2
```

下列查詢會針對具有特定分割區索引鍵、`OrderID` 和值的 `Orders` 資料表，使用 IN 運算子傳回其中的所有項目。傳回的結果會根據 `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` 之特定 `Total` 順序範圍內 `Orders` 資料表中的所有項目。

```
SELECT OrderID, Total 
FROM "Orders"
WHERE Total IN [500, 600]
```

下列查詢顯示完整的資料表掃描，該掃描會傳回使用 BETWEEN 運算子和非索引鍵屬性 `Total` 之特定 `Total` 順序範圍內 `Orders` 資料表中的所有項目。

```
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 子句條件中的文件路徑傳回在 2019 年 12 月 24 日之後首次使用 FireStick 裝置的項目清單。

```
SELECT Devices 
FROM WatchList 
WHERE Devices.FireStick.DateWatched[0] >= '12/24/19'
```

# 適用於 DynamoDB 的 PartiQL Update 陳述式
<a name="ql-reference.update"></a>

使用 `UPDATE` 陳述式來修改 Amazon DynamoDB 資料表中項目內一或多個屬性的值。

**注意**  
您一次只能更新一個項目，因為您無法發出可刪除多個項目的單個 DynamoDB PartiQL 陳述式。如需更新多個項目的相關資訊，請參閱 [使用 DynamoDB 專用 PartiQL 執行交易](ql-reference.multiplestatements.transactions.md) 或 [使用 DynamoDB 專用 PartiQL 執行批次操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [語法](#ql-reference.update.syntax)
+ [Parameters](#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] *
```

## Parameters
<a name="ql-reference.update.parameters"></a>

***資料表***  
(必要) 包含要修改之資料的資料表。

***路徑***  
(必要) 要建立或修改的屬性名稱或文件路徑。

***資料***  
(必要) 屬性值或操作的結果。  
與 SET 搭配使用的支援操作：  
+ LIST\$1APPEND：將數值新增至清單類型。
+ SET\$1ADD：將數值新增至數字或字串集。
+ SET\$1DELETE：從數字或字串集中刪除數值。

***condition***  
(必要) 要修改之項目的選取條件。此條件必須解析為單一主索引鍵值。

***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>

更新現有項目中的屬性值。如果屬性不存在，則會建立此屬性。

下列查詢會透過新增數字類型的屬性 (`AwardsWon`) 和映射類型的屬性 (`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'
```

# 適用於 DynamoDB 的 PartiQL Delete 陳述式
<a name="ql-reference.delete"></a>

使用 `DELETE` 陳述式從您的 Amazon DynamoDB 資料表中刪除現有項目。

**注意**  
您一次只能刪除一個項目。您無法發出可刪除多個項目的單個 DynamoDB PartiQL 陳述式。如需刪除多個項目的相關資訊，請參閱 [使用 DynamoDB 專用 PartiQL 執行交易](ql-reference.multiplestatements.transactions.md) 或 [使用 DynamoDB 專用 PartiQL 執行批次操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [語法](#ql-reference.delete.syntax)
+ [Parameters](#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 *
```

## Parameters
<a name="ql-reference.delete.parameters"></a>

***資料表***  
(必要) 包含要刪除之項目的 DynamoDB 資料表。

***condition***  
(必要) 要刪除之項目的選取條件；此條件必須解析為單一主索引鍵值。

***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"
            }
        }
    ]
}
```

# 適用於 DynamoDB 的 PartiQL Insert 陳述式
<a name="ql-reference.insert"></a>

使用 `INSERT` 陳述式在 Amazon DynamoDB 中將項目新增至資料表。

**注意**  
您一次只能插入一個項目，因為您無法發出可插入多個項目的單個 DynamoDB PartiQL 陳述式。如需插入多個項目的相關資訊，請參閱 [使用 DynamoDB 專用 PartiQL 執行交易](ql-reference.multiplestatements.transactions.md) 或 [使用 DynamoDB 專用 PartiQL 執行批次操作](ql-reference.multiplestatements.batching.md)。

**Topics**
+ [語法](#ql-reference.insert.syntax)
+ [Parameters](#ql-reference.insert.parameters)
+ [傳回值](#ql-reference.insert.return)
+ [範例](#ql-reference.insert.examples)

## 語法
<a name="ql-reference.insert.syntax"></a>

插入單一項目。

```
INSERT INTO table VALUE item
```

## Parameters
<a name="ql-reference.insert.parameters"></a>

***資料表***  
(必要) 您要插入資料的資料表。索資料表必須已存在。

***項目***  
(必要) 有效的 DynamoDB 項目表示為 [PartiQL 元組](https://partiql.org/docs.html)。您必須在 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'}
```

# 搭配 DynamoDB 使用 PartiQL 函數
<a name="ql-functions"></a>

Amazon DynamoDB 中的 PartiQL 支援下列 SQL 標準函數的內建變體。

**注意**  
DynamoDB 目前不支援任何未包含在此清單中的 SQL 函數。

## 彙總函數
<a name="ql-functions.aggregate"></a>
+ [搭配 Amazon DynamoDB 專用 PartiQL 使用 SIZE 函數](ql-functions.size.md)

## 條件函數
<a name="ql-functions.conditional"></a>
+ [搭配 DynamoDB 專用 PartiQL 使用 EXISTS 函數](ql-functions.exists.md)
+ [搭配 DynamoDB 專用 PartiQL 使用 ATTRIBUTE\$1TYPE 函數](ql-functions.attribute_type.md)
+ [搭配 DynamoDB 專用 PartiQL 使用 BEGINS\$1WITH 函數](ql-functions.beginswith.md)
+ [搭配 DynamoDB 專用 PartiQL 使用 CONTAINS 函數](ql-functions.contains.md)
+ [搭配 DynamoDB 專用 PartiQL 使用 MISSING 函數](ql-functions.missing.md)

# 搭配 DynamoDB 專用 PartiQL 使用 EXISTS 函數
<a name="ql-functions.exists"></a>

您可以使用 EXISTS 來執行與 `ConditionCheck` 會在 [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems) API 中執行的相同函數。EXISTS 函數只能在交易中使用。

給定一個值，如果該值是一個非空集合，則傳回 `TRUE`。如果不是，則傳回 `FALSE`。

**注意**  
此函數只能在交易操作中使用。

## 語法
<a name="ql-functions.exists.syntax"></a>

```
EXISTS ( statement )
```

## 引數
<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')
```

# 搭配 DynamoDB 專用 PartiQL 使用 BEGINS\$1WITH 函數
<a name="ql-functions.beginswith"></a>

如果指定的屬性以特定子字串開頭，則傳回 `TRUE`。

## 語法
<a name="ql-functions.beginswith.syntax"></a>

```
begins_with(path, value )
```

## 引數
<a name="ql-functions.beginswith.arguments"></a>

*路徑*  
(必要) 要使用的屬性名稱或文件路徑。

*值*  
(必要) 要搜尋的字串。

## 傳回類型
<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')
```

# 搭配 DynamoDB 專用 PartiQL 使用 MISSING 函數
<a name="ql-functions.missing"></a>

如果項目不包含指定的屬性，即傳回 `TRUE`。只有等於和不等於運算子可以與此函數一起使用。

## 語法
<a name="ql-functions.missing.syntax"></a>

```
 attributename IS | IS NOT  MISSING 
```

## 引數
<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
```

# 搭配 DynamoDB 專用 PartiQL 使用 ATTRIBUTE\$1TYPE 函數
<a name="ql-functions.attribute_type"></a>

如果指定路徑的屬性是特定資料類型，則傳回 `TRUE`。

## 語法
<a name="ql-functions.attribute_type.syntax"></a>

```
attribute_type( attributename, type )
```

## 引數
<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')
```

# 搭配 DynamoDB 專用 PartiQL 使用 CONTAINS 函數
<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 )
```

## 引數
<a name="ql-functions.contains.arguments"></a>

*路徑*  
(必要) 要使用的屬性名稱或文件路徑。

*子字串*  
(必要) 要檢查的屬性子字串或集合成員。如需詳細資訊，請參閱 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')
```

# 搭配 Amazon DynamoDB 專用 PartiQL 使用 SIZE 函數
<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)
```

## 引數
<a name="ql-functions.size.arguments"></a>

*路徑*  
(必要) 屬性名稱或文件路徑。  
如需了解支援的類型，請參閱 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>


****  

| 運算子 | Description | 
| --- | --- | 
| \$1 | 加 | 
| - | 減 | 

## 比較運算子
<a name="ql-operators.comparison"></a>


****  

| 運算子 | Description | 
| --- | --- | 
| = | 等於 | 
| <> | 不等於 | 
| \$1= | 不等於 | 
| > | Greater than | 
| < | Less than | 
| >= | 大於或等於 | 
| <= | 小於或等於 | 

## 邏輯運算子
<a name="ql-operators.logical"></a>


****  

| 運算子 | Description | 
| --- | --- | 
| AND | 如果以 AND 分隔的所有的條件為 TRUE，則為 TRUE | 
| BETWEEN |  如果運算元在比較範圍內，則為 `TRUE`。 此運算子包含您套用運算元的下限和上限。  | 
| IN | `TRUE` 如果運算元等於表達式清單之一 （上限為 50 個雜湊屬性值或上限為 100 個非索引鍵屬性值）。 結果會以最多 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)。

# 使用 DynamoDB 專用 PartiQL 執行交易
<a name="ql-reference.multiplestatements.transactions"></a>

本節說明如何搭配 DynamoDB 專用 PartiQL 使用交易。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)
+ [Parameters](#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 "
         }, ...]
   } , ...
]
```

## Parameters
<a name="ql-reference.multiplestatements.transactions.parameters"></a>

***陳述式***  
(必要) DynamoDB 專用 PartiQL 支援的陳述式。  
整個交易必須由讀取陳述式或寫入陳述式所組成。你不能在一個交易中混合兩者。

***parametertype***  
(選用) DynamoDB 類型，如果在指定 PartiQL 陳述式時使用了參數。

***parametervalue***  
(選用) 參數值，如果在指定 PartiQL 陳述式時使用了參數。

## 傳回值
<a name="ql-reference.multiplestatements.transactions.return"></a>

此陳述式不會傳回寫入作業 (插入、更新或刪除) 的任何值。但是，它根據 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
           {}
       ]
   }
   ```

------

# 使用 DynamoDB 專用 PartiQL 執行批次操作
<a name="ql-reference.multiplestatements.batching"></a>

本節說明如何搭配 DynamoDB 專用 PartiQL 使用批次陳述式。

**注意**  
整個批次必須由讀取陳述式或寫入陳述式所組成；一個批次中不能同時出現這兩種陳述式樣。
`BatchExecuteStatement` 和 `BatchWriteItem` 每個批次不可以執行超過 25 條陳述式。
`BatchExecuteStatement` 會使用 `BatchGetItem`，其會取得個別陳述式中主索引鍵的清單。

**Topics**
+ [語法](#ql-reference.multiplestatements.batching.syntax)
+ [Parameters](#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 "
         }, ...]
   } , ...
]
```

## Parameters
<a name="ql-reference.multiplestatements.batching.parameters"></a>

***陳述式***  
(必要) DynamoDB 專用 PartiQL 支援的陳述式。  
+ 整個批次必須由讀取陳述式或寫入陳述式所組成；一個批次中不能同時出現這兩種陳述式樣。
+ `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());
        }
    }

}
```

------

# DynamoDB 專用 PartiQL 的 IAM 安全政策
<a name="ql-iam"></a>

需要以下許可：
+ 若要使用 DynamoDB 專用 PartiQL 讀取項目，您必須擁有資料表或索引的 `dynamodb:PartiQLSelect` 許可。
+ 若要使用 DynamoDB 專用 PartiQL 插入項目，您必須擁有資料表或索引的 `dynamodb:PartiQLInsert` 許可。
+ 若要使用 DynamoDB 專用 PartiQL 更新項目，您必須擁有資料表或索引的 `dynamodb:PartiQLUpdate` 許可。
+ 若要使用 DynamoDB 專用 PartiQL 刪除項目，您必須擁有資料表或索引的 `dynamodb:PartiQLDelete` 許可。

## 範例：允許在資料表上執行所有 DynamoDB 專用 PartiQL 陳述式 (選取/插入/更新/刪除)
<a name="access-policy-ql-iam-example1"></a>

下列 IAM 政策會授予許可，允許在資料表上執行所有 DynamoDB 專用 PartiQL 陳述式。

------
#### [ 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"
         ]
      }
   ]
}
```

------

## 範例：允許在資料表上執行 DynamoDB 專用 PartiQL 選取陳述式
<a name="access-policy-ql-iam-example2"></a>

下列 IAM 政策會授予許可，允許在特定資料表上執行 `select` 陳述式。

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

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLSelect"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music"
         ]
      }
   ]
}
```

------

## 範例：允許在索引上執行 DynamoDB 專用 PartiQL 插入陳述式
<a name="access-policy-ql-iam-example3"></a>

下列 IAM 政策會授予許可，允許在特定索引上執行 `insert` 陳述式。

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

****  

```
{
   "Version":"2012-10-17",		 	 	 
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "dynamodb:PartiQLInsert"
         ],
         "Resource":[
            "arn:aws:dynamodb:us-west-2:123456789012:table/Music/index/index1"
         ]
      }
   ]
}
```

------

## 範例：允許在資料表上僅執行 DynamoDB 專用 PartiQL 交易陳述式
<a name="access-policy-ql-iam-example4"></a>

下列 IAM 政策會授予許可，允許僅在特定資料表上執行交易陳述式。

------
#### [ 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"
               ]
            }
         }
      }
   ]
}
```

------

## 範例：允許在資料表上執行 DynamoDB 專用 PartiQL 非交易式讀取和寫入，並封鎖 PartiQL 交易式讀取和寫入交易陳述式。
<a name="access-policy-ql-iam-example5"></a>

 下列 IAM 政策會授予許可，允許執行 DynamoDB 專用 PartiQL 非交易式讀取和寫入，同時封鎖 DynamoDB 專用 PartiQL 交易式讀取和寫入。

------
#### [ 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"
         ]
      }
   ]
}
```

------

## 範例：允許在 DynamoDB 專用 PartiQL 中執行選取陳述式並拒絕完整的資料表掃描陳述式
<a name="access-policy-ql-iam-example6"></a>

下列 IAM 政策會授予許可，允許在特定資料表上執行 `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) 操作。

**注意**  
適用於 Java 的開發套件也提供物件持久性模型，讓您將用戶端類別映射至 DynamoDB 資料表。此方法可以減少您必須撰寫的程式碼數量。如需詳細資訊，請參閱 [Java 1.x：DynamoDBMapper](DynamoDBMapper.md)。

本節包含 Java 範例，執行數個 Java 文件 API 項目動作及數個完整的工作範例。

**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 資料表中。若要執行此作業，請使用 `Item` 的 `withJSON` 方法。此方法會剖析 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` 表，放在名為 `VendorInfo` 的 `Map` 屬性中。下列 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) 一節中，您將 JSON 文件存放在名為 `VendorInfo` 的 `Map` 屬性中。您可以使用 `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`。您如此即可指定要從資料表擷取的屬性。

下列程式碼範例會從 `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` 屬性。它也會將價格減一。

`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`，指出只有當書籍不再出版時 (`InPublication` 屬性為 false)，才能刪除 `ProductCatalog` 中的書籍項目。

**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 操作。此範例會建立項目、擷取該項目、執行數次更新，最後刪除該項目。

**注意**  
適用於 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>

本節提供使用 適用於 Java 的 AWS SDK 文件 API 在 Amazon DynamoDB 中批次寫入和批次取得操作的範例。

**注意**  
適用於 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 中建立資料表，以及載入程式碼範例的資料](SampleData.md) 一節的說明將資料載入 DynamoDB 的帳戶。  
如需執行下列範例的逐步說明，請參閱「[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` 類別進行解壓縮。

**注意**  
適用於 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` 用戶端提供 CRUD 操作的 `PutItem`、`GetItem`、`UpdateItem` 和 `DeleteItem` 方法。

**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 軟體開發套件 API 上傳項目的步驟：

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 您可以透過建立 `PutItemRequest` 類別的執行個體來提供必要的參數。

   若要放入項目，您必須提供資料表名稱及項目。

1. 提供您在前一個步驟中建立的 `PutItem` 物件來執行 `PutItemRequest` 方法。

下列 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. 提供您在前一個步驟中建立的 `GetItem` 物件來執行 `GetItemRequest` 方法。

下列 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 軟體開發套件 API 來更新現有項目的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 您可以透過建立 `UpdateItemRequest` 類別的執行個體來提供必要的參數。

   這是您說明所有更新的請求物件，例如新增屬性、更新現有的屬性或刪除屬性。若要刪除現有的屬性，請以 Null 值指定屬性名稱。

1. 提供您在前一個步驟中建立的 `UpdateItem` 物件來執行 `UpdateItemRequest` 方法。

下列 C\$1 程式碼範例示範前述步驟。範例會更新 `ProductCatalog` 表中的項目。它會將新的作者新增到 `Authors` 集合，並刪除現有的 `ISBN` 屬性。它也會將價格減一。



```
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` 來實作原子計數器以增加或減少現有屬性的值，卻不干擾其他寫入請求。若要更新原子計數器，`UpdateExpression` 參數請使用屬性類型為 `Number` 的 `updateItem`，`Action` 使用 `ADD`。

下列範例示範這項作業，將 `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 軟體開發套件 API 來刪除項目的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 您可以透過建立 `DeleteItemRequest` 類別的執行個體來提供必要的參數。

    若要刪除項目，需要有資料表名稱及項目的主索引鍵。

1. 提供您在前一個步驟中建立的 `DeleteItem` 物件來執行 `DeleteItemRequest` 方法。

**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` 指定只有不再發行的書籍項目才可刪除 (InPublication 屬性值為 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 開發套件 API 來擷取多個項目的步驟。

1. 建立 `AmazonDynamoDBClient` 類別的執行個體。

1. 透過建立 `BatchWriteItemRequest` 類別的執行個體來說明所有的放入與刪除操作。

1. 提供您在前一個步驟中建立的 `BatchWriteItem` 物件來執行 `BatchWriteItemRequest` 方法。

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. 提供您在前一個步驟中建立的 `BatchGetItem` 物件來執行 `BatchGetItemRequest` 方法。

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>

下列 C\$1 程式碼範例使用 `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)
+ [在 DynamoDB 中搭配使用 AWS CLI 與全域次要索引](GCICli.md)

## 藍本：使用全域次要索引
<a name="GSI.scenario"></a>

為了示範，假設有一份名為 `GameScores` 的資料表，會追蹤手機遊戲應用程式的使用者與分數。`GameScores` 中的每個項目都是按分割區索引鍵 (`UserId`) 和排序索引鍵 (`GameTitle`) 識別。下圖顯示這些項目在資料表中的組織方式。(並未顯示所有屬性。)

![\[包含使用者 ID、遊戲名稱、分數、日期和勝/敗清單的 GameScores 表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/GSI_01.png)


現在假設您想要撰寫排行榜應用程式來顯示每個遊戲的最高分。已指定索引鍵屬性 (`UserId` 和 `GameTitle`) 的查詢會很有效率。但若應用程式僅能根據 `GameTitle` 從 `GameScores` 擷取資料，就需要使用 `Scan` 操作。當愈來愈多項目新增至資料表時，掃描所有資料會變得很慢且效率不彰。這會讓回答問題變得很困難，例如下列問題：
+ Meteor Blasters 遊戲曾記錄到的最高分為何？
+ 哪位使用者在 Galaxy Invaders 中得到最高分？
+ 最高的勝負比為何？

若要加速非索引鍵屬性的查詢，您可以建立全域次要索引。全域次要索引包含基礎資料表中的一系列屬性，但這些屬性會依與該資料表不同的主索引鍵組織。索引鍵不需要有任何來自資料表的索引鍵屬性。甚至不需要有和資料表相同的索引鍵結構描述。

例如，您可以建立名為 `GameTitleIndex` 的全域次要索引，其中分割區索引鍵為 `GameTitle`，排序索引鍵為 `TopScore`。因為基礎資料表的主索引鍵屬性一律會投影到索引，所以也會顯示 `UserId` 屬性。下圖說明 `GameTitleIndex` 索引可能的樣子。

![\[包含遊戲名稱、分數和使用者 ID 清單的 GameTitleIndex 資料表。\]](http://docs.aws.amazon.com/zh_tw/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`。如果我們接著使用 `GameTitle` Comet Quest 與 `TopScore` 0 來查詢索引，即會傳回下列資料。

![\[包含遊戲名稱、最高分和使用者 ID 清單的資料表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/GSI_05.png)


回應中只會顯示具有指定索引鍵值的項目。在此資料集合中，項目未依特定順序排列。

全域次要索引只會追蹤索引鍵屬性確實存在的資料項目。例如，假設您在 `GameScores` 表中新增另一個新項目，但只提供必要的主索引鍵屬性。


****  

| UserId | GameTitle | 
| --- | --- | 
| 400 | Comet Quest | 

因為您未指定 `TopScore` 屬性，所以 DynamoDB 不會將此項目傳播至 `GameTitleIndex`。因此，如已針對 `GameScores` 查詢所有 Comet Quest 項目，可能會取得下列四個項目。

![\[包含 4 個遊戲名稱、最高分和使用者 ID 清單的資料表。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/GSI_04.png)


針對 `GameTitleIndex` 的類似查詢仍只會傳回三個項目，而不是四個。這是因為具有不存在 `TopScore` 的項目不會傳播至索引。

![\[包含 3 個遊戲名稱、最高分和使用者 ID 清單的資料表。\]](http://docs.aws.amazon.com/zh_tw/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`。因此，雖然應用程式可以在查詢中使用 `GameTitle` 和 `TopScore` 有效率地判斷每個遊戲最高分得主的 `UserId`，卻無法有效率地判斷最高分得主的最高勝負比。若要這樣做，應用程式必須在基礎資料表上執行額外的查詢，以擷取每個最高評分者的優缺點。支援查詢此資料更有效率的方法，便是將這些屬性從基礎資料表投影到全域次要索引，如下圖所示。

![\[描述如何將非索引鍵屬性投射到 GSI 以支援高效查詢。\]](http://docs.aws.amazon.com/zh_tw/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`以建立自然階層。此方法可讓您的資料類型和程式碼保持乾淨，無需處理或剖析字串。

當您使用多屬性索引鍵查詢全域次要索引時，您必須使用等式條件指定所有分割區索引鍵屬性。對於排序索引鍵屬性，您可以依索引鍵結構描述中定義的順序left-to-right查詢它們。這表示您可以單獨查詢第一個排序索引鍵屬性、同時查詢前兩個屬性，或同時查詢所有屬性，但您無法略過中間的屬性。`>`、`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 會存取 *GameTitleIndex*，並使用 *GameTitle* 分割區索引鍵尋找 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。這包含所有傳回項目之所有屬性名稱與值的大小。

例如，假設有一個全域次要索引，其每個項目均包含 2,000 個位元組的資料。現在假設您 `Query` 此索引，而此查詢的 `KeyConditionExpression` 會傳回 8 個項目。相符項目的總大小為 2,000 位元組 × 8 個項目 = 16,000 位元組。然後，此結果四捨五入至最近的 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 新增至現有資料表，則在寫入資料、讀取時剖析和在所有現有項目中回填合成索引鍵時，需要字串串連。這使得程式碼更雜亂且具有挑戰性，以在個別關鍵元件上維持類型安全。

多屬性索引鍵可解決 GSIs此問題。您可以使用多個現有屬性來定義 GSI 分割區索引鍵，例如 tournamentId 和 region。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`) 的全域次要索引
+ **範例查詢：**「NA-EAST 區域中的所有 WINTER2024 相符項目」或「WINTER2024/NA-EAST 上括號中的所有 SEMIFINALS 相符項目」

**存取模式 3：**查詢玩家的配對歷史記錄，選擇性地依日期範圍或競賽四捨五入進行篩選
+ **解決方案：**具有單一分割區索引鍵 (`player1Id`) 和多屬性排序索引鍵 (`matchDate` \$1 `round`) 的全域次要索引
+ **範例查詢：**「玩家 101 的所有配對」或「Player 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 會自動跨多個 GSIs 編製索引，而不需要合成串連金鑰。

**基礎資料表結構描述：**
+ 分割區索引鍵：`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 | region | round | 括號 | player1Id | player2Id | matchDate | 優勝者 | 分數 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| match-001 | WINTER2024 | NA-EAST | 最終 | 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 | 最終 | 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) | 區域 (PK) | round (SK) | 括號 (SK) | matchId (SK) | player1Id | player2Id | matchDate | 優勝者 | 分數 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| WINTER2024 | NA-EAST | 最終 | 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 | 最終 | 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 | region | 括號 | matchId | player2Id | 優勝者 | 分數 | 
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 
| 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 | 最終 | 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 | 最終 | 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：使用多屬性索引鍵建立具有 GSIs資料表
<a name="GSI.DesignPattern.MultiAttributeKeys.CreateTable"></a>

使用簡單的基本金鑰結構和使用多屬性金鑰GSIs 建立資料表。

#### 程式碼範例
<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`分割區索引鍵進行直接比對查詢，讓基礎資料表結構保持直接，同時 GSIs 提供複雜的查詢模式。

**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");
```

**資料結構說明：**

**自然屬性用量：**每個屬性代表一個真正的競賽概念，不需要字串串連或剖析，提供直接映射到網域模型。

**自動全域次要索引索引：**GSIs 會使用現有屬性 (`tournamentId`、`region``round``bracket`、、 `matchId`代表 TournamentRegionIndex，以及 `player1Id`、 `round`代表 PlayerMatchHistoryIndex) 自動索引項目`matchDate`，而不需要合成串連索引鍵。

**不需要回填：**當您將具有多屬性索引鍵的新全域次要索引新增至現有資料表時，DynamoDB 會使用其自然屬性自動為所有現有項目編製索引，而不需要使用合成索引鍵更新項目。

### 步驟 3：查詢具有所有分割區索引鍵屬性的 TournamentRegionIndex 全域次要索引
<a name="GSI.DesignPattern.MultiAttributeKeys.QueryAllPartitionKeys"></a>

此範例會查詢具有多屬性分割區索引鍵 (`tournamentId` \$1 ) 的 TournamentRegionIndex 全域次要索引`region`。所有分割區索引鍵屬性都必須在查詢中指定等式條件 - 您無法`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：從left-to-right查詢全域次要索引排序索引鍵
<a name="GSI.DesignPattern.MultiAttributeKeys.QuerySortKeysLeftToRight"></a>

排序索引鍵屬性必須依全域次要索引中定義的順序left-to-right查詢。此範例示範在不同階層層級查詢 TournamentRegionIndex：僅依 `round`篩選、依 `round` \$1 篩選`bracket`，或依所有三個排序索引鍵屬性篩選。您無法略過中間的屬性，例如，您無法在略過 `matchId`時透過 `round`和 查詢`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
```

**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>

此範例會查詢具有單一分割區索引鍵 (`player1Id`) 和多屬性排序索引鍵 (`matchDate` \$1 ) 的 PlayerMatchHistoryIndex`round`。這可透過查詢特定玩家的所有配對而不了解賽事 IDs，而基礎資料表則需要每個賽事區域組合個別的查詢。

#### 程式碼範例
<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>

銀行系統使用 GSIs追蹤帳戶交易

#### 程式碼範例
<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);
```

**最小程式碼 scaffold**

### 程式碼範例
<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。這將為您的流量分佈提供有價值的洞察。
 在整個過程中觀看 `ThrottledRequests`、 `WriteThrottleEvents`和 `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>

若要將全域次要索引新增至現有資料表，請搭配 `GlobalSecondaryIndexUpdates` 參數使用 `UpdateTable` 操作。您必須提供下列項目：
+ 索引名稱。該名稱在資料表的所有索引中必須是唯一的。
+ 索引的索引鍵結構描述。您必須指定一個屬性做為索引的分割區索引鍵，並可以選擇性地指定另一個屬性做為索引的排序索引鍵。上述任一索引鍵屬性皆不需與資料表的索引鍵屬性相同。每個結構描述屬性的資料類型都必須是純量：`String`、`Number` 或 `Binary`。
+ 要從資料表投影到索引的屬性：
  + `KEYS_ONLY`：索引中的每個項目都只包含資料表分割索引鍵和排序索引鍵值，以及索引鍵值。
  + `INCLUDE`：除了 `KEYS_ONLY` 中描述的屬性外，次要索引會包含您指定的其他非索引鍵屬性。
  + `ALL`：索引包含來源資料表中的所有屬性。
+ 索引的佈建輸送量設定，包含 `ReadCapacityUnits` 和 `WriteCapacityUnits`。這些佈建輸送量設定與資料表的佈建輸送量設定無關。

每個 `UpdateTable` 操作只能建立一個全域次要索引。

### 索引建立階段
<a name="GSI.OnlineOps.Creating.Phases"></a>

當您將新的全域次要索引新增至現有資料表時，資料表在索引建置時仍可繼續使用。但新的索引在其狀態從 `CREATING` 變更為 `ACTIVE` 前，都不可進行 Query 操作。

**注意**  
全域次要索引建立不使用 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 工具需要組態檔案。此檔案中的參數決定 Violation Detector 可以存取哪些 DynamoDB 資源，以及其可以消耗多少佈建的輸送量。下表描述了這些參數。


****  

| 參數名稱 | Description | 是否為必要？ | 
| --- | --- | --- | 
|  `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`) 表示 Violation Detector 將消耗不超過資料表佈建的讀取輸送量的 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" | 

資料表現在共有九個項目。

現在，您將新的全域次要索引新增至資料表 `PriceIndex`。此索引的主索引鍵是分割區索引鍵 `Price`，其類型是 `Number`。建置索引後，將會包含八個項目，但 `ProductCatalog` 資料表含有九個項目。造成此差異的原因是數值 `"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 格式。檔案中的第一行是標頭，後接每個違反索引鍵的項目的一筆紀錄。這些違規情況紀錄的欄位如下所示：
+ **資料表雜湊索引鍵** - 資料表中項目的分割區索引鍵值。
+ **資料表範圍索引鍵** - 資料表中項目的排序索引鍵值。
+ **GSI 雜湊索引鍵值** - 全域次要索引的分割區索引鍵值。
+ **GSI 雜湊索引鍵違規情況類型** - `Type Violation` 或 `Size Violation`。
+ **GSI 雜湊索引鍵違規情況說明** - 違反的原因。
+ **GSI 雜湊索引鍵更新值 (針對使用者)** - 在校正模式下，屬性的新使用者提供的數值。
+ **GSI 範圍索引鍵值** - 全域次要索引的排序索引鍵值。
+ **GSI 範圍索引鍵違規情況類型** - `Type Violation` 或 `Size Violation`。
+ **GSI 範圍索引鍵違規情況說明** - 違反的原因。
+ **GSI 範圍索引鍵更新值 (針對使用者)** - 在校正模式下，屬性的新使用者提供的數值。
+ **更新時刪除空白屬性 (是/否)** - 在校正模式下，決定要刪除 (是) 或保留 (否) 資料表中的違規情況，但只有在下列其中一個欄位為空白時：
  + `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 Document API 建立具有全域次要索引的資料表的步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 建立 `CreateTableRequest` 類別的執行個體，以提供請求資訊。

   您必須提供資料表名稱、其主索引鍵，以及佈建的輸送量數值。對於全域次要索引，您必須提供索引名稱、其佈建的輸送量設定值、索引排序索引鍵的屬性定義、索引的索引鍵結構描述以及屬性投影。

1. 以參數形式提供請求物件，以便呼叫 `createTable` 方法。

下列 Java 程式碼範例示範上述步驟。程式碼會建立具有全域次要索引 (`PrecipIndex`) 的資料表 (`WeatherData`)。索引分割區索引鍵是 `Date`，而其排序索引鍵是 `Precipitation`。所有的資料表屬性都會投影到索引。使用者可以查詢此索引以取得特定日期的天氣資料，可選擇依降水量排序資料。

因為 `Precipitation` 不是資料表的索引鍵屬性，所以其並非必要項目。不過，沒有 `Precipitation` 的 `WeatherData` 項目不會在 `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. 在 `Table` 物件上呼叫 `describe` 方法。

下列 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. 在 `Index` 物件上呼叫 `query` 方法。

屬性名稱 `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`。此資料表上有三個全域次要索引：
+ `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` 不是資料表的索引鍵屬性，所以其並非必要項目。不過，沒有 `Precipitation` 的 `WeatherData` 項目不會在 `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`。此資料表上有三個全域次要索引：
+ `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;
                }
            }
        }
    }
}
```

# 在 DynamoDB 中搭配使用 AWS CLI 與全域次要索引
<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/)。下圖顯示這些項目在資料表中的組織方式。(並未顯示所有屬性。)

![\[主題資料表包含論壇名稱、話題、上次貼文時間和回覆次數的清單。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/LSI_01.png)


DynamoDB 會連續儲存具有相同分割區索引鍵值的所有項目。在本例中，給定一個特定的 `ForumName`，`Query` 操作就可以立即找到該論壇的所有主題。在具有相同分割區索引鍵值的項目群組中，項目會依排序索引鍵值排序。如果查詢中還提供了排序索引鍵 (`Subject`)，DynamoDB 可以縮小傳回的結果範圍，例如傳回「S3」論壇中以字母「a」開頭的 `Subject` 的所有主題。

某些請求可能需要更複雜的資料存取模式。例如：
+ 哪些論壇主題獲得最多的觀看次數和回覆？
+ 特定論壇中哪個主題的訊息數量最多？
+ 在特定時段內，有多少個主題發布在特定論壇？

若要回答這些問題，`Query` 動作並不足夠。您還必須 `Scan` 整個資料表。對於包含數百萬個項目的資料表，這會消耗大量佈建的讀取輸送量，而且需要很長的時間才能完成。

不過，您可以在非索引鍵屬性上指定一或多個本機次要索引，例如 `Replies` 或 `LastPostDateTime`。

*本機次要索引*會針對指定分割區索引鍵值維護替代排序索引鍵。本機次要索引也包含基礎資料表中部分或全部屬性的複本。您可以指定在建立資料表時要投影到本機次要索引中的屬性。本機次要索引中的資料由與基礎資料表相同的分割區索引鍵組織，但具有不同的排序索引鍵。這可讓您在此不同維度之間有效地存取資料項目。為獲得更大的查詢或掃描靈活性，您最多可以為每個資料表建立五個本機次要索引。

假設應用程式需要尋找在最近三個月內張貼於特定論壇中的所有主題。如果沒有本機次要索引，應用程式必須 `Scan` 整個 `Thread` 資料表，並捨棄任何不在指定時間範圍內的貼文。使用本機次要索引，`Query` 操作可以使用 `LastPostDateTime` 作為排序索引鍵，並快速找到資料。

下圖顯示名為 `LastPostIndex` 的本機次要索引。請注意，分割區索引鍵與 `Thread` 資料表的分割區索引鍵相同，但排序索引鍵是 `LastPostDateTime`。

![\[包含論壇名稱、主題和上次貼文時間的 LastPostIndex 資料表。\]](http://docs.aws.amazon.com/zh_tw/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_tw/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_tw/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` 時將 `ReturnItemCollectionMetrics` 參數設定為 `SIZE`。您的應用程式應該檢查輸出中的 `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 Document API 的本機次要索引](LSIJavaDocumentAPI.Example.md)

## 使用本機次要索引建立資料表
<a name="LSIJavaDocumentAPI.CreateTableWithIndex"></a>

您必須同時建立資料表和本機次要索引。若要執行這項操作，請使用 `createTable` 方法，並提供一或多個本機次要索引的規格。以下 Java 程式碼範例會建立資料表來保存音樂收藏中歌曲的相關資訊。分割區索引鍵為 `Artist`，而排序索引鍵為 `SongTitle`。次要索引 `AlbumTitleIndex` 有助於依照專輯標題查詢。

以下是使用 DynamoDB Document 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 Document API 存取資料表的本機次要索引資訊的步驟。

1. 建立 `DynamoDB` 類別的執行個體。

1. 建立 `Table` 類別的執行個體。您必須提供資料表名稱。

1. 在 `Table` 物件上呼叫 `describeTable` 方法。

下列 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. 呼叫 `Index` 類別的 `query` 方法。

下列 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`會使用最終一致讀取。若要請求強式一致讀取，請在 `true`中將 `ConsistentRead`設定為 `QuerySpec`。下列範例查詢`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 Document 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 中使用這些 API。

**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` 操作提交。下列各節說明 API 操作、容量管理、最佳實務，以及關於在 DynamoDB 中使用交易操作的其他詳細資訊。

**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` 請求與 `TransactGetItems` 請求中一或多個項目持續執行的 `TransactWriteItems` 操作相互衝突時。在這種情況中，請求會失敗，並且丟出 `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` 操作之間，不具有可序列化隔離層級。

同樣地，交易操作與 `BatchGetItem` 操作中個別 `GetItems` 之間的隔離層是可以序列化。但是交易與當作一個單位的 `BatchGetItem` 操作之間的隔離層是*專供讀取*。

單一 `GetItem` 請求是兩種可序列化的 `TransactWriteItems` 請求方式之一，可以發生在 `TransactWriteItems` 請求之前或之後。與 `TransactWriteItems` 請求同時發出的多個 `GetItem` 請求，能夠以任何順序執行，因此結果會是*專供讀取*。

例如，如果項目 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 SDKs不會重試請求。  
如果您使用的是 適用於 Java 的 AWS SDK，則例外狀況會包含 [CancellationReasons](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_CancellationReason.html) 的清單，並根據`TransactItems`請求參數中的項目清單排序。對於其他語言，清單的字串表示法會併入異常的錯誤訊息中。
不過，如果有持續執行的 `TransactWriteItems` 和 `TransactGetItems` 操作，與同時並行的 `GetItem` 請求相互衝突時，這兩項操作都可以順利完成。

對於每個失敗的項目層級請求，[TransactionConflict CloudWatch 指標](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/metrics-dimensions.html)會遞增。

## 使用 DynamoDB Accelerator (DAX) 中的交易 API
<a name="transaction-apis-dax"></a>

DynamoDB Accelerator (DAX) 中支援 `TransactWriteItems` 和 `TransactGetItems`，且隔離層級與在 DynamoDB 中相同。

`TransactWriteItems` 會透過 DAX 寫入。DAX 會將 `TransactWriteItems` 呼叫傳送給 DynamoDB，然後傳回回應。為在寫入後填入快取，對於 `TransactWriteItems` 操作中的每個項目，DAX 會在背景呼叫 `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：一個用來準備交易，另一個用來遞交交易。

此外，在出現 `TransactionInProgressException` 例外時，預設的 SDK 動作是重試交易。請規劃這些重試動作會使用的額外讀取容量單位 (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` 許可。

您可以使用下列的 IAM 政策範例，來設定 DynamoDB 交易。

## 範例 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` 的動作。如果項目的產品狀態屬性不等於 `IN_STOCK`，則設定 `ReturnValuesOnConditionCheckFailure` 參數會傳回項目。

```
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 與透過 DynamoDB Streams 使用[屬性型存取控制 (ABAC)](access-control-resource-based.md)，目前未提供支援。

以下影片將為您介紹變更資料擷取概念。

[![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 提供兩種用於變更資料擷取的串流模型：DynamoDB 專用 Kinesis Data Streams 和 DynamoDB Streams。

為了協助您為應用程式選擇合適的解決方案，下表摘要說明每種串流模型的特色。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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 資料串流](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) 和 [Amazon Managed Service for 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 Client Library、使用 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 資料串流讀取這些記錄時，為了擷取原始二進位值，應用程式必須將這些值解碼兩次。

使用 Kinesis Data Streams 時，DynamoDB 會根據變更資料擷取單位收取費用。每個單一項目的 1 KB 變更會計為一個變更資料擷取單位。使用與[寫入作業的容量單位耗用量](read-write-operations.md#write-operation-consumption)相同的邏輯，以寫入串流之項目的「之前」和「之後」映像中較大者，計算每個項目的 KB 變化量。運作方式與 DynamoDB [隨需](capacity-mode.md#capacity-mode-on-demand)模式類似，您不需要為變更資料擷取單位佈建容量輸送量。

### 為 DynamoDB 資料表開啟 Kinesis 資料串流
<a name="kds_howitworks.enabling"></a>

您可以使用 、 AWS 管理主控台 AWS SDK 或 AWS Command Line Interface ()，從現有的 DynamoDB 資料表啟用或停用串流至 Kinesis AWS CLI。
+ 您只能在與資料表相同的 AWS 帳戶和 AWS 區域中，將資料從 DynamoDB 串流到 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 使用 Amazon DynamoDB 資料表的 Kinesis Data Streams。

## 建立作用中的 Amazon Kinesis 資料串流
<a name="kds_gettingstarted.making-changes"></a>

所有這些範例都使用 `Music` DynamoDB 資料表，該資料表是在 [DynamoDB 入門](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStartedDynamoDB.html)教學課程中建立的。

若要進一步了解如何建置取用者並將 Kinesis 資料串流連線至其他 AWS 服務，請參閱《Amazon Kinesis Data Streams 開發人員指南》**中的[從 Kinesis Data Streams 讀取資料](https://docs.aws.amazon.com/streams/latest/dev/building-consumers.html)。

**注意**  
 第一次使用 KDS 碎片時，建議您將碎片設定為隨著使用模式向上擴展和縮減。累積更多使用模式的相關資料後，您可以調整串流中的碎片以進行配對。

------
#### [ Console ]

1. 登入 AWS 管理主控台 並開啟位於 https：//[https://console.aws.amazon.com/kinesis/](https://console.aws.amazon.com/kinesis/) 的 Kinesis 主控台。

1. 選擇 **Create data stream** (建立資料串流)，並依照說明來建立名為 `samplestream` 的串流。

1. 請在 [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在主控台左側的導覽窗格中，選擇 **Tables** (資料表)。

1. 選擇 **Music** (音樂) 資料表。

1. 選擇 **Exports and streams** (匯出與串流) 索引標籤。

1. (選用) 您可以在 **Amazon Kinesis 資料串流詳細資訊**下，將記錄時間戳記精確度從微秒 (預設) 變更為毫秒。

1. 從下拉式清單選擇 **samplestream** (範例串流)。

1. 選擇 **Turn On** (開啟) 按鈕。

------
#### [ 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. 使用 `put-item` 命令將資料寫入 DynamoDB 資料表，如《[DynamoDB 開發人員指南](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/getting-started-step-2.html)》中所述。

   ```
   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 [建立](https://docs.aws.amazon.com/streams/latest/dev/kinesis-using-sdk-java-create-stream.html)名為 `samplestream` 的 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 資料串流
<a name="kds_gettingstarted.making-changes"></a>

本節說明如何使用 主控台 AWS CLI 和 API 來變更作用中的 Kinesis Data Streams for DynamoDB 設定。

**AWS 管理主控台**

1. 請在 [https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 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 Stream 使用佈建模式。採用佈建模式，必須指定資料串流的碎片數目，以容納來自 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 Data Stream 之前，所產生的額外膨脹。

如需進一步了解 Kinesis Data Stream 上的容量模式，請參閱[選擇資料串流容量模式](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 資料串流容量不足而受到 Kinesis Data Streams 限流的記錄數量。儘管在特殊使用峰值期間，您可能會遇到限流的情況，仍應盡可能降低 `ThrottledPutRecordCount`。DynamoDB 會重試將限流記錄傳送到 Kinesis 資料串流，但這可能會導致較高的複寫延遲。

  如果遇到過多且規律的限流，您可能需要按照觀察到的資料表寫入輸送量按比例增加 Kinesis 串流碎片的數量。若要進一步了解如何判斷 Kinesis 資料串流的大小，請參閱[判斷 Kinesis Data Stream 的初始大小](https://docs.aws.amazon.com/streams/latest/dev/amazon-kinesis-streams.html#how-do-i-size-a-stream)。
+ `AgeOfOldestUnreplicatedRecord`：自 DynamoDB 資料表中出現尚未複寫到 Kinesis 資料串流的最舊項目層級變更以來經過的時間。在正常的操作下，`AgeOfOldestUnreplicatedRecord` 應該以毫秒為單位。當不成功的複寫嘗試是因客戶控制的組態選擇所引起時，此數字會隨著不成功複寫嘗試的增加而增加。

   如果 `AgeOfOldestUnreplicatedRecord` 指標超過 168 小時，從 DynamoDB 資料表到 Kinesis 資料串流的項目層級變更複寫將自動停用。

  可能導致複寫嘗試失敗的客戶控制組態示例包括，佈建的 Kinesis 資料串流容量不足導致過度限流，或是手動更新 Kinesis 資料串流的存取政策因而拒絕 DynamoDB 新增資料至您的資料串流。為盡可能降低此指標，您可能需要確保妥善佈建您的 Kinesis 資料串流容量，並確保 DynamoDB 的許可保持不變。
+ `FailedToReplicateRecordCount`：DynamoDB 無法複寫到您的 Kinesis 資料串流的記錄數目。大於 34KB 的某些項目可能會擴充大小，以變更大於 Kinesis Data Streams 1MB 項目大小限制的資料記錄。當這些大於 34KB 的項目包含大量的布林值或空白屬性值時，就會發生此大小擴充。布林值和空白屬性值會以 1 位元組形式儲存在 DynamoDB 中，但是在使用用於 Kinesis Data Streams 複寫的標準 JSON 將其序列化時，最多可擴充至 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 DynamoDB 啟用 Amazon Kinesis Data Streams 時，DynamoDB 會自動為您建立 AWS Identity and Access Management (IAM) 服務連結角色。 DynamoDB 這個角色 (`AWSServiceRoleForDynamoDBKinesisDataStreamsReplication`) 可讓 DynamoDB 代表您管理對 Kinesis Data Streams 的項目層級變更的複寫。請勿刪除此服務連結角色。

如需服務連結角色的詳細資訊，請參閱《*IAM 使用者指南*》中的[使用服務連結角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/using-service-linked-roles.html)。

**注意**  
DynamoDB 不支援 IAM 政策的標籤型條件。

若要啟用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams，您必須擁有下列資料表許可：
+ `dynamodb:EnableKinesisStreamingDestination`
+ `kinesis:ListStreams`
+ `kinesis:PutRecords`
+ `kinesis:DescribeStream`

若要為指定的 DynamoDB 資料表描述 Amazon DynamoDB 專用 Amazon Kinesis Data Streams，您必須擁有下列資料表許可。
+ `dynamodb:DescribeKinesisStreamingDestination`
+ `kinesis:DescribeStreamSummary`
+ `kinesis:DescribeStream`

若要停用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams，您必須擁有下列資料表許可。
+ `dynamodb:DisableKinesisStreamingDestination`

若要更新 Amazon DynamoDB 專用 Amazon Kinesis Data Streams，您必須擁有下列資料表許可。
+ `dynamodb:UpdateKinesisStreamingDestination`

下列範例顯示如何使用 IAM 政策授予 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。

## 範例：啟用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams
<a name="access-policy-kds-example1"></a>

以下 IAM 政策會授予 `Music` 資料表啟用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。其不會授予 `Music` 資料表停用、更新或描述 DynamoDB 專用 Kinesis Data Streams 的許可。

------
#### [ 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 DynamoDB 專用 Amazon Kinesis Data Streams
<a name="access-policy-kds-example2"></a>

以下 IAM 政策會授予 `Music` 資料表更新 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。其不會授予 `Music` 資料表啟用、停用或描述 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:UpdateKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        }
    ]
}
```

------

## 範例：停用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams
<a name="access-policy-kds-example2"></a>

以下 IAM 政策會授予 `Music` 資料表停用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。其不會授予 `Music` 資料表啟用、更新或描述 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。

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

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:DisableKinesisStreamingDestination"
            ],
            "Resource": "arn:aws:dynamodb:us-west-2:111122223333:table/Music"
        }
    ]
}
```

------

## 範例：根據資源選擇性地為 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 套用許可
<a name="access-policy-kds-example3"></a>

下列 IAM 政策會授予 `Music` 資料表啟用及描述 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可，但不會授予 `Orders` 資料表停用 Amazon DynamoDB 專用 Amazon Kinesis Data Streams 的許可。

------
#### [ 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"
        }
    ]
}
```

------

## 為 DynamoDB 專用 Kinesis Data Streams 使用服務連結角色
<a name="kds-service-linked-roles"></a>

Amazon DynamoDB 的 Amazon Kinesis Data Streams 使用 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)。 DynamoDB 服務連結角色是特殊類型的 IAM 角色，此角色可直接連結到 DynamoDB 專用 Kinesis Data Streams。服務連結角色由適用於 DynamoDB 的 Kinesis Data Streams 預先定義，並包含該服務代表您呼叫其他 AWS 服務所需的所有許可。

服務連結角色可讓 DynamoDB 專用 Kinesis Data Streams 的設定更為簡單，因為您不必手動新增必要的許可。DynamoDB 專用 Kinesis Data Streams 會定義其服務連結角色的許可，除非另有定義，否則僅有 DynamoDB 專用 Kinesis Data Streams 可以擔任其角色。定義的許可包括信任政策和許可政策，並且該許可政策不能連接到任何其他 IAM 實體。

如需關於支援服務連結角色的其他服務的資訊，請參閱[可搭配 IAM 運作的AWS 服務](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)，並尋找 **Service-Linked Role** (服務連結角色) 欄顯示為 **Yes** (是) 的服務。選擇具有連結的 **Yes** (是)，以檢視該服務的服務連結角色文件。

### DynamoDB 專用 Kinesis Data Streams 的服務連結角色許可
<a name="slr-permissions"></a>

DynamoDB 專用 Kinesis Data Streams 會使用名為 **AWSServiceRoleForDynamoDBKinesisDataStreamsReplication** 的服務連結角色。服務連結角色的目的是讓 Amazon DynamoDB 代表您管理對 Kinesis Data Streams 項目層級變更的複寫。

`AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 服務連結角色信任下列服務以擔任角色：
+ `kinesisreplication.dynamodb.amazonaws.com`

此角色許可政策允許 DynamoDB 專用 Kinesis Data Streams 對指定資源完成下列動作：
+ 動作：`Kinesis stream` 上的 `Put records and describe`
+ 動作：`Generate data keys`開啟 `AWS KMS` 以將資料放置在使用使用者產生 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)。

### 為 DynamoDB 專用 Kinesis Data Streams 建立服務連結角色
<a name="create-slr"></a>

您不需要手動建立服務連結角色，當您在 AWS 管理主控台、 AWS CLI或 AWS API 中啟用 DynamoDB 的 Kinesis Data Streams 時，DynamoDB 的 Kinesis Data Streams 會為您建立服務連結角色。

若您刪除此服務連結角色，之後需要再次建立，您可以在帳戶中使用相同程序重新建立角色。在啟用 DynamoDB 專用 Kinesis Data Streams 時，DynamoDB 專用 Kinesis Data Streams 會再次為您建立服務連結角色。

### 為 DynamoDB 專用 Kinesis Data Streams 編輯服務連結角色
<a name="edit-slr"></a>

DynamoDB 專用 Kinesis Data Streams 不允許您編輯 `AWSServiceRoleForDynamoDBKinesisDataStreamsReplication` 服務連結角色。因為有各種實體可能會參考服務連結角色，所以您無法在建立角色之後變更角色名稱。然而，您可使用 IAM 來編輯角色描述。如需詳細資訊，請參閱《*IAM 使用者指南*》中的[編輯服務連結角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/contributorinsights-service-linked-roles.html#edit-service-linked-role)。

### 為 DynamoDB 專用 Kinesis Data Streams 刪除服務連結角色
<a name="delete-slr"></a>

您也可以使用 IAM 主控台、 AWS CLI 或 AWS API 手動刪除服務連結角色。若要執行此操作，您必須先手動清除服務連結角色的資源，然後才能手動刪除它。

**注意**  
若 DynamoDB 專用 Kinesis Data Streams 服務在您試圖刪除資源時正在使用該角色，刪除可能會失敗。若此情況發生，請等待數分鐘後並再次嘗試操作。

**使用 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 Streams 中的資料。如需詳細資訊，請參閱 [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 轉接器處理串流記錄](Streams.KCLAdapter.md)
+ [DynamoDB Streams 低階 API：Java 範例](Streams.LowLevel.Walkthrough.md)
+ [DynamoDB 串流和 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-only 端點**：具有命名慣例的端點。 `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 SDKs 為 DynamoDB 和 DynamoDB Streams 提供單獨的用戶端。視您需求的不同，應用程式可以存取 DynamoDB 端點、DynamoDB Streams 端點，或同時存取這兩種端點。若要連線到這兩種端點，您的應用程式必須具體化兩個用戶端：一個用於 DynamoDB，另一個用於 DynamoDB Streams。

## 啟用串流
<a name="Streams.Enabling"></a>

您可以使用 AWS CLI 或其中一個 AWS SDKs 在新資料表上啟用串流。您也可以在現有的資料表上啟用或停用串流，或變更串流的設定。DynamoDB Streams 會以非同步方式運作，因此若您啟用串流，也不會影響資料表的效能。

管理 DynamoDB Streams 最簡單的方式就是使用 AWS 管理主控台。

1. 登入 AWS 管理主控台 ，並在 https：//[https://console.aws.amazon.com/dynamodb/](https://console.aws.amazon.com/dynamodb/) 開啟 DynamoDB 主控台。

1. 在 DynamoDB 主控台儀表板上，選擇 **Tables** (資料表)，然後選取現有的資料表。

1. 選擇 **Exports and streams** (匯出與串流) 索引標籤。

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 Resource Name (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 轉接器，則系統會為您處理這個問題。應用程式會依正確順序處理碎片和串流紀錄。除了當應用程式正在執行時分割的碎片，其還會自動處理新的或過期碎片。如需詳細資訊，請參閱 [使用 DynamoDB Streams Kinesis 轉接器處理串流記錄](Streams.KCLAdapter.md)。)

下圖說明串流、串流中的碎片及碎片中的串流紀錄之間的關係。

![\[DynamoDB Streams 結構。代表資料修改的串流記錄，會以碎片的形式組織。\]](http://docs.aws.amazon.com/zh_tw/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 Resource Name (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>

您可以備份或處理[存留時間](TTL.md) (TTL) 刪除的項目，方法是在資料表上啟用 Amazon DynamoDB Streams 並處理過期項目的串流紀錄。如需詳細資訊，請參閱[讀取及處理串流](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_tw/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 轉接器處理串流記錄
<a name="Streams.KCLAdapter"></a>

建議透過 Amazon Kinesis 轉接器耗用來自 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 轉接器。Kinesis 轉接器會實作 Kinesis Data Streams 介面，以便您將 KCL 用於耗用和處理來自 DynamoDB Streams 的紀錄。如需如何設定和安裝 DynamoDB Streams Kinesis Adapter 的指示，請參閱 [GitHub 儲存庫](https://github.com/awslabs/dynamodb-streams-kinesis-adapter)。

您可以使用 Kinesis Client Library (KCL) 為 Kinesis Data Streams 撰寫應用程式。KCL 會在低階 Kinesis Data Streams API 上提供有用的抽象，可以簡化程式碼。如需 KCL 的詳細資訊，請參閱《Amazon Kinesis Data Streams 開發人員指南》中的[使用 Kinesis Client Library 開發取用者](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 版搭配 適用於 Java 的 AWS SDK 適用於 v1.x 的 AWS SDK，在過渡期間會繼續如預期完全支援整個生命週期，以符合 [AWS SDKs和工具維護政策](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) 頁面。如需最新 Kinesis Client Library 版本的相關詳細資訊，請參閱[使用 Kinesis Client Library](https://docs.aws.amazon.com/streams/latest/dev/kcl.html)。如需有關從 KCL 1.x 移轉至 3.x 的詳細資訊，請參閱《從 KCL 1.x 移轉到 KCL 3.x》。

下圖顯示這些程式庫彼此如何互動。

![\[DynamoDB Streams、Kinesis Data Streams 和 KCL 之間的互動，用於處理 DynamoDB Streams 記錄。\]](http://docs.aws.amazon.com/zh_tw/amazondynamodb/latest/developerguide/images/streams-kinesis-adapter.png)


有了 DynamoDB Streams Kinesis 轉接器，您就可以開始針對 KCL 介面進行開發，並將 API 呼叫順暢地導向 DynamoDB Streams 端點。

當應用程式啟動後，其會呼叫 KCL 以將工作者執行個體化。您必須向工作者提供應用程式的組態資訊，例如串流描述項和 AWS 登入資料，以及您提供的記錄處理器類別名稱。當其在紀錄處理器中執行程式碼時，工作者會執行下列任務：
+ 連線到串流
+ 列舉串流內的碎片
+ 檢查並列舉串流中已關閉父碎片的子碎片
+ 與其他工作者 (若有) 協調碎片關聯性
+ 為其所管理的每個碎片執行個體化記錄處理器
+ 從串流提取紀錄
+ 在高輸送量期間擴展 GetRecords API 呼叫速率 （如果已設定追趕模式）
+ 將記錄推送至對應的記錄處理器
+ 對已處理的記錄執行檢查點作業
+ 當工作者執行個體數目變更時，平衡碎片與工作者的關聯
+ 當碎片進行分割時，平衡碎片與工作者的關聯

KCL 轉接器支援追趕模式，這是一種自動呼叫速率調整功能，用於處理暫時輸送量增加。當串流處理延遲超過可設定的閾值 （預設一分鐘） 時，追趕模式會將 GetRecords API 呼叫頻率擴展為可設定的 值 （預設 3 倍），以更快地擷取記錄，然後在延遲下降時恢復正常。在高傳輸量期間，DynamoDB 寫入活動可能會使用預設輪詢率讓消費者負擔過重，這非常寶貴。可以透過`catchupEnabled`組態參數啟用追趕模式 （預設 false)。

**注意**  
如需此處所列 KCL 概念的描述，請參閱 *Amazon Kinesis Data Streams 開發人員指南*中的[使用 Kinesis Client Library 開發取用者](https://docs.aws.amazon.com/kinesis/latest/dev/developing-consumers-with-kcl.html)。  
如需搭配 使用串流的詳細資訊， AWS Lambda 請參閱 [DynamoDB 串流和 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 1.x 使用的類別和介面與 KCL 3.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 轉接器所實作的記錄處理器：

```
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 轉接器現在使用 SDKv2 記錄模型。在 SDKv2 中，複雜 `AttributeValue` 物件 (`BS`、`NS`、`M`、`L`、`SS`) 永遠不會傳回空值。使用 `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. 將 `Worker` 類別的 `import` 陳述式變更為 `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 3.x 中，在 KCL 1.x 之後推出的相關組態，請參閱 [KCL 組態](https://docs.aws.amazon.com//streams/latest/dev/kcl-configuration.html)和 [KCL 移轉用戶端組態](https://docs.aws.amazon.com//streams/latest/dev/kcl-migration.html#client-configuration)。

**重要**  
我們建議使用 `ConfigsBuilder` 在 KCL 3.x 及更新版本中設定組態，而非直接建立 `checkpointConfig`、`coordinatorConfig`、`leaseManagementConfig`、`metricsConfig`、`processorConfig` 及 `retrievalConfig` 的物件，以避免排程器初始化問題。`ConfigsBuilder` 則提供更靈活且跟更容易維護的 KCL 應用程式設定方式。

#### KCL 3.x 的更新預設值組態
<a name="kcl3-configuration-overview"></a>

`billingMode`  
KCL 1.x 版中，`billingMode` 的預設值設定為 `PROVISIONED`。不過，KCL 3.x 版 `billingMode` 的預設值為 `PAY_PER_REQUEST` (隨需模式)。我們建議租用資料表使用隨需容量模式，根據用量自動調整容量。如需租用資料表使用佈建容量的指南，請參閱[具有佈建容量模式的租用資料表最佳實務](https://docs.aws.amazon.com//streams/latest/dev/kcl-migration-lease-table.html)。

`idleTimeBetweenReadsInMillis`  
在 KCL 1.x 版中，`idleTimeBetweenReadsInMillis` 的預設值設定為 1,000 (或 1 秒)。KCL 3.x 版會將 i`dleTimeBetweenReadsInMillis` 的預設值設定為 1,500 (或 1.5 秒)，但 Amazon DynamoDB Streams Kinesis 轉接器會將預設值覆寫為 1,000 (或 1 秒)。

#### KCL 3.x 中的新組態
<a name="kcl3-new-configs"></a>

`leaseAssignmentIntervalMillis`  
此組態會定義新發現碎片開始處理之前的時間間隔，計算方式為 1.5 x `leaseAssignmentIntervalMillis`。如果未明確配置此設定，則時間間隔預設為 1.5 x `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) 版本及其相容性，請遵循移轉指南[從 KCL 2.x 升級到 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 疑難排解問題，請參閱 [KCL 取用者應用程式疑難排解](https://docs.aws.amazon.com//streams/latest/dev/troubleshooting-consumers.html)。

# 轉返為先前的 KCL 版本
<a name="kcl-migration-rollback"></a>

本主題說明如何將取用者應用程式轉返為先前的 KCL 版本。轉返程序包含兩個步驟：

1. 執行 [KCL 移轉工具](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)的指令碼目錄中，下載 [KCL 移轉工具](https://github.com/awslabs/amazon-kinesis-client/blob/master/amazon-kinesis-client/scripts/KclMigrationTool.py)。從具備適當許可的工作者或主機執行指令碼，以寫入協調器狀態資料表、工作者指標資料表和租用資料表。確保已針對 KCL 取用者應用程式設定適當的 [IAM 許可](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]
```

### Parameters
<a name="kcl-migration-rollback-parameters"></a>

`--region`  
將*區域*取代為 AWS 區域。

`--application_name`  
如果您使用 DynamoDB 中繼資料資料表 (租用資料表、協調器狀態資料表和工作者指標資料表) 的預設名稱，則需要此參數。如果您已為這些資料表指定自訂名稱，可以省略此參數。使用您現有應用程式的名稱取代 *applicationName*。如果未提供自訂名稱，工具會使用此名稱衍生預設資料表名稱。

`--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 移轉工具](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]
```

### Parameters
<a name="kcl-migration-rollforward-parameters"></a>

`--region`  
將*區域*取代為您的 AWS 區域。

`--application_name`  
如果您使用協調器狀態資料表的預設名稱，則需要此參數。如果您已指定協調器狀態資料表的自訂名稱，可以省略此參數。使用您現有應用程式的名稱取代 *applicationName*。如果未提供自訂名稱，工具會使用此名稱衍生預設資料表名稱。

`--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 將程式碼部署至工作者。若要完成移轉，請參閱[步驟 8：完成移轉](https://docs.aws.amazon.com/streams/latest/dev/kcl-migration-from-2-3.html#kcl-migration-from-2-3-finish)。

# 逐步解說：DynamoDB Streams Kinesis 轉接器
<a name="Streams.KCLAdapter.Walkthrough"></a>

本節會逐步解說使用 Amazon Kinesis Client Library 與 Amazon DynamoDB Streams Kinesis 轉接器的 Java 應用程式。此應用程式示範資料複寫的範例，將一份資料表的寫入活動套用至第二份資料表，讓兩份資料表的內容保持同步。如需來源碼，請參閱「[完整程式：DynamoDB Streams Kinesis 轉接器](Streams.KCLAdapter.Walkthrough.CompleteProgram.md)」。

此程式執行下列操作：

1. 建立兩個 DynamoDB 資料表，並命名為 `KCL-Demo-src` 和 `KCL-Demo-dst`。這些資料表各會啟用串流。

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 轉接器](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 轉接器會作為 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 Client Library 開發取用者](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` 類別包含呼叫低階 `Scan` API 的 `ScanTable` 方法。下列範例示範其使用方法。

```
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 轉接器
<a name="Streams.KCLAdapter.Walkthrough.CompleteProgram"></a>

以下是執行此[逐步解說：DynamoDB Streams Kinesis 轉接器](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 v2 的 AWS SDK 做為暫時性相依性：

------
#### [ 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 的所有案例。從 DynamoDB 使用串流紀錄的建議方式，是透過使用 Kinesis Client Library (KCL) 的 Amazon Kinesis 轉接器，如 [使用 DynamoDB Streams Kinesis 轉接器處理串流記錄](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 串流和 AWS Lambda 觸發
<a name="Streams.Lambda"></a>

Amazon DynamoDB 與 整合， AWS Lambda 因此您可以建立*觸發，*也就是自動回應 DynamoDB Streams 中事件的程式碼片段。您可以利用觸發條件建立對 DynamoDB 資料表資料修改做出反應的應用程式。

**Topics**
+ [教學課程 \$11：使用篩選條件來處理 Amazon DynamoDB 的所有事件， AWS Lambda 並使用 AWS CLI](Streams.Lambda.Tutorial.md)
+ [教學課程 \$12：使用篩選條件來處理 DynamoDB 和 Lambda 的所有事件](Streams.Lambda.Tutorial2.md)
+ [搭配 Lambda 使用 DynamoDB Streams 的最佳實務](Streams.Lambda.BestPracticesWithDynamoDB.md)

如果您在資料表上啟用 DynamoDB Streams，您可以將串流 Amazon Resource Name (ARN) 與您寫入的 AWS Lambda 函數建立關聯。然後，即可將該 DynamoDB 資料表的所有變動動作擷取為串流上的項目。例如，您可以設定觸發條件，以便在修改資料表中的項目時，新記錄會立即顯示在該資料表的串流中。

**注意**  
如果您為單一 DynamoDB 串流訂閲兩個以上的 Lambda 函式，可能會發生讀取限流。

此 [AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html) 服務會以每秒四次的速度輪詢串流以偵測新記錄。當有新的串流記錄可用時，系統會同步調用 Lambda 函式。對於相同的 DynamoDB 串流，最多可以訂閱兩個 Lambda 函式。如果您為相同 DynamoDB 串流訂閲兩個以上的 Lambda 函式，可能會發生讀取限流。

Lambda 函式可以傳送通知、啟動工作流程，或執行您指定的其他許多動作。您可以撰寫 Lambda 函式僅將每筆串流紀錄複製到耐久性儲存，例如 Amazon S3 File Gateway (Amazon S3)，並在您的資料表中建立永久的寫入活動稽核軌跡。或者，假設您有一個會寫入 `GameScores` 表的手機遊戲應用程式。每當 `TopScore` 資料表的 `GameScores` 屬性更新時，對應的串流紀錄就會寫入資料表串流。然後，這個事件就會觸發在社交媒體網路張貼賀電的 Lambda 函式。(此函數也可以編寫為忽略所有非更新至 `GameScores` 或不修改 `TopScore` 屬性的串流紀錄)。

如果函數傳回錯誤，Lambda 會不斷重試批次直到處理成功或資料過期。您還可以設定 Lambda，使用較小批次重試、限制重試次數、在記錄太舊時捨棄，以及其他選項。

作為效能最佳實務，Lambda 函式必須為短期函數。為了避免產生不必要的處理延遲，此函數也不應執行複雜的邏輯。尤其對於高速串流而言，相較於同步長時間執行的 Lambda，觸發非同步後續處理 Step Function 工作流程是較佳的做法。

 您可以在 DynamoDB 串流上設定資源型政策，以授予 Lambda 函數的跨帳戶讀取存取權， AWS 藉此跨帳戶使用 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/)。

# 教學課程 \$11：使用篩選條件來處理 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 使用者使用互相傳送的 *barks (簡短的文字訊息)* 進行通訊。下圖顯示此應用程式的元件和工作流程。

![\[DynamoDB 資料表的 Woofer 應用程式工作流程、串流記錄、Lambda 函式和 Amazon SNS 主題。\]](http://docs.aws.amazon.com/zh_tw/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`。(將 `region` 和 `accountID` 更換為 AWS 區域和帳戶 ID，將 `example@example.com` 更換為有效的電子郵件地址。)

   ```
   aws sns subscribe \
       --topic-arn arn:aws:sns:region:accountID:wooferTopic \
       --protocol email \
       --notification-endpoint example@example.com
   ```

1. Amazon SNS 會將確認訊息傳送至電子郵件地址。選擇該郵件中的 **Confirm subscription (確認訂閱)** 連結，完成訂閱程序。

## 步驟 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. 建立 zip 檔以包含 `publishNewBark.js`。如果您有 zip 命令列公用程式，即可輸入下列命令執行此作業。

   ```
   zip publishNewBark.zip publishNewBark.js
   ```

1. 當您建立 Lambda 函式時，您要為自己在 [步驟 2：建立 Lambda 執行角色](#Streams.Lambda.Tutorial.CreateRole) 中建立的 `WooferLambdaRole` 指定 Amazon Resource Name (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 函式發生問題，您可以使用這些診斷進行疑難排解：  
在 [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/) 開啟 CloudWatch 主控台。
在導覽窗格中，選擇 **Logs** (日誌)。
選擇下列的日誌群組：`/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. 輸入以下命令來建立觸發。將 `streamARN` 更換為實際的串流 ARN。

   ```
   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 函式發生問題，您可以使用這些診斷進行疑難排解。  
在 [https://console.aws.amazon.com/cloudwatch/](https://console.aws.amazon.com/cloudwatch/) 開啟 CloudWatch 主控台。
在導覽窗格中，選擇 **Logs** (日誌)。
選擇下列的日誌群組：`/aws/lambda/publishNewBark`
選擇最新的日誌串流，檢視函數的輸出 (和錯誤)。

# 教學課程 \$12：使用篩選條件來處理 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 } } `  | 
|  空白  |  產品名稱為空  |  `{ "product_name": { "S": [ ""] } } `  | 
|  Equals  |  州等於佛羅里達州  |  `{ "state": { "S": ["FL"] } } `  | 
|  及  |  產品原產州等於佛羅里達州，且產品類別為巧克力  |  `{ "state": { "S": ["FL"] } , "category": { "S": [ "CHOCOLATE"] } } `  | 
|  或  |  產品原產州是佛羅里達州或加州  |  `{ "state": { "S": ["FL","CA"] } } `  | 
|  Not  |  產品原產州不是佛羅里達州  |  `{"state": {"S": [{"anything-but": ["FL"]}]}}`  | 
|  存在  |  自製產品存在  |  `{"homemade": {"S": [{"exists": true}]}}`  | 
|  不存在  |  自製產品不存在  |  `{"homemade": {"S": [{"exists": false}]}}`  | 
|  Begins with  |  PK 以 COMPANY 開頭  |  `{"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 Streams 讀取事件。它也會在 DynamoDB Streams 和 Lambda 函式之間新增事件來源映射，以便每次在 Amazon DynamoDB Streams 中發生事件時，都可以執行該函數。

```
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
```

部署此雲端編組範本之後，即可插入下列 Amazon DynamoDB 項目：

```
{
 "PK": "COMPANY#1000",
 "SK": "PRODUCT#CHOCOLATE#DARK",
 "company_id": "1000",
 "type": "",
 "state": "FL",
 "stores": 5,
 "price": 15,
 "quantity": 50,
 "fabric": "Florida Chocolates"
}
```

由於此雲端編組範本內嵌簡單的 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 專案編組範本逐步解說事件篩選功能。在使用此 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
```

# 搭配 Lambda 使用 DynamoDB Streams 的最佳實務
<a name="Streams.Lambda.BestPracticesWithDynamoDB"></a>

 AWS Lambda 函數會在*容器中*執行，這是與其他函數隔離的執行環境。當您第一次執行函數時， 會 AWS Lambda 建立新的容器，並開始執行函數的程式碼。

Lambda 函式有一個*處理常式*，每次調用只執行一次。此處理常式包含函數的主要商業邏輯。例如，[步驟 4：建立並測試 Lambda 函式](Streams.Lambda.Tutorial.md#Streams.Lambda.Tutorial.LambdaFunction) 中顯示的 Lambda 函式有一個可處理 DynamoDB Streams 紀錄的處理常式。

您也可以在建立容器之後，但在第一次 AWS Lambda 執行處理常式之前，提供僅執行一次的初始化程式碼。[步驟 4：建立並測試 Lambda 函式](Streams.Lambda.Tutorial.md#Streams.Lambda.Tutorial.LambdaFunction) 中顯示的 Lambda 函式有初始程式碼，可匯入適用於 Node.js 中 JavaScript 的開發套件，並建立 Amazon SNS 的用戶端。這些物件應該只在處理常式外部定義一次。

函數執行後， AWS Lambda 可能會選擇重複使用容器來後續叫用函數。在這種情況下，您的函數處理常式或許能夠重複使用您在初始化程式碼中定義的資源。(您完全無法控制 AWS Lambda 保留容器多長時間，或容器是否得以重複使用。)

對於使用 的 DynamoDB 觸發程序 AWS Lambda，我們建議下列事項：
+ 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 記錄。透過 [Amazon Managed Service for Apache Flink](https://aws.amazon.com/managed-service-apache-flink/)，您可以使用 Apache Flink 即時轉換並分析串流資料。Apache Flink 是開放原始碼串流處理架構，用於處理即時資料。Apache Flink 專用的 Amazon DynamoDB Streams 連接器可簡化 Apache Flink 工作負載的建置和管理，方便您將應用程式與其他 AWS 服務整合。

Amazon Managed Service for Apache Flink 可協助您快速建置端對端串流處理應用程式，以進行日誌分析、點擊流分析、物聯網 (IoT)、廣告技術、遊戲等。四個最常見的使用案例是串流擷取、轉換和載入 (ETL)、事件驅動型應用程式、回應式即時分析，以及資料串流的互動式查詢。如需從 Amazon DynamoDB Streams 寫入 Apache Flink 的詳細資訊，請參閱 [Amazon DynamoDB Streams 連接器](https://nightlies.apache.org/flink/flink-docs-master/docs/connectors/datastream/dynamodb/)。