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 failed

DynamoDB 是對的:那個屬性就在那裡,持有 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 failed

errors.As(err, &ccf) 會把它解開,而在上面的執行中回傳了 true;接著 ccf.ErrorMessage() 就會給你那個乾淨的 The conditional request failed。在外層字串上做 strings.Contains 檢查今天行得通,但在 SDK 改變它的包裝格式那天就會壞掉。

具型別的錯誤能夠帶著那個擋住你的項目。在輸入上設定 ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOldccf.Item 回來時就會有內容:在上面那次執行中有五個屬性,其中 Year&types.AttributeValueMemberN{Value:"1994"}。那省下了多數重試迴圈手動做的那次失敗後再讀取的往返。ReturnValues: types.ReturnValueAllOld 則是成功路徑上的對應物(ReturnValues)。

數字是字串,而那不是 Go 的怪癖AttributeValueMemberN{Value: "1994"} 對任何從具型別語言過來的人來說都看起來不對,但 DynamoDB 的 N 型別是一個以文字傳輸的十進位數,正是為了讓任何東西都不必繞經 float64 來回一趟。strconv.FormatIntstrconv.FormatFloat 是那個轉換;attributevalue 會替你做。

你 marshal 了什麼,就付什麼錢。一次約 15 KB 項目的 put,在 ReturnConsumedCapacity 下回報了 "CapacityUnits": 15。寫入每 1 KB 進位一次,所以一個不小心帶進來的 blob 欄位,或者一個 MarshalMap 吐出了你本想丟掉的屬性,都會直接顯示在帳單上。

改用視覺化操作

既然是 MarshalMap 在決定實際落進項目裡的東西,那就值得知道那個項目有多重。免費的 DynamoDB 項目大小計算機接受 marshal 過的 JSON,並回傳位元組大小與它進位後的寫入單位數。

想針對你自己的資料表寫入與編輯項目 — 每個屬性一個欄位、型別選擇器、把結果當成 Go 複製出去 — 就下載 DynoTable

相關範例

參考資料

已於 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 後的值、錯誤字串與容量讀數皆為擷取到的輸出,逐字複製。

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

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

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