DynamoDB ConditionalCheckFailedException
TL;DR — 你的写入带了一个 ConditionExpression,它针对当前项目求值为 false,因此 DynamoDB 拒绝了该写入并保持项目不变。这通常是_预期之内_的(乐观并发、“不存在则创建”)——捕获它并分支处理,不要盲目重试。
含义
与 ValidationException 不同,请求本身格式良好。DynamoDB 对你的条件求了值,而它不成立,因此 PutItem / UpdateItem / DeleteItem(或 TransactWriteItems 内的单个项目)被拒绝了。没有数据被改变。它返回 HTTP 400,且原样不可重试。
为什么会发生
- 创建时的
attribute_not_exists(pk)守卫——项目已经存在(重复插入)。 - 更新/删除时的
attribute_exists(pk)守卫——项目已经不在了。 - 乐观并发——一个
version = :expected(或updatedAt)检查,其中另一个写入者先到了一步。 - 业务规则守卫——
balance >= :amount、#status = :expected与存储的项目不再匹配。
如何修复
- 把它当作正常结果,而非故障。捕获异常,判断一个失败的条件在你的流程中_意味着_什么(项目已存在 → 返回它;版本陈旧 → 重新读取并用新版本重试)。
- 把当前项目读回来。设置
ReturnValuesOnConditionCheckFailure: 'ALL_OLD',无需第二次往返即可拿到导致失败的那个项目——它随异常本身返回(Item字段),且不消耗任何读取容量。 - 对并发情况重新读取 + 重新计算,然后用新版本重新尝试——不要只是把同一个期望值再发一遍。
那套“重新读取再比对”的循环,正是 DynoTable 的暂存区为手工编辑所做的事——它把你的写入暂存起来,一旦发生乐观锁冲突,就把当前项目摆在你的改动旁边,让你在任何东西被发送出去之前解决冲突。
示例
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, PutCommand} from '@aws-sdk/lib-dynamodb';
import {ConditionalCheckFailedException} from '@aws-sdk/client-dynamodb';
const doc = DynamoDBDocumentClient.from(new DynamoDBClient({}));
try {
await doc.send(
new PutCommand({
TableName: 'Users',
Item: {pk: 'USER#1', email: 'a@b.com'},
ConditionExpression: 'attribute_not_exists(pk)' // create-only
})
);
} catch (err) {
if (err instanceof ConditionalCheckFailedException) {
// Expected: the user already exists. Handle gracefully.
return {alreadyExists: true};
}
throw err;
}常见问题
是什么导致了 DynamoDB 中的 ConditionalCheckFailedException? 一次写入(PutItem、UpdateItem、DeleteItem 或 TransactWrite 中的一项)带了一个 ConditionExpression,它针对当前项目求值为假——例如对一个已经存在的键使用 attribute_not_exists(pk),或者一个不再匹配的版本检查。DynamoDB 拒绝该写入并保持项目不变。
我怎样才能不让 ConditionalCheckFailedException 崩溃我的应用? 捕获该异常,把它当作预期的结果而非故障。一个失败的条件通常意味着“别人先到了一步”(乐观并发)或“项目已经存在”——对它分支处理,而不是盲目重试。
复现方法
一个由 attribute_not_exists 守卫、却打在一个确实存在的键上的 PutItem:
await client.send(
new PutItemCommand({
TableName: 'orders',
Item: {pk: {S: 'ORDER#1'}, sk: {S: 'META'}},
ConditionExpression: 'attribute_not_exists(pk)'
})
);实际输出:
ConditionalCheckFailedException: The conditional request failed
HTTP 400这条消息是刻意不提供信息的——它从不说是条件的哪一部分失败了,也不说项目里实际存的是什么。传入 ReturnValuesOnConditionCheckFailure: "ALL_OLD",当前项目就会随 error.Item 一起回来,于是这件事从猜测变成了对比。
相关错误
- TransactionCanceledException——一个失败的条件_发生在事务内部_。
- ValidationException (overview)
- 代码示例:Conditional write in Node.js · in Python (boto3)——可运行的 ConditionExpression 模式。
- 学习:Condition expressions · Atomic counters
参考资料
- Error handling with DynamoDB — Amazon DynamoDB Developer Guide
- PutItem — Amazon DynamoDB API Reference
- DynamoDB condition expression examples — Amazon DynamoDB Developer Guide
最后核实于 2026-07-13,依据上方链接的 AWS 官方文档。
2026-07-26 针对 DynamoDB Local 2.x 与 AWS SDK for JavaScript v3.1095.0 复现——上方输出为原样照录。