Node.js findOne和updateOne的Mongo DB并发问题

Node.js findOne和updateOne的Mongo DB并发问题,node.js,mongodb,concurrency,Node.js,Mongodb,Concurrency,我对更新同一文档的并发请求有问题。我没有使用findAndModify(),因为我需要访问文档的当前状态来进行更新,findAndModify()不支持更新。我还希望避免使用db.fsyncLock(),因为这会锁定整个数据库,我只需要锁定一个集合中的一个文档 首先我使用findOne()获取文档,然后在findOne()的回调中使用updateOne()更新同一文档。当我将一组操作排队并同时运行它们时,我相信它们在调用findOne()时都在访问相同的状态,而不是等待updateOne()从上

我对更新同一文档的并发请求有问题。我没有使用
findAndModify()
,因为我需要访问文档的当前状态来进行更新,
findAndModify()
不支持更新。我还希望避免使用
db.fsyncLock()
,因为这会锁定整个数据库,我只需要锁定一个集合中的一个文档

首先我使用
findOne()
获取文档,然后在
findOne()
的回调中使用
updateOne()
更新同一文档。当我将一组操作排队并同时运行它们时,我相信它们在调用
findOne()
时都在访问相同的状态,而不是等待
updateOne()
从上一个操作完成

我该怎么处理

mongoDBPromise.then((db)=> {
    db.collection("notes").findOne(
        {path: noteId},
        (err, result)=> {
            if (err) {
                console.log(err);
                return;
            }

            if (!result.UndoableNoteList.future.length) {
                console.log("Nothing to redo");
                return;
            }

            let past = result.UndoableNoteList.past.concat(Object.assign({},result.UndoableNoteList.present));
            let present = Object.assign({},result.UndoableNoteList.future[0]);
            let future = result.UndoableNoteList.future.slice(1, result.UndoableNoteList.future.length);

            db.collection("notes").updateOne(
                {path: noteId},
                {
                    $set: {
                        UndoableNoteList: {
                            past: past,
                            present: present,
                            future:future
                        }
                    }
                },
                (err, result)=> {
                    if (err) {
                        console.log(err);
                        return;
                    }
                }
            )
        }
    );
});
由于
updateOne()
是一个异步调用,
findOne()
不会等待它完成,因此可能存在同时更新同一文档的情况,这在mongo中是不允许的

我认为在这种情况下,
updateOne()
是不必要的请注意,您已经在
findOne()
query
中找到了需要更新的文档的正确实例。现在,您可以更新该实例并保存该文档,而无需执行
updateOne()
。我认为这样可以避免这个问题:

mongoDBPromise.then((db)=> {
    db.collection("notes").findOne(
        {path: noteId},
        (err, result)=> {
            if (err) {
                console.log(err);
                return;
            }

            if (!result.UndoableNoteList.future.length) {
                console.log("Nothing to redo");
                return;
            }

            let past = result.UndoableNoteList.past.concat(Object.assign({},result.UndoableNoteList.present));
            let present = Object.assign({},result.UndoableNoteList.future[0]);
            let future = result.UndoableNoteList.future.slice(1, result.UndoableNoteList.future.length);
            result.UndoableNoteList.past = past;
            result.UndoableNoteList.present = present;
            result.UndoableNoteList.future = future;

            //save the document here and return
        }
    );
});

希望这个答案对你有帮助

我无法找到使用纯mongodb函数顺序运行查询的方法。我编写了一些node.js逻辑,阻止mongodb查询在同一文档上运行,并将这些查询添加到队列中。下面是代码当前的样子

Websocket撤消侦听器

module.exports = (noteId, wsHelper, noteWebSocket) => {
    wsHelper.addMessageListener((msg, ws)=> {
        if (msg.type === "UNDO") {
            noteWebSocket.broadcast(msg, noteWebSocket.getOtherClientsInPath(noteId, wsHelper));
            noteWebSocket.saveUndo(noteId);
        }
    });
};
从侦听器调用的
saveUndo
函数

saveUndo(noteId) {
    this.addToActionQueue(noteId, {payload: noteId, type: "UNDO"});
    this.getNoteByIdAndProcessQueue(noteId);
}
从saveUndo调用的
getNoteByIdAndProcessQueue
函数

    getNoteByIdAndProcessQueue(noteId) {
        if (this.isProcessing[noteId])return;
        this.isProcessing[noteId] = true;
        mongoDBPromise.then((db)=> {
            db.collection("notes").findOne(
                {path: noteId},
                (err, result)=> {
                    if (err) {
                        this.isProcessing[noteId] = false;
                        this.getNoteByIdAndProcessQueue(noteId);
                        return;
                    }

                    this.processQueueForNoteId(noteId, result.UndoableNoteList);
                });
        });
    }
processQueueForNoteId
函数

processQueueForNoteId(noteId, UndoableNoteList) {

    this.actionQueue[noteId].forEach((action)=> {
        if (action.type === "UNDO") {
            UndoableNoteList = this.undoNoteAction(UndoableNoteList);
        } else if (action.type === "REDO") {
            UndoableNoteList = this.redoNoteAction(UndoableNoteList);
        } else if (action.type === "ADD_NOTE") {
            UndoableNoteList = this.addNoteAction(UndoableNoteList, action.payload);
        } else if (action.type === "REMOVE_NOTE") {
            UndoableNoteList = this.removeNoteAction(UndoableNoteList, action.payload);
        }
    });

    let actionsBeingSaved = this.actionQueue[noteId].concat();
    this.actionQueue[noteId] = [];
    mongoDBPromise.then((db)=> {
        db.collection("notes").updateOne(
            {path: noteId},
            {
                $set: {
                    UndoableNoteList: UndoableNoteList
                }
            },
            (err, result)=> {
                this.isProcessing[noteId] = false;

                // If the update failed then try again
                if (err) {
                    console.log("update error")
                    this.actionQueue[noteId] = actionsBeingSaved.concat(this.actionQueue[noteId]);
                }

                // if action were queued during save then save again
                if (this.actionQueue[noteId].length) {
                    this.getNoteByIdAndProcessQueue(noteId);
                }
            }
        )
    });
}

当我删除updateOne时,文档不会保存。我目前有一个解决方案,我将db调用排队,然后一次执行一个,但是如果db管理这个队列就好了。当然,它不会被保存,除非您显式地调用
result.save()
。但同样,如果请求是并行生成的,则相同文档的
result.save()
可以同时发生。result对象只是一个包含我的数据的普通对象。调用
result.save()