Python(boto3)中的 DynamoDB Scan
boto3 裡的一次 scan 是兩個決定:請求本身,以及你怎麼分頁。下面的程式碼用的是內建分頁器,那不是別人繞著你的迴圈寫出來的包裝。它是五行 botocore 設定,而那五行決定了你的掃描是否正確、以及它要花多少錢。(至於你到底該不該掃描,那是另一個問題。)
程式碼
import boto3
client = boto3.client("dynamodb")
paginator = client.get_paginator("scan")
items = []
for page in paginator.paginate(
TableName="Music",
FilterExpression="#filter0 >= :filterValue0",
ExpressionAttributeNames={"#filter0": "Year"},
ExpressionAttributeValues={":filterValue0": {"N": "2010"}},
):
items.extend(page["Items"])
print(f"Matched {len(items)} items")說明
- 分頁器是資料,不是程式碼。botocore 在
paginators-1.json裡為每個操作各放一筆;Scan的那一筆是{"input_token": "ExclusiveStartKey", "output_token": "LastEvaluatedKey", "limit_key": "Limit", "result_key": ["Items", "Count", "ScannedCount"], "non_aggregate_keys": ["ConsumedCapacity"]}。以下的一切都是從那些 key 推導出來的。 PaginationConfig={"PageSize": n}設定的是Limit,因為Limit就是那個limit_key。Limit限制的是被讀取的項目數,永遠不是被回傳的項目數,所以搭配FilterExpression時,一頁可以是空的、卻仍然有成本。MaxItems算的是result_key的項目數,並交還一個NextToken,讓你在之後的行程裡當成StartingToken傳入。它不會阻止請求讀過你設定的截止點。build_full_result()只彙總result_key那些欄位。Items、Count與ScannedCount會被加總;ConsumedCapacity是non_aggregate_key,所以合併後的結果會把單一頁的容量當成整次掃描的容量來回報。請自己逐頁加總,否則你少報的倍數就是頁數。FilterExpression是在讀取之後於伺服器端執行,所以你是按ScannedCount而不是Count計費。#filter0為Year取別名,是因為它是一個保留字;沒有那個別名,請求在讀到任何東西之前就會失敗。- 錯誤一律以
botocore.exceptions.ClientError抵達。請依e.response["Error"]["Code"]分支;個別錯誤的類別只以用戶端上動態產生的屬性形式存在(client.exceptions.ProvisionedThroughputExceededException),從來不是可以匯入的符號。 - 資源 API 是另一種使用手感。
Table.scan接受原生 Python 型別、以decimal.Decimal回傳數字,並用Attr("Year").gte(2010)而不是佔位符對應來組出 filter。
一頁帶 filter 的掃描實際花多少
60 個項目,每個約 2 KB,其中 Year = 2024 命中兩個,PageSize=10,對 DynamoDB Local 執行:
page 1: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 2: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 3: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 4: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 5: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 6: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 7: Count=0 ScannedCount=0 CU=0.0 LastEvaluatedKey=no
total CU across pages: 15.0六個真實頁面中有四頁什麼都沒回傳,卻付了全額。那就是分頁器存在所要防止的那種 bug 的樣貌:一個在 Items 為空時就中斷的自刻迴圈,會在第 1 頁退出,並把兩首符合的歌回報成零。
第 7 頁是另一半的故事。第 6 頁在表格的最後一個項目上碰到它的 Limit,所以 DynamoDB 照樣回傳了一個 LastEvaluatedKey,於是分頁器多花了一趟來回,才知道已經沒東西了。LastEvaluatedKey 的意思是「我停下來了」,不是「還有更多」。
對同一次掃描呼叫 build_full_result() 回報的是 CapacityUnits: 2.5。那六頁消耗的是 15.0。
不用自己寫迴圈的分頁方式
DynamoDB 查詢建構器會把 filter、別名對應與分頁迴圈組成一支可執行的程式,所以上面那個 Limit 與 Count 的陷阱在你貼上之前就已經處理好了。若想互動式地翻閱一張真正的表格,而不是從指令碼跑,請下載 DynoTable。
相關指南
- Query vs. Scan — 什麼時候(很少)
scan是站得住腳的。 - 為什麼我的 DynamoDB Scan 又慢又貴? — 成本模型與如何避開它。
- 平行掃描 — 用
Segment/TotalSegments切開表格,每段一個分頁器。 - DynamoDB ProvisionedThroughputExceededException — 一次全表掃描會對佈建表格的容量做什麼。
參考資料
- Scan — Amazon DynamoDB API Reference
- scan — Boto3 DynamoDB.Client Reference
- Scan paginator — Boto3 DynamoDB Reference
- Scanning tables — Amazon DynamoDB Developer Guide
最後驗證於 2026-07-28,對照上方連結的 AWS 官方文件。