MongoDB实现无员工关联Task的原子删除 避免查询删除间竞态条件
解决方案
方案1:使用MongoDB多文档事务(最优方案,要求MongoDB 4.0及以上版本)
MongoDB 4.0之后支持跨集合的多文档ACID事务,你可以把「校验无员工关联该任务」和「删除任务」两个操作放在同一个事务中执行,事务的隔离性会自动避免查询和删除间隙的其他写入操作干扰,从根本上解决竞态问题。
示例代码如下:
async deleteTask(taskID: string) { // 开启会话 const session = await db.startSession(); try { await session.withTransaction(async () => { // 事务内查询是否有员工关联该任务 const emp = await db.employeesCollection.findOne( { tasks: taskID }, { session } ); if (emp) { throw new WrongOperationException("can't delete task while some employees working on it."); } // 事务内执行删除 await db.tasksCollection.deleteOne( { _id: taskID }, { session } ); }); } finally { // 关闭会话 await session.endSession(); } }
注意:该方案需要你的MongoDB部署为副本集或者分片集群模式,这是MongoDB事务的必要运行环境。
方案2:轻量补偿方案(适用于不支持事务的低版本MongoDB)
如果你无法使用事务,可以通过「先查询存任务快照+执行删除+后置校验+异常回滚」的逻辑避免竞态,同时要求任务分配的逻辑必须先校验任务存在才允许分配,具体逻辑如下:
async deleteTask(taskID: string) { // 先查询待删除任务快照,用于后续回滚 const task = await db.tasksCollection.findOne({ _id: taskID }); if (!task) throw new Error("task not exist"); // 执行原子删除操作 const deleteRes = await db.tasksCollection.deleteOne({ _id: taskID }); if (deleteRes.deletedCount === 0) throw new Error("task delete failed"); // 回查是否有员工关联该任务 const emp = await db.employeesCollection.findOne({ tasks: taskID }); if (emp) { // 若存在关联,把删除的任务插回集合实现回滚 await db.tasksCollection.insertOne(task); throw new WrongOperationException("can't delete task while some employees working on it."); } }
该方案性能损耗更低,但需要配套任务分配接口的存在性校验逻辑,避免删除间隙的无效分配操作。
内容的提问来源于stack exchange,提问作者Amjad Roy
相关产品推荐
相关产品推荐

