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",它会丢掉这个字段而不是把它置空。同一个标签也能挡住空字符串属性,而这很要紧,因为空字符串会破坏稀疏索引,而且出现在键属性上会被直接拒绝:
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 的项目,在 ReturnConsumedCapacity 下报告 "CapacityUnits": 15。写入按每 1 KB 向上取整,所以一个不小心带上的大字段,或者一个把你本想丢掉的属性也发出去的 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 后的值、错误字符串与容量读数均为捕获到的输出,原样照录。