MongoServerError排查:更新不可变字段_id触发ImmutableField错误
修复MongoDB ImmutableField错误:Performing an update on the path '_id' would modify the immutable field '_id'
问题描述
每次执行命令都会触发MongoServerError,提示修改不可变字段_id。此前命令运行正常,添加更新数组的新函数后出现该问题。
错误栈
MongoServerError: Plan executor error during findAndModify :: caused by :: Performing an update on the path '_id' would modify the immutable field '_id' at Connection.onMessage (/app/node_modules/mongodb/lib/cmap/connection.js:230:30) at MessageStream.<anonymous> (/app/node_modules/mongodb/lib/cmap/connection.js:61:60) at MessageStream.emit (node:events:527:28) at processIncomingData (/app/node_modules/mongodb/lib/cmap/message_stream.js:125:16) at MessageStream._write (/app/node_modules/mongodb/lib/cmap/message_stream.js:33:9) at writeOrBuffer (node:internal/streams/writable:390:12) at _write (node:internal/streams/writable:331:10) at MessageStream.Writable.write (node:internal/streams/writable:335:10) at TLSSocket.ondata (node:internal/streams/readable:777:22) at TLSSocket.emit (node:events:527:28) { ok: 0, code: 66, codeName: 'ImmutableField', '$clusterTime': { clusterTime: new Timestamp({ t: 1678502007, i: 4 }), signature: { hash: new Binary(Buffer.from("6d07aad2ad2644b91f653725c0f9ffbbcd035be7", "hex"), 0), keyId: new Long("7148511101804609538") } }, operationTime: new Timestamp({ t: 1678502007, i: 4 }), [Symbol(errorLabels)]: Set(0) {} }
新增函数
async function task(card, damage, interaction) { let profile = await profileModel.findOne({ user: card.userID, }); if (profile.task.length === 0) return; profile.task.forEach((e, i) => { if (e.complete !== true && e.type === "damage") { e.damageDone += damage; } }); profile.markModified("task"); await profileModel.findOneAndUpdate({ user: interaction.user.id }, profile); return profile; }
核心代码片段
await profileModel.findOneAndUpdate( { user: winner, }, { $inc: { coins: bet, }, } ); await profileModel.findOneAndUpdate( { user: loser, }, { $inc: { coins: -bet, }, } ); await interaction.followUp({ embeds: [endEmbed], }); if (winner === interaction.user.id) { if (profile.guild) { let guild = await guildModel.findOne({ name: profile.guild, }); guild.chestXP += 15; await profileModel.findOneAndUpdate( { user: interaction.user.id, }, profile ); } if (profile.task.length === 0) return; let profileTask = profile.task.filter( (v) => v.complete !== true && v.type === "badge" && v.id === "bc" ); if (profileTask.length === 0) return; profileTask.forEach(async (element, int) => { let task = element; task.progress += 1; await profileModel.findOneAndUpdate( { user: interaction.user.id, }, profile ); }); } }
解决方案
错误根源
通过findOne获取Mongoose文档实例后,直接将整个实例传给findOneAndUpdate,MongoDB会执行文档替换操作,此时实例中的_id字段会被当作更新字段,而_id是MongoDB的不可变字段,因此触发错误。
正确做法是:修改文档实例后直接调用save()方法,Mongoose会自动生成仅更新变化字段的操作,不会包含_id。
1. 修复新增的task函数
async function task(card, damage, interaction) { let profile = await profileModel.findOne({ user: card.userID }); if (!profile || profile.task.length === 0) return; profile.task.forEach((e) => { if (e.complete !== true && e.type === "damage") { e.damageDone += damage; } }); profile.markModified("task"); await profile.save(); // 替换findOneAndUpdate,直接保存修改后的实例 return profile; }
2. 修复核心代码中的错误
修复 guild 相关的更新
将:
await profileModel.findOneAndUpdate( { user: interaction.user.id }, profile );
替换为:
await profile.save();
修复任务进度更新的循环
原forEach循环存在异步并发问题,且多次调用findOneAndUpdate效率低下,改为for...of循环后一次性保存:
// 替换原forEach代码块 if (profileTask.length > 0) { for (const element of profileTask) { element.progress += 1; } profile.markModified("task"); await profile.save(); }
验证修改
修改完成后,执行命令时MongoDB只会更新实际变化的字段(如task数组),不会尝试修改_id,即可解决该错误。
内容的提问来源于stack exchange,提问作者Shinpi Tekita
相关产品推荐
相关产品推荐

