Go 中的 DynamoDB Query(AWS SDK v2)
Query 會讀取一個分割區,並可選擇用排序索引鍵再收窄(Query 與 Scan 的比較說明何時該這麼做,鍵條件運算式則列出每一個合法的運算子)。AWS SDK for Go v2 額外加上的是 QueryPaginator,它把 LastEvaluatedKey 迴圈變成一個 for 迴圈,也在這個過程中悄悄拿走了你喊停的機會。
程式碼
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.NewQueryPaginator(client, &dynamodb.QueryInput{
TableName: aws.String("Music"),
KeyConditionExpression: aws.String(
"#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)"),
ExpressionAttributeNames: map[string]string{
"#hashKey": "Artist",
"#rangeKey": "SongTitle",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":hashKeyValue": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
":rangeKeyValue": &types.AttributeValueMemberS{Value: "C"},
},
})
var items []map[string]types.AttributeValue
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
log.Fatalf("query: %v", err)
}
items = append(items, page.Items...)
}
fmt.Printf("Found %d items\n", len(items))
}說明
Limit 並不會限制分頁器實際讀了多少。這一條是會花到錢的。在上面的輸入加上 Limit: aws.Int32(5),然後對一個含 30 筆各約 60 KB 項目的分割區執行,結果是:
page 1: Count=5 CU=37.0 page 5: Count=5 CU=37.0
page 2: Count=5 CU=37.0 page 6: Count=5 CU=37.0
page 3: Count=5 CU=37.0 page 7: Count=0 CU=0.0
page 4: Count=5 CU=37.0 total: 30 items, 222.0 units三十筆全都回來了。Limit 是頁面大小,而分頁器的工作就是一直要下一頁直到沒有為止,所以兩者正好互相抵銷。AWS 把它定義為「the maximum number of items to evaluate (not necessarily the number of matching items)」(擷取於 2026-07-28)。如果你只要前五筆,請在第一頁之後自己 break 出迴圈。
小頁面只會稍微貴一點,不會便宜。同一個分割區不帶 Limit 讀取時分成兩頁、共 220.0 個單位;Limit: 5 則跑了七次呼叫、共 222.0 個單位。每一頁都會把自己的位元組總量向上進位到下一個 4 KB 邊界,所以頁數愈多、進位就愈多,還多了六次來回的延遲。想靠調低 Limit 來「少讀一點」,兩邊都得不到。
這個迴圈永遠會比資料多發出一次呼叫。上面的第 7 頁回傳了零筆項目。只要達到了 Limit,DynamoDB 就會回傳一個 LastEvaluatedKey,不管後面還有沒有東西,而 HasMorePages() 就信了它。所以一個帶 Limit 的分頁器會以一次白費的請求收尾,而任何逐頁的副作用(進度條、批次寫出、日誌行)都會對著一個空頁觸發一次。請用 len(page.Items) 來擋。
在第一次請求之前 HasMorePages() 就是 true。它被初始化為 true,好讓那個 for 迴圈至少進得去 — 也就是說它是迴圈條件,不是「有沒有資料」的檢查。拿它來決定要不要查詢,答案永遠是 yes。
錯誤是逐頁浮現的,而讀到一半是一種真實狀態。NextPage 回傳的是與其他呼叫一樣、被 smithy 包過的錯誤,所以請用 errors.As 對 *types.ProvisionedThroughputExceededException 之類的型別解包,而不是比對字串。片段裡的 log.Fatalf 會把已經蒐集到的頁面全丟掉;在服務裡,你通常會想保住 items 並回報自己走到了哪裡。
ScanIndexForward 是唯一能反向讀取分割區的方法。設 ScanIndexForward: aws.Bool(false) 就是排序索引鍵由大到小。除此之外沒有任何「sort by」:順序來自排序索引鍵,需要別的順序就得再開一個索引。IndexName: aws.String("...") 則會把整個查詢搬到那個索引上。
有兩個套件能讓這一切更短。github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression 能從 expression.Key("Artist").Equal(expression.Value("Arturo Sandoval")) 產生鍵條件與兩張佔位符對應表,省掉手寫的 #hashKey/:hashKeyValue 配對,連帶也省掉保留字的風險。attributevalue.UnmarshalListOfMaps(page.Items, &songs) 則能把一頁直接變成 []Song。
改用視覺化操作
那兩張佔位符對應表,正是值得用產生的、而不是用打的部分。免費的 DynamoDB Expression Builder 會把鍵條件與相符的 ExpressionAttributeNames 及 ExpressionAttributeValues 一起組出來,並輸出成 Go 的字面值,讓名稱與值不會各走各的。
想對你自己的資料表執行查詢 — 鍵條件表單、隨著捲動自動分頁的表格、把請求複製回 Go 程式碼 — 請下載 DynoTable。
相關範例
- Java 中的 DynamoDB Query — 以 AWS SDK for Java 2.x 做同一個查詢。
- Go 中的 DynamoDB Scan — 當你無法用鍵鎖定一個分割區時。
- 分頁 —
LastEvaluatedKey、ExclusiveStartKey,以及為什麼Limit不是頁面大小。 - 「Query condition missed key schema element」 — 鍵條件指到了錯的屬性,或漏掉了分割區索引鍵。
- 「Query key condition not supported」 — 鍵條件不能用的運算子,例如 contains 或第二個排序索引鍵條件。
參考資料
- Query — Amazon DynamoDB API Reference
- Use Query with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Querying tables — Amazon DynamoDB Developer Guide
已於 2026-07-28 在 go1.26.5 上以 aws-sdk-go-v2/service/dynamodb v1.62.1,對照連接埠 9000 上的 DynamoDB Local(amazon/dynamodb-local),在一個含 30 筆各約 60 KB 項目的分割區上實測。逐頁的筆數與容量數據皆為擷取的實際輸出。DynamoDB Local 套用文件所述的進位規則;請把絕對單位當成形狀的示範,並在估算容量之前先量測線上服務。