Go 中的 DynamoDB Scan(AWS SDK v2)
NewScanPaginator 是一個游標,不是一個集合:它開頭樂觀、結尾耗盡,而且它的頁面大小來自一個你眼前這個 ScanInput 沒有顯示的地方。至於何時該完全避開 Scan,請見 Query 與 Scan 的比較。
程式碼
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
if err != nil {
log.Fatalf("load config: %v", err)
}
client := dynamodb.NewFromConfig(cfg)
paginator := dynamodb.NewScanPaginator(client, &dynamodb.ScanInput{
TableName: aws.String("Music"),
FilterExpression: aws.String("#filter0 >= :filterValue0"),
ExpressionAttributeNames: map[string]string{
"#filter0": "Year",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":filterValue0": &types.AttributeValueMemberN{Value: "2010"},
},
})
var items []map[string]types.AttributeValue
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
log.Fatalf("scan: %v", err)
}
items = append(items, page.Items...)
}
fmt.Printf("Matched %d items\n", len(items))
}HasMorePages() 實際回傳的是什麼
針對一個 600 首歌、項目約 3.9 KB 的樣本資料,其中 8 首符合 Year >= 2010 且排在最後:
fresh HasMorePages(): true (before any request)
page 1: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 2: len(page.Items)=0 ScannedCount=271 CU=128.5 LastEvaluatedKey set
page 3: len(page.Items)=8 ScannedCount= 58 CU= 27.5 LastEvaluatedKey nil
exhausted HasMorePages(): false
NextPage() after exhaustion: err = "no more pages available"HasMorePages() 在第一次呼叫之前就是 true,所以那個迴圈一定至少跑一次。你不能拿它來測試一張資料表是不是空的。接著有兩頁在掃描仍在進行時回傳了零個項目,這代表 if len(page.Items) == 0 { break } 會對一張握有 8 筆符合項目的資料表回報空結果。而且這個分頁器是一次性的:迴圈之後這個值就用完了,NextPage 會回傳錯誤,而不是重新開始。要再掃一次,就得再呼叫一次 NewScanPaginator。
Limit 住在兩個地方,而選項那個贏
Go 把頁面大小拆到輸入結構與分頁器自己的選項兩邊。兩邊都設定時,選項優先:
ScanInput.Limit = 500, ScanPaginatorOptions.Limit = 25 -> ScannedCount 25當別處某個函式選項已經設了 25,你卻指派 in.Limit = aws.Int32(500) 並期待 500 個項目一頁,就是一次無聲的落空。如果你只動 ScanInput,分頁器會照著它做。
已於 2026-07-28 對照 9000 埠上的 DynamoDB Local(amazon/dynamodb-local),使用 go1.26.5 上的 aws-sdk-go-v2/service/dynamodb v1.62.1 實測。
說明
page.Items是[]map[string]types.AttributeValue,不是你的結構。attributevalue.UnmarshalListOfMaps(items, &songs)會一次呼叫就轉換整批,並把Year變成 Go 的int;dynamodbav結構標籤控制那個對應。- 在傳輸線上數字是字串。原始值是
&types.AttributeValueMemberN{Value: "2010"}—N成員裝的是一個 Gostring,所以除非你 unmarshal 進具型別的結構,否則任何算術的兩側都會坐著strconv。 FilterExpression是在讀取之後才執行的,這正是那兩個空頁面每頁仍要花 128.5 個單位的原因。API 參考明說過濾 "does not consume any additional read capacity units",而其推論是:它也不會替你省下任何容量。#filter0是必要的,不是風格。Year是 DynamoDB 的保留字;不做別名就會回傳ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year。Segment/TotalSegments讓每個 goroutine 拿到資料表自己的那一片,而每一片都需要自己的分頁器。平行化砍的是牆鐘時間,花掉的容量一樣多。
改用視覺化操作
DynamoDB query builder 會產出整個程式的形狀 — 過濾條件、名稱與值的對應,以及分頁器迴圈 — 所以本頁量測的那些部分是被產生出來的,而不是靠記憶。
想在把掃描交付到某個服務之前,先看看一個過濾條件真正碰到多少項目,就下載 DynoTable,並在格線中對那張資料表執行它。
相關範例
- Java 中的 DynamoDB Scan — 以 AWS SDK for Java 2.x 做同一件掃描。
- Go 中的 DynamoDB Query — 你通常該優先動用的、比較便宜的讀取。
- Query 與 Scan 的比較 — 什麼時候(很少)一次
Scan是合理的。 - 為什麼我的 DynamoDB Scan 又慢又貴? — 成本模型與如何避開它。
- DynamoDB ProvisionedThroughputExceededException — 一次全資料表掃描對佈建資料表容量造成的後果。
- DynamoDB ThrottlingException — 另一種節流,以及指數退避如何處理它。