Go 中的 DynamoDB PutItem(AWS SDK v2)
PutItem 會寫入一整個項目,並替換掉任何具有相同主索引鍵的既有項目(以項目為單位的操作談了那與 UpdateItem 的差別)。在 AWS SDK for Go v2 中,有意思的部分不是那個呼叫,而是你的 Go 值在送出去的路上變成了什麼。
程式碼
package main
import (
"context"
"errors"
"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)
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String("Music"),
Item: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
"SongTitle": &types.AttributeValueMemberS{Value: "Cubano Chant"},
"AlbumTitle": &types.AttributeValueMemberS{Value: "Danzon"},
"Year": &types.AttributeValueMemberN{Value: "1994"},
"Awards": &types.AttributeValueMemberN{Value: "0"},
},
ConditionExpression: aws.String("attribute_not_exists(#cond0) AND attribute_not_exists(#cond1)"),
ExpressionAttributeNames: map[string]string{
"#cond0": "Artist",
"#cond1": "SongTitle",
},
})
if err != nil {
var ccf *types.ConditionalCheckFailedException
if errors.As(err, &ccf) {
fmt.Println("A song with that key already exists — not overwritten")
return
}
log.Fatalf("put item: %v", err)
}
fmt.Println("Song written")
}說明
attributevalue.MarshalMap 是那個捷徑,而且它有自己的主張。把一個結構丟給 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue,而不是像上面那樣手工組出 map[string]types.AttributeValue,是一般的做法。以下是它對一個含有 time.Time、一個原封未動的 string 欄位與一個 nil *int 的結構,實際產出的東西:
Artist => &types.AttributeValueMemberS{Value:"Arturo Sandoval"}
SongTitle => &types.AttributeValueMemberS{Value:"Cubano Chant"}
Released => &types.AttributeValueMemberS{Value:"1994-01-01T00:00:00Z"}
Notes => &types.AttributeValueMemberS{Value:""}
Rating => &types.AttributeValueMemberNULL{Value:true}從中有三件事要記住。time.Time 會變成一個 RFC 3339 字串,而不是 Unix 數字,所以時間戳排序索引鍵是按字典序排序的,而且只有在每個值都補零到相同長度、且位於同一個時區時才會如你所願。一個原封未動的字串會變成一個真正的空字串屬性,而不是被略過。而一個 nil 指標會變成 NULL,那是一個存在的屬性。
一個 NULL 屬性會擊敗 attribute_not_exists。那是會賠掉一個下午的那一種。寫入一個 Rating 來自 nil *int 的項目,然後用 attribute_not_exists(Rating) 來防護下一次寫入,它就會失敗:
ConditionalCheckFailedException: The conditional request failedDynamoDB 是對的:那個屬性就在那裡,持有 NULL。修法是結構標籤 dynamodbav:"Rating,omitempty",它會把欄位丟掉而不是把它設成 null。同一個標籤也擋下了那個空字串屬性,而這很重要,因為那些東西會破壞稀疏索引,而且出現在鍵屬性上時會被直接拒絕:
ValidationException: One or more parameter values are not valid. The AttributeValue for a key attribute cannot contain an empty string value. Key: Artist用 errors.As,絕不要比對字串。Go SDK 會把每一個服務錯誤包進 smithy 的操作錯誤裡,所以你從 err.Error() 拿到的字串並不是 DynamoDB 送來的訊息:
operation error DynamoDB: PutItem, https response error StatusCode: 400, RequestID: 6886fce0-b246-4762-8a8b-0c6dab813040, ConditionalCheckFailedException: The conditional request failederrors.As(err, &ccf) 會把它解開,而在上面的執行中回傳了 true;接著 ccf.ErrorMessage() 就會給你那個乾淨的 The conditional request failed。在外層字串上做 strings.Contains 檢查今天行得通,但在 SDK 改變它的包裝格式那天就會壞掉。
具型別的錯誤能夠帶著那個擋住你的項目。在輸入上設定 ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld,ccf.Item 回來時就會有內容:在上面那次執行中有五個屬性,其中 Year 是 &types.AttributeValueMemberN{Value:"1994"}。那省下了多數重試迴圈手動做的那次失敗後再讀取的往返。ReturnValues: types.ReturnValueAllOld 則是成功路徑上的對應物(ReturnValues)。
數字是字串,而那不是 Go 的怪癖。AttributeValueMemberN{Value: "1994"} 對任何從具型別語言過來的人來說都看起來不對,但 DynamoDB 的 N 型別是一個以文字傳輸的十進位數,正是為了讓任何東西都不必繞經 float64 來回一趟。strconv.FormatInt 與 strconv.FormatFloat 是那個轉換;attributevalue 會替你做。
你 marshal 了什麼,就付什麼錢。一次約 15 KB 項目的 put,在 ReturnConsumedCapacity 下回報了 "CapacityUnits": 15。寫入每 1 KB 進位一次,所以一個不小心帶進來的 blob 欄位,或者一個 MarshalMap 吐出了你本想丟掉的屬性,都會直接顯示在帳單上。
改用視覺化操作
既然是 MarshalMap 在決定實際落進項目裡的東西,那就值得知道那個項目有多重。免費的 DynamoDB 項目大小計算機接受 marshal 過的 JSON,並回傳位元組大小與它進位後的寫入單位數。
想針對你自己的資料表寫入與編輯項目 — 每個屬性一個欄位、型別選擇器、把結果當成 Go 複製出去 — 就下載 DynoTable。
相關範例
- Java 中的 DynamoDB PutItem — 以 AWS SDK for Java 2.x 做同一個條件寫入,在那裡一個未設定的值會以相反的方式失敗。
- Go 中的 DynamoDB UpdateItem — 變更特定屬性,而不是替換整個項目。
- DynamoDB 條件運算式 —
attribute_not_exists、樂觀鎖等等。 - DynamoDB ConditionalCheckFailedException — 當項目已經存在時,僅建立用的條件會拋出什麼。
- DynamoDB ValidationException — 格式錯誤的項目或運算式的萬用捕手。
參考資料
- PutItem — Amazon DynamoDB API Reference
- Use PutItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- attributevalue package — AWS SDK for Go v2 (pkg.go.dev)
- Handling errors — AWS SDK for Go v2 Developer Guide
- Condition expressions — Amazon DynamoDB Developer Guide
已於 2026-07-28 在 go1.26.5 上,使用 aws-sdk-go-v2/service/dynamodb v1.62.1 與 feature/dynamodb/attributevalue v1.20.55,對照 9000 埠上的 DynamoDB Local(amazon/dynamodb-local)重現。marshal 後的值、錯誤字串與容量讀數皆為擷取到的輸出,逐字複製。