为什么这段Node.js+React代码会在MongoDB中生成两个相似的对象?
问题根因及解决方案
首先注意:MongoDB默认会给集合级别的_id字段添加唯一索引,不可能出现两个完全相同_id的顶层文档,你遇到的大概率是同一个文档内嵌评论数组中的子文档_id重复,或是两个文档的业务字段(movieId/type)重复被你误判为_id相同,具体原因及修复方案如下:
1. 后端Mongoose方法重复执行(最高概率)
你当前的findOneAndUpdate方法同时使用了await调用 + 回调参数,这会导致同一条更新操作被执行两次,等价于连续调用两次$push插入评论,是最常见的重复插入诱因:
// 错误写法:同时用await和回调,操作会执行两次 const movie = await Movie.findOneAndUpdate( { movieId: movieId, type: type }, { $push: { movieReview: { ... } } }, { upsert: true, new: true }, (err, info) => { ... } // 此处传入回调会触发一次执行 )
修正方案:回调和await二选一即可,推荐使用await的写法统一处理逻辑:
try { const movie = await Movie.findOneAndUpdate( { movieId: movieId, type: type }, { $push: { movieReview: { ... } } }, { upsert: true, new: true } ) // 成功逻辑 } catch (err) { // 错误逻辑 }
2. 前端重复提交请求
前端提交逻辑的防重机制存在漏洞:
- 你当前用
commentFlag做防重判断,但如果commentFlag的状态更新是在请求返回之后才执行,用户快速点击两次的间隔小于请求响应时间,就会成功发出两次完全相同的请求 - 额外检查axios全局配置是否开启了自动重试,若请求超时触发自动重发也会导致后端收到两次相同请求
修正方案:点击提交后立刻修改防重状态,请求完成后再按需恢复:
const submit_comment = async () => { if (commentFlag) return // 提交前立刻锁死防重状态 commentFlag = true try { const file = { movieId: id, type: type, userId: userInfo._id, comment: comment, }; const { data } = await axios.post("/api/movie/add-comment", file, config); // 后续成功逻辑 } catch (err) { // 失败后恢复可提交状态 commentFlag = false } }
3. 无联合唯一索引导致并发upsert插入重复文档
如果你遇到的是Movie集合中出现两条movieId和type完全相同的文档,是因为你没有给这两个字段加联合唯一索引,并发请求过来时,两个请求同时查询无匹配文档,会同时触发upsert插入两条数据。
修正方案:在Schema中添加联合唯一索引:
const movieSchema = new mongoose.Schema({ movieId: String, type: String, comment: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }], }); // 添加movieId+type联合唯一索引 movieSchema.index({ movieId: 1, type: 1 }, { unique: true })
4. 内嵌评论子文档手动指定固定_id
如果你遇到的是同一个Movie文档的movieReview数组中出现两个_id相同的评论子文档,大概率是你在$push的时候手动给评论对象传入了固定的_id值,两次插入复用了同一个_id,只需要去掉手动赋值的_id字段,Mongoose会自动为每个子文档生成唯一_id。
内容的提问来源于stack exchange,提问作者Ayush Niroula
相关产品推荐
相关产品推荐

