MongoError: E11000唯一键重复错误触发异常不一致问题
你的问题核心在于部分唯一索引的生效规则和**mongoose_delete插件的字段注入范围**不匹配导致的:
部分唯一索引的校验逻辑
你配置的唯一索引仅对deleted: false的文档生效,也就是说,MongoDB只会检查那些明确带有deleted: false字段的文档是否存在重复的userName,而没有deleted字段或者deleted为true的文档不会被纳入校验范围。findOneAndUpdateupsert的文档未被插件处理mongoose_delete插件只会对模型的create、save等标准创建方法自动注入deleted: false字段,但findOneAndUpdate的upsert操作属于直接的数据库更新操作,不会触发插件的字段注入逻辑。所以你通过upsert创建的Kim文档没有deleted字段,不在唯一索引的校验范围内。两次
create的差异- 第一次
Order.create时,插件自动给新文档加上了deleted: false,此时集合中没有其他满足deleted: false的userName: 'Kim'文档,所以不会触发唯一键冲突。 - 第二次
Order.create时,已经存在一个deleted: false的Kim文档,触发了唯一索引的校验规则,因此抛出E11000重复键错误。
- 第一次
针对这个问题,有两种可靠的解决方式:
方案1:给deleted字段设置默认值
在Schema中直接给deleted字段添加default: false,这样无论通过哪种方式创建文档(包括upsert),都会自动带上deleted: false,确保所有正常文档都被纳入唯一索引的校验范围:
const orderSchema = new mongoose.Schema({ userName: { type: String, index: { unique: true, partialFilterExpression: { deleted: false } }, }, item: { type: String }, deleted: { type: Boolean, default: false } // 添加默认值 });
方案2:在upsert操作中显式设置deleted字段
如果你不想修改Schema的默认值,可以在findOneAndUpdate的更新参数中显式指定deleted: false,确保upsert创建的文档符合索引过滤条件:
const one = await Order.findOneAndUpdate( { userName: 'Kim' }, { $set: { item: 'Flowers', deleted: false } }, // 显式添加deleted字段 { upsert: true, new: false } );
额外注意
确保mongoose_delete插件的配置正确,它的overrideMethods: 'all'会覆盖大部分查询方法,但不会自动处理findOneAndUpdate这类更新操作的upsert场景,所以通过Schema默认值兜底是最稳妥的方式。
内容的提问来源于stack exchange,提问作者Dashiell Rose Bark-Huss

