MongoDB源码delete分析CmdDelete过程
本文详细分析了MongoDB中deleteOne操作的执行过程。通过代码示例展示了deleteOne删除单条记录的基本用法,并使用explain()解析其执行计划为DELETE>FETCH>IXSCAN的三阶段流程。文章深入源码层面,梳理了从CmdDelete入口到具体删除操作的调用链,包括prepareExecution生成查询计划、getExecutorDelete获取执行器、以及
在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过程核心代码调用链如下:
- mongo/db/commands/write_commands/write_commands.cpp的CmdDelete类
- mongo/db/commands/write_commands/write_commands.cpp的CmdDelete:runImpl
- mongo/db/ops/write_ops_exec.cpp中的performDeletes
- mongo/db/ops/write_ops_exec.cpp中的performSingleDeleteOp
- mongo/db/ops/write_ops_exec.cpp中的getExecutorDelete
- mongo/db/query/get_executor.cpp中的getExecutorDelete,返回<PlanExecutor, PlanExecutor::Deleter>
- mongo/db/query/get_executor.cpp中的prepareExecution,返回PrepareExecutionResult
- mongo/db/query/query_planner.cpp中的QueryPlanner::plan,返回QuerySolution对象
- mongo/db/query/get_executor.cpp中的std::make_unique<DeleteStage>
- mongo/db/query/get_executor.cpp中的PlanExecutor::make,返回<PlanExecutor, PlanExecutor::Deleter
- mongo/db/ops/write_ops_exec.cpp中的exec->executePlan()
- mongo/db/query/plan_executor_impl.cpp中的executePlan
- mongo/db/query/plan_executor_impl.cpp中的getNext
- 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
更多推荐


所有评论(0)