Node.js 中的 DynamoDB 条件写入(AWS SDK v3)
在 AWS SDK v3 里,条件写入有意思的地方不是 ConditionExpression——它到哪儿都一个样,DynamoDB 条件表达式里讲过了。有意思的是失败路径:如果你开口要了,v3 会把落败的那个项目挂在抛出的错误上交给你;你没要,它就什么都不给。
代码
import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
TableName: 'Music',
Key: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'}
},
UpdateExpression: 'SET #upd0 = :updValue0, #version = :newVersion',
ConditionExpression: 'attribute_exists(#cond0) AND #version = :expectedVersion',
ExpressionAttributeNames: {
'#upd0': 'Genre',
'#version': 'Version',
'#cond0': 'Artist'
},
ExpressionAttributeValues: {
':updValue0': {S: 'Latin Jazz'},
':expectedVersion': {N: '7'},
':newVersion': {N: '8'}
},
ReturnValuesOnConditionCheckFailure: 'ALL_OLD'
});
try {
await client.send(command);
console.log('Updated to version 8');
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
// With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
// rides back on the exception — no extra read to see what beat you.
console.log('Lost the race — item is now:', err.Item);
} else {
throw err;
}
}说明
- 条件检查失败是抛出的错误,不是一个状态字段。v3 会 reject 这个 promise,所以写入成功的路径和竞争落败的路径是两条不同的分支。
err.name === 'ConditionalCheckFailedException'是判别式;其他任何东西都必须重新抛出,这也是代码里那个else的用处。把整个catch吞掉,你就把一次限流悄悄变成了什么都没做。 ReturnValuesOnConditionCheckFailure是唯一能看到谁赢了你的办法。没有它,错误只带着消息、别的什么都没有,你又得回去做一次本来不需要的GetItem。API 参考把它的有效值定为ALL_OLD | NONE,并确认它不消耗读取容量。err.Item是一个原始的AttributeValue映射,形状和你发过去的Key一样,不是普通的 JavaScript 值。在拿Version跟数字比较之前,先用@aws-sdk/util-dynamodb的unmarshall过一道,否则你比较的对象是{N: '9'}。- 失败的写入照样要计费。开发者指南写得很明白:条件求值为 false 仍然会消耗写入容量,按新旧项目中较大的那个来算。热键上的重试循环在账单上是实打实的一行,所以要给尝试次数封顶。
- 代码里每个名字都做了别名(
#version→Version、#cond0→Artist),因为生成它的表达式构建器无条件地做别名。在这里这比必要的更重,但永远不会错——这就是它做的取舍。
从异常里读出落败者的那份副本
把存储的 Version 设为 9,再运行这段期望值为 7 的代码。DynamoDB Local 3.3.0 会抛出异常,捕获到的错误带着:
err.name ConditionalCheckFailedException
err.message The conditional request failed
err.$metadata.httpStatusCode 400
err.Item {
Artist: { S: 'Arturo Sandoval' },
Year: { N: '1994' },
Version: { N: '9' },
SongTitle: { S: 'Cubano Chant' },
AlbumTitle: { S: 'Danzon' }
}那个 Version: 9 才是重点所在。重试可以把 :expectedVersion 设成 9 直接再走一遍更新,既不用额外读一次,也不会留下一个让第三个写入者从你的 GetItem 和重试之间钻进来的窗口。
把 ReturnValuesOnConditionCheckFailure 从同一个命令里删掉再跑一次。一样的 name、一样的 message、一样的 400,而 err.Item 是 undefined。没有任何东西提醒你:这个参数是可选的,缺了它不算错误,而读 err.Item 的代码只会在生产里开始打印 undefined。
还要注意,这里的 400 并不代表请求格式有问题。ValidationException 和 ConditionalCheckFailedException 共用这个状态码,而其中只有一个是 bug——这正是分支要判 err.name、绝不判状态码的原因。
想针对自己的数据看着一个条件成功和失败,并且表达式是替你写好而不是敲出来的,就下载 DynoTable。
相关示例
- Python 中的 DynamoDB 条件写入——用 boto3 实现同样的乐观锁。
- 用 AWS CLI 执行 DynamoDB 条件写入——从命令行实现同样的乐观锁。
- Node.js 中的 DynamoDB PutItem——仅创建的
attribute_not_exists写入。 - DynamoDB 条件表达式——每个函数,附带模式。
- 在多个属性上强制唯一性——条件与事务的组合。
- DynamoDB ConditionalCheckFailedException——当条件检查失败是预期之中时,如何低成本地处理它。
参考资料
- UpdateItem — Amazon DynamoDB API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。