如何实现多进程对MongoDB文档的互斥Read与Write操作?
MongoDB单文档互斥读写解决方案
针对你提到的双进程操作同一份MongoDB文档的并发冲突问题,有两种成熟的落地方案可以满足互斥读写的要求:
方案1:用内置原子操作规避冲突
绝大多数场景下不需要手动加锁,你的问题本质是「读-改-写」非原子流程导致的并发问题,直接把读写逻辑合并为单条原子更新语句即可解决:
- process_a的新增类操作直接用
$push/$addToSet/$inc这类内置原子更新算子实现,不需要提前读取文档,也可以配合findOneAndUpdate直接拿到更新后的最新文档内容,整个执行过程是单文档原子性的,不会被其他进程打断。 - process_b的清空操作直接用
updateOne配合$set置空对应字段、或者$unset删除字段即可,本身也是单文档原子操作,不会出现读和写之间被其他进程插入更新的问题。
这种方案性能最高,没有额外的锁开销,适合绝大多数常规业务场景。
方案2:手动实现文档级悲观锁
如果你的业务逻辑非常复杂,无法合并为单条原子语句,必须走完整的「读-改-写」长流程,可以给目标文档加锁字段实现自定义互斥:
- 先给目标文档新增两个字段:
lock_status(布尔值,默认false,表示未锁定)、lock_expire_at(时间戳,用于避免进程崩溃导致死锁)。 - 任意进程要操作文档前,先执行原子抢锁操作:
const lockResult = await db.collection.updateOne( { _id: 目标文档ID, lock_status: false }, { $set: { lock_status: true, lock_expire_at: Date.now() + 30000 } } // 示例设置锁30秒超时 )
只有返回结果中modifiedCount === 1时才算抢锁成功,才能执行后续的读写更新逻辑。
3. 进程完成所有操作后主动释放锁:
await db.collection.updateOne( { _id: 目标文档ID }, { $set: { lock_status: false }, $unset: { lock_expire_at: "" } } )
- 可以额外加一个低频率的定时任务,清理超时未释放的锁,避免进程异常退出导致锁永久占用。
补充说明
- 不推荐使用集合级别的锁,粒度过大会严重影响整体性能,单文档级别的控制刚好匹配你的需求。
- MongoDB 4.0+虽然支持多文档事务,但如果仅操作单文档,上述两种方案的开销远低于事务,性能表现更好。
内容的提问来源于stack exchange,提问作者Manipal King
相关产品推荐
相关产品推荐

