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) 執行一次:

請求形狀來回次數讀取單位回傳項目數
照原樣3284.58
.limit(25)25288.08

容量是按頁計費,並向上進位到 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 400

Year 是那 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 裡。
  • 在建構器中數字是 StringAttributeValue.builder().n("2010") 接受的是 java.lang.String,因為 DynamoDB 是以十進位文字傳輸數字的。傳入 int 無法通過編譯。
  • .segment(...) / .totalSegments(...) 會把一次全表掃描切給多個工作者,每個都有自己的分頁器。那能分掉牆上時鐘的時間,花費的容量則不變。

改用視覺化操作

保留字檢查器會拿你的屬性名稱去比對完整的 AWS 清單,並交還 ExpressionAttributeNames 對應,這比一次一個 ValidationException 慢慢學到 YearNameSizeStatus 全都被占用了要快得多。

若想在把一個 filter 接進 ScanRequest 之前先對真正的表格試跑,請下載 DynoTable,在格線中翻閱結果。

相關範例

參考資料

以視覺化方式建構此請求

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

開啟 DynamoDB 查詢建構器

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

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

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