Python 中的 DynamoDB Query(boto3)

boto3 的 query 分頁器正是本頁很短的原因:它把 LastEvaluatedKey 完全藏了起來。它也藏起了一個你多半想要的數字,而那才是在信任它之前值得知道的部分。至於究竟何時該動用 query,請見 Query 與 Scan 的比較

程式碼

import boto3

client = boto3.client("dynamodb")

paginator = client.get_paginator("query")

items = []
for page in paginator.paginate(
    TableName="Music",
    KeyConditionExpression="#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
    ExpressionAttributeNames={"#hashKey": "Artist", "#rangeKey": "SongTitle"},
    ExpressionAttributeValues={":hashKeyValue": {"S": "Arturo Sandoval"}, ":rangeKeyValue": {"S": "C"}},
):
    items.extend(page["Items"])

print(f"Found {len(items)} items")

分頁器不會替你把帳單加總

針對一個 600 首歌的樣本資料、每首歌約 3.9 KB 且全都在 Artist = "Arturo Sandoval" 之下,這個迴圈產出三頁:271、271 與 58 個項目,分別花費 128.5、128.5 與 27.5 個讀取單位。向同一個分頁器要一份合併結果,你會得到這個:

build_full_result() -> Items 600  Count 600  ScannedCount 600
                       ConsumedCapacity.CapacityUnits 128.5

CountScannedCount 被加總了。ConsumedCapacity 沒有 — 它是第一頁的數字,而真正的總數是 284.5。botocore 的 DynamoDB 分頁器設定明白說出了原因:CountScannedCount 被列為結果鍵,ConsumedCapacity 則是非彙總鍵。如果你是從 build_full_result() 記錄容量,那你對一次整個分割區讀取的回報少了一半以上。

上面 for page in paginator.paginate(...) 迴圈裡那些逐頁的 dict 是原始回應,所以自己把 page["ConsumedCapacity"]["CapacityUnits"] 加起來,就能得到誠實的 284.5。

那個讓你多跑 58 次往返的 Limit

Limit 是一個合法的 query 參數,所以 paginate() 接受它,而它並不是 Python 使用者以為的那個參數:

paginate(..., Limit=10)  ->  61 pages, 10 items each
paginate(...)            ->   3 pages

它限制的是每次請求的項目數,而不是總數,所以分頁器就盡責地發出 61 次 HTTP 呼叫來取回同樣那 600 個項目。要限制總數,請用 PaginationConfig={"MaxItems": 10}PaginationConfig["PageSize"] 才是對應到 Limit 的那個旋鈕。

已於 2026-07-28 對照 9000 埠上的 DynamoDB Local(amazon/dynamodb-local),使用 CPython 3.14.6 上的 boto3 1.43.58 實測。

說明

  • client 在兩個方向上都說 DynamoDB JSON。值以 {"S": "Arturo Sandoval"} 的形式進去,而 Year{"N": "1994"} 的形式回來。而 resource API(boto3.resource("dynamodb").Table(...).query)會雙向轉換,並交給你 Decimal('1994') — 那對金額是正確的,而第一次它拒絕跟一個 float 相加時會讓你意外。
  • Key("Artist").eq(...) 只屬於 resource API。把它傳給 client 會在請求送出前就拋出錯誤:ParamValidationError: Invalid type for parameter KeyConditionExpression ... valid types: <class 'str'>。client 要的是本頁組出來的那個運算式字串。
  • 索引鍵條件是一個等式加上最多一個排序索引鍵比較=<<=>>=BETWEENbegins_with)。其他任何東西都放進 FilterExpression,boto3 會原樣傳過去,而 DynamoDB 會在讀取之後才套用它。ScanIndexForward=False 會反轉順序,IndexName="..." 會改指向某個索引。

改用視覺化操作

DynamoDB Expression Builder 會把索引鍵條件與具型別的 ExpressionAttributeValues 對應寫成可直接給 boto3 用的 Python,而那正是你打成 {"N": 2010} 而不是 {"N": "2010"} 時會出錯的部分。

想從一個索引鍵條件表單,把同一個查詢指向你自己的資料表,並在分頁格線中讀取結果,就下載 DynoTable

相關指南

參考資料

以視覺化方式建構此請求

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

開啟 DynamoDB 查詢建構器

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

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

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