在MongoDB中,deleteOne和deleteMany是两种不同的删除操作,主要区别在于删除的文档数量和操作行为,deleteOne删除第一个匹配查询条件的文档(即使有多个文档匹配,也只删一个),deleteMany()功能:删除所有匹配查询条件的文档。

本文以deleteOne举例子,先插入一条记录,再删除这条记录,具体例子代码如下:

> db.user.insert({
...     "name": "alice",
...     "age": 2828
... });
WriteResult({ "nInserted" : 1 })
>

user表根据字段age=2828进行删除,后面会详细分析deleteOne代码执行过程和执行原理。

db.user.deleteOne({  "age" : 2828});

db.user.deleteOne({  "age" : 2828})我们都知道deleteOne需要两步,先查询,再删除。具体是咋么查询的?怎么删除的呢?先通过explain()进行分析:

db.runCommand({
  explain: {
    delete: "user",       // 集合名
    deletes: [{
      q: { age: 2828 },   // 查询条件
      limit: 1            // 限制删除1条(即 deleteOne)
    }]
  },
  verbosity: "executionStats" // 详细模式(可选:queryPlanner | executionStats | allPlansExecution)
})

db.user.deleteOne({  "age" : 2828})的explain()输出结果如下,deleteOne执行的策略是DELETE>FETCH>IXSCAN,先按照FETCH>IXSCAN读取数据,再根据DELETE删除,具体详细数据如下:

 "winningPlan" : {
                        "stage" : "DELETE",
                        "inputStage" : {
                                "stage" : "FETCH",
                                "inputStage" : {
                                        "stage" : "IXSCAN",
                                        "keyPattern" : {
                                                "age" : 1
                                        },
                                        "indexName" : "age_1",
                                        "isMultiKey" : false,
                                        "multiKeyPaths" : {
                                                "age" : [ ]
                                        },
                                        "isUnique" : false,
                                        "isSparse" : false,
                                        "isPartial" : false,
                                        "indexVersion" : 2,
                                        "direction" : "forward",
                                        "indexBounds" : {
                                                "age" : [
                                                        "[2828.0, 2828.0]"
                                                ]
                                        }
                                }
                        }
                },

MongoDB源码delete分析CmdDelete过程核心代码调用链如下:

  1. mongo/db/commands/write_commands/write_commands.cpp的CmdDelete类
  2. mongo/db/commands/write_commands/write_commands.cpp的CmdDelete:runImpl
  3. mongo/db/ops/write_ops_exec.cpp中的performDeletes
  4. mongo/db/ops/write_ops_exec.cpp中的performSingleDeleteOp
  5. mongo/db/ops/write_ops_exec.cpp中的getExecutorDelete
  6.     mongo/db/query/get_executor.cpp中的getExecutorDelete,返回<PlanExecutor, PlanExecutor::Deleter>
  7.     mongo/db/query/get_executor.cpp中的prepareExecution,返回PrepareExecutionResult
  8.         mongo/db/query/query_planner.cpp中的QueryPlanner::plan,返回QuerySolution对象
  9.     mongo/db/query/get_executor.cpp中的std::make_unique<DeleteStage>
  10.     mongo/db/query/get_executor.cpp中的PlanExecutor::make,返回<PlanExecutor, PlanExecutor::Deleter
  11. mongo/db/ops/write_ops_exec.cpp中的exec->executePlan()
  12. mongo/db/query/plan_executor_impl.cpp中的executePlan
  13. mongo/db/query/plan_executor_impl.cpp中的getNext
  14. mongo/db/query/plan_executor_impl.cpp中的_getNextImpl

CmdDelete是delete命令的入口类,mongo/db/commands/write_commands/write_commands.cpp的CmdDelete具体代码如下:

class CmdDelete final : public WriteCommand {
public:
    CmdDelete() : WriteCommand("delete") {}

private:
    class Invocation final : public InvocationBase {
    public:
        Invocation(const WriteCommand* cmd, const OpMsgRequest& request)
            : InvocationBase(cmd, request), _batch(DeleteOp::parse(request)) {}

    private:
        NamespaceString ns() const override {
            return _batch.getNamespace();
        }

        void doCheckAuthorizationImpl(AuthorizationSession* authzSession) const override {
            auth::checkAuthForDeleteCommand(authzSession, getBypass(), _batch);
        }

        void runImpl(OperationContext* opCtx, BSONObjBuilder& result) const override {
            auto reply = performDeletes(opCtx, _batch);
            serializeReply(opCtx,
                           ReplyStyle::kNotUpdate,
                           !_batch.getWriteCommandBase().getOrdered(),
                           _batch.getDeletes().size(),
                           std::move(reply),
                           &result);
        }
...
    std::string help() const final {
        return "delete documents";
    }
} cmdDelete;

delete数据删除通过命令处理模块中的CmdDelete::runImpl(...) ->performDeletes接口完成和write写模块delete操作对接,下面我们分析该接口核心代码实现,如下:

WriteResult performDeletes(OperationContext* opCtx, const write_ops::Delete& wholeOp) {
  ...

    for (auto&& singleOp : wholeOp.getDeletes()) {
        const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++);
       ...

        // TODO: don't create nested CurOp for legacy writes.
        // Add Command pointer to the nested CurOp.
        auto& parentCurOp = *CurOp::get(opCtx);
        const Command* cmd = parentCurOp.getCommand();
        CurOp curOp(opCtx);
       ...
        
        try {
            lastOpFixer.startingOp();
            out.results.emplace_back(
                performSingleDeleteOp(opCtx, wholeOp.getNamespace(), stmtId, singleOp));
            lastOpFixer.finishedOpSuccessfully();
        } catch (const DBException& ex) {
           ...
        }
    }

从上面代码分析可以看出,如果wholeOp携带有多个DeleteOpEntry(也就是singleOp )操作,则循环对singleOp 进行处理,这个处理过程由performSingleDeleteOp(...)接口实现。

mongo/db/ops/write_ops_exec.cpp中的performSingleDeleteOp代码如下:

static SingleWriteResult performSingleDeleteOp(OperationContext* opCtx,
                                               const NamespaceString& ns,
                                               StmtId stmtId,
                                               const write_ops::DeleteOpEntry& op) {
    uassert(ErrorCodes::InvalidOptions,
            "Cannot use (or request) retryable writes with limit=0",
            opCtx->inMultiDocumentTransaction() || !opCtx->getTxnNumber() || !op.getMulti());

    globalOpCounters.gotDelete();
    ServerWriteConcernMetrics::get(opCtx)->recordWriteConcernForDelete(opCtx->getWriteConcern());
    auto& curOp = *CurOp::get(opCtx);
    {
        stdx::lock_guard<Client> lk(*opCtx->getClient());
        curOp.setNS_inlock(ns.ns());
        curOp.setNetworkOp_inlock(dbDelete);
        curOp.setLogicalOp_inlock(LogicalOp::opDelete);
        curOp.setOpDescription_inlock(op.toBSON());
        curOp.ensureStarted();
    }

    DeleteRequest request(ns);
    request.setQuery(op.getQ());
    request.setCollation(write_ops::collationOf(op));
    request.setMulti(op.getMulti());
    request.setYieldPolicy(opCtx->inMultiDocumentTransaction() ? PlanExecutor::INTERRUPT_ONLY
                                                               : PlanExecutor::YIELD_AUTO);
    request.setStmtId(stmtId);

    ParsedDelete parsedDelete(opCtx, &request);
    uassertStatusOK(parsedDelete.parseRequest());

    CurOpFailpointHelpers::waitWhileFailPointEnabled(
        &hangDuringBatchRemove,
        opCtx,
        "hangDuringBatchRemove",
        []() {
            log() << "batch remove - hangDuringBatchRemove fail point enabled. Blocking "
                     "until fail point is disabled.";
        },
        true  // Check for interrupt periodically.
    );
    if (MONGO_unlikely(failAllRemoves.shouldFail())) {
        uasserted(ErrorCodes::InternalError, "failAllRemoves failpoint active!");
    }

    AutoGetCollection collection(opCtx, ns, fixLockModeForSystemDotViewsChanges(ns, MODE_IX));

    if (collection.getDb()) {
        curOp.raiseDbProfileLevel(collection.getDb()->getProfilingLevel());
    }

    assertCanWrite_inlock(opCtx, ns, collection.getCollection());

    CurOpFailpointHelpers::waitWhileFailPointEnabled(
        &hangWithLockDuringBatchRemove, opCtx, "hangWithLockDuringBatchRemove");

    auto exec = uassertStatusOK(getExecutorDelete(opCtx,
                                                  &curOp.debug(),
                                                  collection.getCollection(),
                                                  &parsedDelete,
                                                  boost::none /* verbosity */));

    {
        stdx::lock_guard<Client> lk(*opCtx->getClient());
        CurOp::get(opCtx)->setPlanSummary_inlock(Explain::getPlanSummary(exec.get()));
    }

    uassertStatusOK(exec->executePlan());
    long long n = DeleteStage::getNumDeleted(*exec);
    curOp.debug().additiveMetrics.ndeleted = n;

    PlanSummaryStats summary;
    Explain::getSummaryStats(*exec, &summary);
    if (collection.getCollection()) {
        CollectionQueryInfo::get(collection.getCollection()).notifyOfQuery(opCtx, summary);
    }
    curOp.debug().setPlanSummaryMetrics(summary);

    if (curOp.shouldDBProfile()) {
        BSONObjBuilder execStatsBob;
        Explain::getWinningPlanStats(exec.get(), &execStatsBob);
        curOp.debug().execStats = execStatsBob.obj();
    }

    LastError::get(opCtx->getClient()).recordDelete(n);

    SingleWriteResult result;
    result.setN(n);
    return result;
}

mongo/db/ops/write_ops_exec.cpp中的performSingleDeleteOp该接口最核心:(1)获取delete执行器并运行getExecutorDelete (2)执行删除动作exec->executePlan()

getExecutorDelete 执行器由query查询引擎模块实现,因此getExecutorDelete(...)获取delete执行器及其运行过程具体实现流程,具体代码如下:

StatusWith<unique_ptr<PlanExecutor, PlanExecutor::Deleter>> getExecutorDelete(
    OperationContext* opCtx,
    OpDebug* opDebug,
    Collection* collection,
    ParsedDelete* parsedDelete,
    boost::optional<ExplainOptions::Verbosity> verbosity) {
    const DeleteRequest* request = parsedDelete->getRequest();

   ...
    const size_t defaultPlannerOptions = 0;
    StatusWith<PrepareExecutionResult> executionResult =
        prepareExecution(opCtx, collection, ws.get(), std::move(cq), defaultPlannerOptions);
    if (!executionResult.isOK()) {
        return executionResult.getStatus();
    }
    cq = std::move(executionResult.getValue().canonicalQuery);
    unique_ptr<QuerySolution> querySolution = std::move(executionResult.getValue().querySolution);
    unique_ptr<PlanStage> root = std::move(executionResult.getValue().root);

    deleteStageParams->canonicalQuery = cq.get();

    invariant(root);
    root = std::make_unique<DeleteStage>(
        opCtx, std::move(deleteStageParams), ws.get(), collection, root.release());

    if (!request->getProj().isEmpty()) {
        invariant(request->shouldReturnDeleted());

        const bool allowPositional = true;
        StatusWith<unique_ptr<PlanStage>> projStatus = applyProjection(
            opCtx, nss, cq.get(), request->getProj(), allowPositional, ws.get(), std::move(root));
        if (!projStatus.isOK()) {
            return projStatus.getStatus();
        }
        root = std::move(projStatus.getValue());
    }

    // We must have a tree of stages in order to have a valid plan executor, but the query
    // solution may be null.
    return PlanExecutor::make(std::move(cq),
                              std::move(ws),
                              std::move(root),
                              collection,
                              policy,
                              NamespaceString(),
                              std::move(querySolution));
}

mongo/db/query/get_executor.cpp中的prepareExecution根据sql命令生成PrepareExecutionResult,具体过程参照《mongod源代码Find的QueryPlanner::plan解析出来1个执行计划案例流程分析》、《mongodb源代码Find的QueryPlanner::plan解析出来2个执行计划案例流程分析》、《mongodb源代码Find的QueryPlanner::plan多个执行计划选择pickBestPlan高评分策略》详细分析。

mongo/db/query/get_executor.cpp中的prepareExecution会调用生成计划方法mongo/db/query/query_planner.cpp中的QueryPlanner::plan,详细过程参照上面3篇文章,这里简单说明sql获取执行计划步骤:

db.user.deleteOne({  "age" : 2828})命令中关键参数,过滤器,排序,返回字段打印如下:

=============================
Options = INDEX_INTERSECTION SPLIT_LIMITED_SORT
Canonical query:
ns=db.userTree: age $eq 2828.0
Sort: {}
Proj: {}
=============================

QueryPlanner::plan步骤2:遍历user表的所有索引,打印结果:_id是系统默认字段,字段age和wages手动增加的字段索引;

QueryPlanner::plan步骤3:db.user.deleteOne({  "age" : 2828})命令查询哪些字段设置了索引,打印结果是:过滤器字段age设置了索引;

QueryPlanner::plan步骤4:db.user.deleteOne({  "age" : 2828})命令判断是否有空间索引(gps),结果是没有,不生成计划;

QueryPlanner::plan步骤5:db.user.deleteOne({  "age" : 2828})命令判断是否有TEXT索引,结果是没有,不生成计划;

QueryPlanner::plan步骤6:db.user.deleteOne({  "age" : 2828})命令如果过滤器有索引字段,给age索引生成对应的执行计划

QueryPlanner::plan步骤7:db.user.deleteOne({  "age" : 2828})如果没有排序,不用生成新的索引计划。

QueryPlanner::plan步骤8:db.user.deleteOne({  "age" : 2828})如果执行返回字段,给Proj字段生成执行计划,这个sql没有proj字段,所以没有生成新的计划。

QueryPlanner::plan步骤9:db.user.deleteOne({  "age" : 2828})命令;如果前面没有生成执行计划,则生成默认的全表扫描计划COLLSCAN,前面步骤6生成了age索引执行计划,所以这里不会生成COLLSCAN全表扫描计划了。

db.user.deleteOne({  "age" : 2828})命令生成的查询计划如下:

FETCH
---fetched = 1
---sortedByDiskLoc = 1
---getSort = [{ age: 1 }, ]
---Child:
------IXSCAN
---------indexName = age_1
keyPattern = { age: 1.0 }
---------direction = 1
---------bounds = field #0['age']: [2828.0, 2828.0]
---------fetched = 0
---------sortedByDiskLoc = 1
---------getSort = [{ age: 1 }, ]

mongo/db/query/query_planner.cpp中的QueryPlanner::plan生成了一个执行计:FETCH>IXSCAN。

mongo/db/query/get_executor.cpp中的getExecutorDelete代码中:

 StatusWith<PrepareExecutionResult> executionResult =
        prepareExecution(opCtx, collection, ws.get(), std::move(cq), defaultPlannerOptions);
...
 cq = std::move(executionResult.getValue().canonicalQuery);
    unique_ptr<QuerySolution> querySolution = std::move(executionResult.getValue().querySolution);
    unique_ptr<PlanStage> root = std::move(executionResult.getValue().root);

    deleteStageParams->canonicalQuery = cq.get();

    invariant(root);
    root = std::make_unique<DeleteStage>(
        opCtx, std::move(deleteStageParams), ws.get(), collection, root.release());
...

生成新的std::make_unique<DeleteStage>(opCtx, std::move(deleteStageParams), ws.get(), collection, root.release()),DeleteStage类源代码如下,把【FETCH>IXSCAN】作为儿子策略放入到DeleteStage里面。

DeleteStage::DeleteStage(OperationContext* opCtx,
                         std::unique_ptr<DeleteStageParams> params,
                         WorkingSet* ws,
                         Collection* collection,
                         PlanStage* child)
    : RequiresMutableCollectionStage(kStageType, opCtx, collection),
      _params(std::move(params)),
      _ws(ws),
      _idRetrying(WorkingSet::INVALID_ID),
      _idReturning(WorkingSet::INVALID_ID) {
    _children.emplace_back(child);
}

最后执行计划会变成DELETE>FETCH>IXSCAN,mongo/db/query/get_executor.cpp中的PlanExecutor::make返回执行计划对应的策略对象,和前面db.user.deleteOne({  "age" : 2828})的explain()输出结果对应上了。

mongo/db/ops/write_ops_exec.cpp中的performSingleDeleteOp获取到执行器【DELETE>FETCH>IXSCAN】,会继续调用exec->executePlan()执行具体逻辑,这半部分放到下一遍文档分析。

db.user.deleteOne({  "age" : 2828})命令执行日志,能明显看到先query后delete。

2025-06-21T20:31:02.473+0800 I  WRITE    [conn1] remove db.user appName: "MongoDB Shell" command: { q: { age: 2828.0 }, limit: 1 } planSummary: IXSCAN { age: 1 } keysExamined:0 docsExamined:0 ndeleted:0 numYields:0 queryHash:3838C5F3 planCacheKey:041C5DE3 locks:{ ParallelBatchWriterMode: { acquireCount: { r: 1 } }, ReplicationStateTransition: { acquireCount: { w: 1 } }, Global: { acquireCount: { w: 1 } }, Database: { acquireCount: { w: 1 } }, Collection: { acquireCount: { w: 1 } }, Mutex: { acquireCount: { r: 1 } } } flowControl:{ acquireCount: 1 } storage:{} 42ms
2025-06-21T20:31:02.474+0800 D2 REPL     [conn1] Waiting for write concern. OpTime: { ts: Timestamp(0, 0), t: -1 }, write concern: { w: 1, wtimeout: 0 }
2025-06-21T20:31:02.474+0800 D1 -        [conn1] conca responseLength=45
2025-06-21T20:31:02.476+0800 D1 -        [conn1] conca serverGlobalParams.slowMS=100
2025-06-21T20:31:02.482+0800 D1 -        [conn1] conca slowMsOverride=(nothing)
2025-06-21T20:31:02.483+0800 D1 -        [conn1] conca _debug.executionTimeMicros=55732
2025-06-21T20:31:02.483+0800 D1 -        [conn1] conca serverGlobalParams.sampleRate=1
2025-06-21T20:31:02.484+0800 D1 -        [conn1] conca shouldSample=1
2025-06-21T20:31:02.485+0800 I  COMMAND  [conn1] command db.$cmd appName: "MongoDB Shell" command: delete { delete: "user", ordered: true, lsid: { id: UUID("ee77104e-ad36-470d-b691-7081dd8a547a") }, $db: "db" } numYields:0 reslen:45 locks:{ ParallelBatchWriterMode: { acquireCount: { r: 2 } }, ReplicationStateTransition: { acquireCount: { w: 2 } }, Global: { acquireCount: { r: 1, w: 1 } }, Database: { acquireCount: { w: 1 } }, Collection: { acquireCount: { w: 1 } }, Mutex: { acquireCount: { r: 1 } } } flowControl:{ acquireCount: 1 } storage:{} protocol:op_msg 55ms

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐