在 Python(boto3)中查詢 DynamoDB GSI

GSI 查詢就是一般的 query 再加上 IndexName,而 AlbumTitle-index 讓我們能依專輯取得歌曲 — 這是 Artist + SongTitle 這組資料表鍵無法服務的存取模式。在 Python 裡真正不一樣的是錯誤處理:兩個最常見的索引錯誤失敗在 boto3 的不同層級,而其中只有一個能用例外類別攔下來。

程式碼

import boto3

client = boto3.client("dynamodb")

paginator = client.get_paginator("query")

items = []
for page in paginator.paginate(
    TableName="Music",
    IndexName="AlbumTitle-index",
    KeyConditionExpression="#hashKey = :hashKeyValue",
    ExpressionAttributeNames={"#hashKey": "AlbumTitle"},
    ExpressionAttributeValues={":hashKeyValue": {"S": "Danzon"}},
):
    items.extend(page["Items"])

print(f"Found {len(items)} songs on the album")

except ValidationException 別說攔截了,連編譯都過不了

在上面的查詢加上 ConsistentRead=True,boto3 會拋出這個 — client API 與 resource API 都一樣:

botocore.exceptions.ClientError: An error occurred (ValidationException) when
calling the Query operation: Consistent reads are not supported on global
secondary indexes

最直覺的處理方式是 except client.exceptions.ValidationException。而它並不存在:

AttributeError: <botocore.errorfactory.DynamoDBExceptions object> has no
attribute ValidationException. Valid exceptions are: BackupInUseException,
... IndexNotFoundException, ... ProvisionedThroughputExceededException, ...

botocore 是從服務模型產生例外類別的,而 DynamoDB 只模型化了其中 33 個。ValidationException 是通訊協定層級的錯誤,不在那 33 個裡面,所以唯一可靠的分支是看錯誤碼:

except ClientError as exc:
    if exc.response["Error"]["Code"] == "ValidationException":
        ...

這種不對稱是真的。索引名稱打錯,你會拿到 IndexNotFoundException,它確實有被模型化,也能用類別攔截。誤用一致性旗標,你拿到的則是一次字串比對。兩個都是索引錯誤;只有一個有型別。

游標裡也帶著資料表的鍵

分頁器把 LastEvaluatedKey 藏了起來,但知道它在索引上裝了什麼還是有價值的。在同一張專輯的 300 首歌上:

page 1: Count 271  capacity 128.5  LastEvaluatedKey ['AlbumTitle', 'Artist', 'SongTitle']
page 2: Count  29  capacity  14.0  LastEvaluatedKey []

GSI 的鍵不是唯一的,所以光靠索引鍵沒辦法接續讀取;DynamoDB 會把索引鍵與基礎資料表的鍵一起回傳。自己手寫、只存索引鍵的分頁,會重複或漏掉項目。

已於 2026-07-28 以 CPython 3.14.6 上的 boto3 1.43.58,對照 DynamoDB Local(amazon/dynamodb-local)重現。錯誤文字與鍵清單皆為函式庫自己的輸出。

說明

  • IndexName 不會取代 TableName。兩者要放在同一次呼叫裡,而 KeyConditionExpression 指的是索引的分割區索引鍵(AlbumTitle),可用的運算子與資料表查詢相同。
  • 你拿到的只有投影,別的都沒有。索引回傳的是它所投影的內容(ALLKEYS_ONLY,或 INCLUDE 清單);照 API 參考文件所說,「global secondary index queries cannot fetch attributes from the parent table」。少了某個屬性,就代表要再對基礎鍵做一次 get_item,或是開一個投影更寬的新索引。
  • 缺少索引鍵的項目根本不會出現 — 這就是稀疏索引模式。它能讓一個建在 status = "OPEN" 上的索引保持很小,也正是為什麼 GSI 查詢可能回傳得比你預期少、卻什麼錯都不拋。
  • resource API 吃的是同一個 IndexNametable.query(IndexName="AlbumTitle-index", KeyConditionExpression=Key("AlbumTitle").eq("Danzon")),進去的是原生 Python 值,出來的是 Decimal
  • GSI 的寫入會落在資料表寫入之後。複寫是非同步的,所以對索引做「寫完馬上讀」的路徑偶爾會讀不到。在緊密迴圈裡重試只會燒掉容量,不會讓複寫變快。

改用視覺化操作

DynamoDB Expression Builder 會把索引的鍵條件與帶型別的值對應表寫成可直接用於 boto3 的 Python,包含 client 堅持要、而 resource API 禁止的那些 {"S": ...} 外殼。

想從一張表單把同樣的索引查詢指向你自己的資料表,並在可分頁的表格裡讀取結果,請下載 DynoTable

相關範例

參考資料

以視覺化方式建構此請求

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

開啟 DynamoDB 查詢建構器

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

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

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