Go 中的 DynamoDB UpdateItem(AWS SDK v2)
UpdateItem 在 Go 裡很直觀;彆扭的地方在於 map[string]types.AttributeValue 是一個裝著_介面_的 map,所以你送出的值與拿回來的值,都是指向九種成員結構之一的指標。那一個設計選擇就解釋了底下大部分的摩擦,從這段程式碼裡的 fmt.Println 印不出你的項目這件事開始。
程式碼
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)
out, err := client.UpdateItem(ctx, &dynamodb.UpdateItemInput{
TableName: aws.String("Music"),
Key: map[string]types.AttributeValue{
"Artist": &types.AttributeValueMemberS{Value: "Arturo Sandoval"},
"SongTitle": &types.AttributeValueMemberS{Value: "Cubano Chant"},
},
UpdateExpression: aws.String("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2"),
ExpressionAttributeNames: map[string]string{
"#upd0": "Genre",
"#upd1": "Year",
"#upd2": "Awards",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":updValue0": &types.AttributeValueMemberS{Value: "Latin Jazz"},
":updValue1": &types.AttributeValueMemberN{Value: "1994"},
":updValue2": &types.AttributeValueMemberN{Value: "1"},
},
ReturnValues: types.ReturnValueAllNew,
})
if err != nil {
log.Fatalf("update item: %v", err)
}
fmt.Println(out.Attributes) // the item after the update
}說明
AttributeValueMemberN.Value是一個string,不是數值型別,而attributevalue.Marshal也維持這樣:int64(9007199254740993)會原封不動地 marshal 成"9007199254740993"。DynamoDB 的數字帶著 38 位數的精度,比任何 Go 的浮點數都多,所以 SDK 從不做轉換。剖析由你在邊界處刻意進行。ReturnValues: types.ReturnValueAllNew— 這個常數的值就是字面上的"ALL_NEW"。如果你寧可從型別而不是從文件把它們讀出來,types.ReturnValueAllNew.Values()會列出全部五個(NONE、ALL_OLD、UPDATED_OLD、ALL_NEW、UPDATED_NEW)。UpdateExpression是一個你自己組出來的*string,或者交給下面那個expression套件去做。不論哪種方式,DynamoDB 都是唯一的剖析器:ADD #upd2 :updValue2給你那個原子遞增,ConditionExpression裡的attribute_exists(Artist)會讓這個呼叫只做更新而不是 upsert,而子句文法住在更新運算式裡。ValidationException沒有 Go 型別。types/errors.go定義了 35 個錯誤結構,包括ConditionalCheckFailedException、TransactionCanceledException、ProvisionedThroughputExceededException與TransactionConflictException。驗證失敗不在其中,所以一個壞掉的運算式會以一個泛型的smithy.APIError浮現,而你只能靠字串認出它:operation error DynamoDB: UpdateItem, https response error StatusCode: 400, RequestID: 702d67f1-4f15-43dc-b3e9-cea691878801, api error ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year注意那個
api error前綴,底下那個有建模的ConditionalCheckFailedException並不會有。它的出現是一個不錯的訊號,說明在這裡拿errors.As去比對具體型別幫不了你。相對地,一個失敗的條件是真正的型別。
var ccf *types.ConditionalCheckFailedException; errors.As(err, &ccf)會匹配,而在輸入上加了ReturnValuesOnConditionCheckFailure: types.ReturnValuesOnConditionCheckFailureAllOld之後,ccf.Item回來時會帶著那個項目當下真正的樣子。ccf.ErrorMessage()是乾淨的The conditional request failed,沒有err.Error()會加在前面的那段傳輸層前言。
把結果讀回來
對一個介面 map 呼叫 fmt.Println(out.Attributes) 印出來的是位址:
map[Artist:0x1c8cf589e060 Awards:0x1c8cf589e078 Genre:0x1c8cf589e090 SongTitle:0x1c8cf589e0c0 Year:0x1c8cf589e0a8]有兩條出路。一是 unmarshal 進一個結構,那也是多數程式碼該做的:
var song struct {
Artist string
Genre string
Year int
Awards int
}
err = attributevalue.UnmarshalMap(out.Attributes, &song)
// {Artist:Arturo Sandoval Genre:Latin Jazz Year:1994 Awards:1}二是對你在意的那一個成員做型別斷言,並記得 .Value 是字串:
n, ok := out.Attributes["Awards"].(*types.AttributeValueMemberN)
if ok {
awards, _ := strconv.Atoi(n.Value) // "1" -> 1
fmt.Println(awards)
}讓 expression 套件替你寫
Go 是本站上唯一會替你產生 UpdateExpression 的 SDK。feature/dynamodb/expression 會組出那些子句與兩張對應:
upd := expression.Set(expression.Name("Genre"), expression.Value("Latin Jazz")).
Set(expression.Name("Year"), expression.Value(1994)).
Add(expression.Name("Awards"), expression.Value(1))
expr, _ := expression.NewBuilder().WithUpdate(upd).Build()出來的東西不是你寫的那樣:
UpdateExpression: ADD #0 :0
SET #1 = :1, #2 = :2
Names: map[#0:Awards #1:Genre #2:Year]builder 重排了那些子句、用換行把它們隔開,並自己替佔位符編號,所以 #0 是 Awards,而不是你最先提到的那個名稱。下游沒有人在意,但那些字串在你每次修改後都不穩定,所以拿它們在測試中做斷言並不合適。把 expr.Update()、expr.Names() 與 expr.Values() 直接傳進 UpdateItemInput,然後就別再看它們了。
好處是它會替每一個名稱做別名,所以保留字不再是一類你可能出貨帶走的 bug。如果你改成手寫運算式,請先把屬性名稱丟進 DynamoDB 保留字檢查器 — AWS 的清單有 573 個項目,而 Year、Name 與 Status 全都在上面。而如果你寧可把一個項目當成資料來看,而不是一堆介面指標的 map,就下載 DynoTable。
相關範例
- Java 中的 DynamoDB UpdateItem — 以 AWS SDK for Java 2.x 做同一次更新。
- Go 中的 DynamoDB PutItem — 改成替換整個項目。
- DynamoDB 更新運算式 —
SET、ADD、REMOVE、DELETE與慣用寫法。 - 認識 ReturnValues — 每個
ReturnValues選項各給你什麼。 - 「Attribute name is a reserved keyword」 — 為什麼這裡的別名對應不是選用的。
- 「Invalid UpdateExpression」語法錯誤 — 常見的 SET/ADD 語法錯誤解讀。
參考資料
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- dynamodb package — AWS SDK for Go v2 (pkg.go.dev)
- dynamodb/types package — AWS SDK for Go v2 (pkg.go.dev)
- expression package — AWS SDK for Go v2 (pkg.go.dev)
- Update expressions — Amazon DynamoDB Developer Guide
最後查證於 2026-07-28,對照上方連結的官方 AWS 文件。