在 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),可用的運算子與資料表查詢相同。- 你拿到的只有投影,別的都沒有。索引回傳的是它所投影的內容(
ALL、KEYS_ONLY,或INCLUDE清單);照 API 參考文件所說,「global secondary index queries cannot fetch attributes from the parent table」。少了某個屬性,就代表要再對基礎鍵做一次get_item,或是開一個投影更寬的新索引。 - 缺少索引鍵的項目根本不會出現 — 這就是稀疏索引模式。它能讓一個建在
status = "OPEN"上的索引保持很小,也正是為什麼 GSI 查詢可能回傳得比你預期少、卻什麼錯都不拋。 - resource API 吃的是同一個
IndexName:table.query(IndexName="AlbumTitle-index", KeyConditionExpression=Key("AlbumTitle").eq("Danzon")),進去的是原生 Python 值,出來的是Decimal。 - GSI 的寫入會落在資料表寫入之後。複寫是非同步的,所以對索引做「寫完馬上讀」的路徑偶爾會讀不到。在緊密迴圈裡重試只會燒掉容量,不會讓複寫變快。
改用視覺化操作
DynamoDB Expression Builder 會把索引的鍵條件與帶型別的值對應表寫成可直接用於 boto3 的 Python,包含 client 堅持要、而 resource API 禁止的那些 {"S": ...} 外殼。
想從一張表單把同樣的索引查詢指向你自己的資料表,並在可分頁的表格裡讀取結果,請下載 DynoTable。
相關範例
- Node.js 中查詢 DynamoDB GSI — 以 AWS SDK v3 做同一個索引查詢。
- 使用 AWS CLI 查詢 DynamoDB GSI — 從 shell 做同一個索引查詢。
- Python 中的 DynamoDB Query — 查詢基礎資料表。
- GSI 與 LSI 的比較 — 哪一種索引型別適合這個存取模式。
- 為什麼 GSI 是最終一致的 — 複寫延遲的解釋。
- 「The table does not have the specified index」 — 索引名稱對不上(GSI 名稱區分大小寫)。
- 「Consistent reads are not supported on global secondary indexes」 — 為什麼一致性讀取旗標在 GSI 上會失敗。