

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

# 在 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 不會將該項目的任何資料寫入索引。