Node.js 中的 DynamoDB Scan(AWS SDK v3)
底下那個 do/while 不是防禦性寫法。一個帶過濾條件的 Scan 頁面可能回來時 Items 陣列是空的,而資料表卻還沒掃完,所以在第一個回應就停下來,正是一次掃描在一張其實有符合項目的資料表上回報零筆的方式。Query 與 Scan 的比較談了何時該完全避開這個操作。
程式碼
import {DynamoDBClient, ScanCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new ScanCommand({
TableName: 'Music',
FilterExpression: '#filter0 >= :filterValue0',
ExpressionAttributeNames: {
'#filter0': 'Year'
},
ExpressionAttributeValues: {
':filterValue0': {N: '2010'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Matched ${items.length} items`);兩個空頁面、284.5 個讀取單位、8 個項目
樣本資料是 600 首歌、每首約 3.9 KB,其中剛好 8 首的 Year >= 2010,而且它們排在最後。以下是上面那個迴圈實際收到的東西:
| 往返次序 | Items.length | ScannedCount | 讀取單位 | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 0 | 271 | 128.5 | 有 |
| 2 | 0 | 271 | 128.5 | 有 |
| 3 | 8 | 58 | 27.5 | 無 |
連續兩頁什麼都沒回傳,而每頁各花 128.5 個讀取單位。寫成 if (!response.Items.length) return 的程式碼會回報一張空的資料表。API 參考把規則講得很白:"a scan result can result in no items meeting the criteria and the Count will result in zero",另外還有 "a FilterExpression is applied after the items have already been read; the process of filtering does not consume any additional read capacity units"。
第二句話請用你的帳單的方式來讀。過濾是免費的,而被它丟掉的每一樣東西都不是:284.5 個讀取單位才送出 8 個項目,和你完全不加過濾條件時付的是同一筆帳。
已於 2026-07-28 對照 9000 埠上的 DynamoDB Local(amazon/dynamodb-local),使用 node v24.18.0 上的 @aws-sdk/client-dynamodb 3.1095.0 實測。計數與容量皆為引擎自己的回應欄位。
說明
- 第一輪時
ExclusiveStartKey: lastEvaluatedKey是undefined。v3 的序列化器會丟掉undefined的成員,所以同一個物件字面值就涵蓋了第一次請求與之後的每一次。改傳{}則會以ValidationException: The provided starting key is invalid失敗。 response.Items ?? []是在做真正的工作。把它和上面那張表一起看:那個空值合併運算子讓累加器在什麼都沒配對到的頁面上保持誠實,而while讓迴圈越過那些頁面繼續活著。#filter0不是裝飾。Year在 AWS 的保留字清單上,不做別名而直接使用會回傳ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year。Limit算的是讀取的項目數,不是回傳的項目數。搭配這個過濾條件,Limit: 10會產出Count: 0與ScannedCount: 10。它是應付容量尖峰的節流旋鈕,而不是要十筆結果的方式。Segment/TotalSegments會把一次全資料表掃描分散到多個工作者上。那切分的是牆鐘時間,不是成本 — 花掉的仍是同樣的 284.5 個單位,只是更快、更並行。
在真實資料表上這要花多少錢
8 個項目花 284.5 個讀取單位就是問題的形狀,而且它是隨資料表線性成長,不是隨結果成長。在把一次帶過濾條件的掃描放上熱路徑之前,先在 DynamoDB 定價計算機裡以你的項目大小與流量替完整讀取標價,再拿它跟一個能把同樣存取模式變成 Query 的 GSI 比較。
想在 GUI 中探索資料表,並取得帶過濾與分頁的結果格線,就下載 DynoTable,別再從腳本裡盲目掃描。
相關指南
- Query 與 Scan 的比較 — 什麼時候(很少)一次
Scan是合理的。 - 為什麼我的 DynamoDB Scan 又慢又貴? — 成本模型與如何避開它。
- DynamoDB ProvisionedThroughputExceededException — 一次全資料表掃描對佈建資料表容量造成的後果。
- DynamoDB ThrottlingException — 另一種節流,以及指數退避如何處理它。