Node.js 中的 DynamoDB UpdateItem(AWS SDK v3)
底层的 v3 client 在两个方向上说的都是 DynamoDB JSON,这意味着你发出去的每一个数字和拿回来的每一个数字都是字符串。这不是瑕疵;这是让一个 38 位的 DynamoDB 数字在一门唯一数字类型是 double 的语言里活下来的唯一办法。这也正是 bug 藏身之处。
代码
import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const command = new UpdateItemCommand({
TableName: 'Music',
Key: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'}
},
UpdateExpression: 'SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2',
ExpressionAttributeNames: {
'#upd0': 'Genre',
'#upd1': 'Year',
'#upd2': 'Awards'
},
ExpressionAttributeValues: {
':updValue0': {S: 'Latin Jazz'},
':updValue1': {N: '1994'},
':updValue2': {N: '1'}
},
ReturnValues: 'ALL_NEW'
});
const response = await client.send(command);
console.log(response.Attributes); // the item after the update针对一个原本既没有 Genre 也没有 Awards 的项目,response.Attributes 回来时是:
{"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"1"},"Genre":{"S":"Latin Jazz"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"}}typeof response.Attributes.Awards.N 是 "string",所以 response.Attributes.Awards.N + 1 求值为 "11"。没有东西抛错,没有东西警告,而这个错误的数字会进到你的下一次写入里。在边界处解析:Number(response.Attributes.Awards.N)。
说明
表达式就是一个普通字符串,而 v3 不会检查它。
UpdateItemCommand校验的是输入对象的形状,从不校验UpdateExpression内部的语法,所以一个笔误换来的是一次往返和一个 400。语法在更新表达式里;ADD #upd2 :updValue2是原子自增,而加上ConditionExpression: 'attribute_exists(Artist)'会让这次调用变成只更新而不是 upsert。ReturnValues: 'UPDATED_NEW'通常才是你想要的那个。同样的更新只返回{"Awards":{"N":"2"}},别的什么都没有。ALL_NEW每次调用都把整个项目运回来,在一个大项目上,那是你为读一个计数器而付出的带宽。$metadata是 v3 的带外通道:{"httpStatusCode":200,"requestId":"...","attempts":1,"totalRetryDelay":0}。attempts是对"这次重试了吗"的诚实回答,而当你在推断一次非幂等写入是不是跑了两遍时,这一点很要紧。ValidationException不是一个你能 catch 的类,只是一个你能比较的name。缺一个别名回来的是err.name === 'ValidationException',err.message被设为Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year。文档 client 是另一笔交易。
@aws-sdk/lib-dynamodb接收原生 JS 值并反序列化响应,代价是丢掉那份字符串安全。来自@aws-sdk/util-dynamodb的marshall({awards: 9007199254740993})会直接拒绝:Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.仔细看那条消息里的数字。它结尾是
2,而不是字面量里写的那个3:JavaScript 在 SDK 看到它之前就已经把它舍入过了。本例里的底层 client 不可能有这个问题,因为{N: '9007199254740993'}一路到线上都是文本。
条件失败会交给你什么
在输入里加上 ReturnValuesOnConditionCheckFailure: 'ALL_OLD',抛出的错误就会带着赢了你的那个项目:
name: ConditionalCheckFailedException | message: "The conditional request failed" | http: 400
err.Item: {"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"2"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"},"Genre":{"S":"Latin Jazz"}}无论是哪个 client 抛出的,err.Item 都是原始的 DynamoDB JSON,而且它是免费的。没有它,想搞清楚一次乐观并发更新为什么失败,诚实的办法是再补一次 GetItem——它要花一次读取,而且可能一拿到就又过时了。
DynamoDB JSON 转换器能把那份负载变成一个普通 JS 对象再变回去,这是从一个真实项目造出测试夹具最快的办法。至于一开始怎么从实时表里把那个项目捞出来,下载 DynoTable。
相关指南
- DynamoDB 更新表达式——
SET、ADD、REMOVE、DELETE以及惯用写法。 - 理解 ReturnValues——每个
ReturnValues选项各给你什么。 - "Attribute name is a reserved keyword"——为什么这里的别名映射不是可选的。
- "Invalid UpdateExpression" 语法错误——常见的 SET/ADD 语法错误的解读。
参考资料
- UpdateItem — Amazon DynamoDB API Reference
- UpdateItemCommand — AWS SDK for JavaScript v3 Reference
- Update expressions — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。