在 Node.js(AWS SDK v3)中查詢 DynamoDB GSI

GSI 查詢就是一次普通的 Query 加上 IndexName,然後有兩件事不再照表格查詢的方式運作:你習慣的那個一致性旗標會變成錯誤,而分頁游標會多長出一個屬性。這裡 AlbumTitle-index 依專輯取歌,而基礎表格(Artist + SongTitle)若不掃描就做不到這件事。

程式碼

import {DynamoDBClient, QueryCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

const items = [];
let lastEvaluatedKey;

do {
  const response = await client.send(
    new QueryCommand({
      TableName: 'Music',
      IndexName: 'AlbumTitle-index',
      KeyConditionExpression: '#hashKey = :hashKeyValue',
      ExpressionAttributeNames: {
        '#hashKey': 'AlbumTitle'
      },
      ExpressionAttributeValues: {
        ':hashKeyValue': {S: 'Danzon'}
      },
      ExclusiveStartKey: lastEvaluatedKey
    })
  );

  items.push(...(response.Items ?? []));
  lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);

console.log(`Found ${items.length} songs on the album`);

游標有三個屬性寬,不是兩個

拿上面那個迴圈去跑同一張專輯上的 300 首歌,然後檢查它交還的 LastEvaluatedKey

table query  -> ['Artist', 'SongTitle']
GSI query    -> ['AlbumTitle', 'Artist', 'SongTitle']

GSI 的鍵不是唯一的,所以光靠索引鍵無法續讀一次掃描。DynamoDB 會把索引鍵基礎表格的鍵一起回傳,兩者都必須原封不動地放回 ExclusiveStartKey。這就是為什麼一個自己刻的、只存「我看到的最後一個 sort key」的游標在表格上能運作,到了索引上卻會悄悄漏掉或重複項目 — 也是為什麼當表格的鍵是一個你不希望外洩的使用者 id 時,把那個鍵持久化到用戶端是個壞主意。

ConsistentRead: true 是 400,不是升級

直覺會告訴你強一致讀取花更多容量,換來更新鮮的資料。在 GSI 上,它花掉的是你的整個請求:

ValidationException: Consistent reads are not supported on global secondary indexes
HTTP 400

API 參考同樣直白:"Strongly consistent reads are not supported on global secondary indexes. If you query a global secondary index with ConsistentRead set to true, you will receive a ValidationException." 對 GSI 執行 Scan 也會以同樣的訊息拒絕同一個旗標。local secondary index 則接受它。

已於 2026-07-28 在 node v24.18.0 上,以 @aws-sdk/client-dynamodb 3.1095.0 對照 DynamoDB Local(amazon/dynamodb-local)重現。錯誤文字與鍵的形狀皆為引擎自身的輸出。

說明

  • IndexName 不會取代 TableName。兩者放在同一個指令裡,接著 KeyConditionExpression 指名的是索引的 partition key(AlbumTitle),不是表格的,運算子集合則與表格查詢相同。
  • 你拿到的只有投影出來的東西。GSI 查詢回傳的是索引所投影的內容(ALLKEYS_ONLY,或 INCLUDE 清單),而依 API 參考的說法:"global secondary index queries cannot fetch attributes from the parent table"。少了某個屬性,就代表要對基礎鍵補一次 GetItem,或是加寬投影並重建索引。
  • 缺少索引鍵的項目永遠不會出現。那就是稀疏索引模式,而且是個功能:只把 status = "OPEN" 的資料列納入索引,GSI 就能維持小巧。這也是為什麼 GSI 查詢可能回傳比你預期更少的項目,卻不會拋出任何錯誤。
  • 複寫是非同步的,所以剛落地到表格的一次寫入,可能還不在索引裡。請在「寫後讀」的路徑上把這件事納入考量,而不是用緊密迴圈一直重試。

改用視覺化操作

事後才補上一個 GSI,是學會這件事最昂貴的方式。單一表格設計規劃器會拿你的存取模式,推算出其中哪些需要索引鍵、哪些基礎表格本來就服務得了。

若想瀏覽一張表格的各個索引,並從表單執行 GSI 查詢、搭配分頁格線,請下載 DynoTable

相關範例

參考資料

以視覺化方式建構此請求

在免費的 DynamoDB 查詢建構器中組合此操作 — 鍵條件、Filter、Index、Limit、排序方向與分頁迴圈 — 再把它複製成可執行的 SDK v3、CLI 或 boto3 程式。

開啟 DynamoDB 查詢建構器

不必透過主控台就能操作 DynamoDB

一款快速的 DynamoDB 桌面用戶端,可執行 DynamoDB 無法執行的真正 SQL — JOINs、GROUP BY、聚合 — 並支援視覺化編輯與使用你自己的 Bedrock 金鑰的 AI 代理。

30 天免費試用,無需信用卡 — 之後為無時間限制的免費方案。