Java(AWS SDK v2)中的 DynamoDB Scan
scanPaginator 會替你處理 LastEvaluatedKey,而多數人接下來會伸手去轉的那個旋鈕 — .limit(...) — 會讓同一次掃描變慢,而且稍微更貴。至於什麼時候該完全避開 Scan,請見 Query vs. Scan。
程式碼
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
public class ScanExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, String> names = new HashMap<>();
names.put("#filter0", "Year");
Map<String, AttributeValue> values = new HashMap<>();
values.put(":filterValue0", AttributeValue.builder().n("2010").build());
ScanRequest request = ScanRequest.builder()
.tableName("Music")
.filterExpression("#filter0 >= :filterValue0")
.expressionAttributeNames(names)
.expressionAttributeValues(values)
.build();
List<Map<String, AttributeValue>> items = new ArrayList<>();
for (ScanResponse page : ddb.scanPaginator(request)) {
items.addAll(page.items());
}
System.out.println("Matched " + items.size() + " items");
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}.limit(25) 把 3 個請求變成 25 個,而且更貴
測試資料集裡有 600 首歌,每首大約 3.9 KB,其中 8 首符合條件。先照原樣執行範例,再加上 .limit(25) 執行一次:
| 請求形狀 | 來回次數 | 讀取單位 | 回傳項目數 |
|---|---|---|---|
| 照原樣 | 3 | 284.5 | 8 |
.limit(25) | 25 | 288.0 | 8 |
容量是按頁計費,並向上進位到 4 KB 邊界,所以把一次 1 MB 的讀取切成 24 次小讀取,就等於把那個進位付了 24 次。第 25 個請求是另一個意外:第 24 頁把整張表格讀完了,卻仍然回傳了一個 LastEvaluatedKey,於是分頁器又問了一次,得到 scannedCount=0。DynamoDB 表達「沒有更多資料」的方式是省略那個鍵,而不是回傳一頁不滿的結果,而在 Limit 的邊界上它自己也還不知道。
.limit(...) 是一個平滑容量的旋鈕,適合用在你不想讓它拖垮線上表格的背景工作。它不是讓掃描更便宜或更短的方法。
那個別名不是風格問題
拿掉 #filter0 直接對 Year 做 filter,SDK 會透過 awsErrorDetails() 把這個丟出來:
DynamoDbException / ValidationException /
Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year
/ http 400Year 是那 573 個保留字之一。跟這個 SDK 裡每一個分頁器錯誤一樣,它是在 for 迴圈的第一次迭代時抵達,而不是在你呼叫 scanPaginator(request) 的時候,所以 try 必須把整個迴圈包起來。
已於 2026-07-28 在 OpenJDK 26.0.1 上,以 software.amazon.awssdk:dynamodb 2.49.4 對照 DynamoDB Local(amazon/dynamodb-local)量測。
說明
- 前兩頁回傳零個項目。用這個 filter,每頁的結果是 0、0 與 8,對應 128.5、128.5 與 27.5 個讀取單位。
filterExpression是在讀取之後才執行,所以那兩個空回應付的是全額,而任何if (page.items().isEmpty()) break都會把表格回報成空的。 ddb.scanPaginator(request).items()會把各頁攤平成單一的Iterable<Map<String, AttributeValue>>並在你背後自動翻頁,當你只想要項目時,這能把巢狀迴圈收掉。它是一個SdkIterable,所以.stream()可用 — 這裡.items().stream().count()會回傳 8。ScanIterable在每次迭代時都會重跑一次掃描。它是惰性的,不是快取的:對同一個物件迴圈兩次就會送出兩次請求、計費兩次。請像範例那樣,一次把它排乾到一個List裡。- 在建構器中數字是
String。AttributeValue.builder().n("2010")接受的是java.lang.String,因為 DynamoDB 是以十進位文字傳輸數字的。傳入int無法通過編譯。 .segment(...)/.totalSegments(...)會把一次全表掃描切給多個工作者,每個都有自己的分頁器。那能分掉牆上時鐘的時間,花費的容量則不變。
改用視覺化操作
保留字檢查器會拿你的屬性名稱去比對完整的 AWS 清單,並交還 ExpressionAttributeNames 對應,這比一次一個 ValidationException 慢慢學到 Year、Name、Size 與 Status 全都被占用了要快得多。
若想在把一個 filter 接進 ScanRequest 之前先對真正的表格試跑,請下載 DynoTable,在格線中翻閱結果。
相關範例
- Go 中的 DynamoDB Scan — 以 AWS SDK for Go v2 做同一次掃描。
- Java 中的 DynamoDB Query — 你通常該優先伸手拿的那個較便宜的讀取。
- Query vs. Scan — 什麼時候(很少)
Scan是站得住腳的。 - 為什麼我的 DynamoDB Scan 又慢又貴? — 成本模型與如何避開它。
- DynamoDB ProvisionedThroughputExceededException — 一次全表掃描會對佈建表格的容量做什麼。
- DynamoDB ThrottlingException — 另一種節流,以及指數退避如何處理它。
參考資料
- Scan — Amazon DynamoDB API Reference
- Use Scan with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- ScanRequest — AWS SDK for Java 2.x API Reference
- Scanning tables — Amazon DynamoDB Developer Guide
- Reserved words in DynamoDB — Amazon DynamoDB Developer Guide