MongoDB迁移执行结果不一致,无报错但仅部分记录生效求助
迁移脚本执行结果不稳定问题
使用migrate@2.1.0与mongoose@8.5.1编写Schema更新的up迁移脚本时,出现执行结果不一致的情况:预期4条记录受影响,但实际仅1-3条生效,偶尔甚至无变更。添加控制台日志后仅能看到部分输出,全程无报错。脚本逻辑验证时结果符合预期,但实际运行不稳定。
原迁移脚本代码:
module.exports.up = async () => { try { // Fetch SpecType documents that have instructions array not empty const specTypes = await SpecType.find({ instructions: { $exists: true, $ne: [] } }).lean(); for (const specType of specTypes) { console.log('specType', specType); const newInstructions = []; await Promise.all( specType.instructions.map(async instruction => { let existingInstruction = await Instruction.findOne({ text: instruction }); if (!existingInstruction) { existingInstruction = await new Instruction({ text: instruction }).save(); } newInstructions.push({ id: existingInstruction._id, text: existingInstruction.text }); return Promise.resolve(); }) ); console.log('NEW', newInstructions); await SpecType.updateOne({ _id: specType._id }, { $set: { instructions: newInstructions } }); } console.log('Migration completed successfully.'); } catch (error) { console.error('An error occurred during migration:', error); throw error; } };
问题根源
- 异步竞态导致数组填充不完整:在
Promise.all的异步回调中直接修改外部的newInstructions数组,由于异步操作的执行顺序不确定,可能出现数组未完全填充就执行updateOne的情况,导致更新数据缺失。 - 未校验更新操作结果:没有检查
updateOne的返回值,无法确认更新是否真正生效,掩盖了可能的更新失败场景。 .lean()的潜在风险:虽然此处查询用.lean()不直接引发问题,但返回普通JS对象而非Mongoose文档,后续若有依赖文档实例的操作易出问题。
修复后的迁移脚本
module.exports.up = async () => { try { // 获取instructions非空的SpecType文档,保留Mongoose文档实例 const specTypes = await SpecType.find({ instructions: { $exists: true, $ne: [] } }); for (const specType of specTypes) { console.log('Processing specType ID:', specType._id); // 由Promise.all直接收集异步处理结果,避免竞态问题 const newInstructions = await Promise.all( specType.instructions.map(async instructionText => { let existingInstruction = await Instruction.findOne({ text: instructionText }); if (!existingInstruction) { // 使用Mongoose推荐的create方法创建文档 existingInstruction = await Instruction.create({ text: instructionText }); } return { id: existingInstruction._id, text: existingInstruction.text }; }) ); console.log('Generated instructions for', specType._id, ':', newInstructions); // 执行更新并校验结果 const updateResult = await SpecType.updateOne( { _id: specType._id }, { $set: { instructions: newInstructions } } ); if (updateResult.modifiedCount === 0) { console.warn('No changes applied to specType:', specType._id); } } console.log('Migration completed successfully.'); } catch (error) { console.error('Migration failed:', error); throw error; } };
关键改进点
- 消除异步竞态:不再通过外部数组收集结果,而是让
map中的异步函数直接返回处理后的对象,由Promise.all统一收集,确保newInstructions完全填充后再执行更新。 - 使用Mongoose原生创建方法:用
Instruction.create()替代手动实例化后调用save(),代码更简洁可靠。 - 校验更新结果:通过
updateResult.modifiedCount确认记录是否被修改,便于排查未生效的情况。 - 移除
.lean():保留Mongoose文档实例,避免后续操作的潜在风险。
内容的提问来源于stack exchange,提问作者ranran212
相关产品推荐
相关产品推荐

