写过 MongoDB 的人对 db.users.find({age: {$gt: 18}}) 都很熟,但很少有人能说清这条查询从发出到返回,中间到底走了几段路。在接口层,它就是「查文档」;可一旦你要排查「为什么这条查询慢」,就必须知道它卡在链路的哪一段。
这一篇不展开索引和优化器的细节(那是后面几篇的主线),而是把一条 find 的完整执行路径铺开。后面再看索引、事务、复制时,才知道这些机制分别站在链路的什么位置。
先把机制边界说清楚
一条 find 在 MongoDB 里不是被一个黑盒直接执行,而是经过五段:driver 序列化、网络层鉴权、解析与投影、优化器选计划、执行计划调用 WiredTiger。其中优化器决定走 IXSCAN(索引扫描)还是 COLLSCAN(全表扫描),是查询性能的分水岭。
写操作(insert/update/delete)的链路在前四段几乎一样,差别在第五段:写操作要同时维护集合页、索引页、oplog 和 journal,所以写比读贵,也比读更容易抖动。
整体路径
上面这张图先把主线铺开:driver 把查询序列化成 BSON 发给 mongod,网络层完成鉴权和请求入队,解析层把查询转成内部表示并提取投影/排序/limit,优化器在多个候选计划里选成本最低的,执行计划按计划调用 WiredTiger 访问 B-tree 索引和集合页,最后把结果组装返回。
读 MongoDB 这类系统,最重要的是别只停在命令接口,要继续追问它在存储引擎里访问了多少页、在主路径上走多远、结果集有多大。
读路径:五段路逐段拆
1. driver 层。 应用调用 find,driver 把查询条件、投影、排序序列化成 BSON,走 Wire Protocol 的 opMsg 发给 mongod。这一段的成本主要在序列化和网络往返,连接池配置不当会让请求排队,看起来像「MongoDB 慢」。
2. 网络层。 mongod 收到请求后做鉴权(SCRAM)、检查连接状态、把请求交给工作线程。高并发下这一段会出现线程竞争,mongostat 里的 queuedReaders 就是这里。
3. 解析层。 把 BSON 查询条件转成内部查询对象,提取投影(只要哪些字段)、排序、limit、skip。解析本身不贵,但投影会影响后续结果组装的成本。
4. 优化器。 这是读路径的核心。优化器根据查询条件、可用索引、统计信息,在多个候选计划里选一个。如果没有合适索引,被迫走 COLLSCAN——这就是全表扫描,文档越多越慢。优化器有时会同时跑多个候选计划,看谁先返回一批结果(trial period),再选定。
5. 执行计划 + WiredTiger。 选定计划后,执行器按计划调用 WiredTiger:先在 B-tree 索引里定位(IXSCAN),再根据需要回集合页读完整文档(FETCH)。这一段的成本取决于索引是否命中、数据是否在 Cache、要读多少页。每读一批文档,执行器会 yield(让出),避免长查询占住资源。
写路径:读路径之外的四本账
写操作前四段和读几乎一样,但第五段要同时维护四样东西,这是写比读贵的根本原因:
- 集合页:文档本身的存储位置。
- 索引页:每个被影响的索引都要更新。索引越多,写越贵。
- oplog:复制集的复制日志,每个写都要记一条,供 Secondary 拉取。
- journal:WiredTiger 的预写日志,保证崩溃可恢复。
这就是为什么 MongoDB 的写入延迟和「索引数量」「是否开启复制」「journal 刷盘策略」强相关。一个有 10 个索引的集合,每次写要做 11 次页修改;一个复制集节点,每次写还要追加 oplog 和 journal。写关注(writeConcern: {w: "majority"})会让写延迟再翻一倍,因为它要等多数节点确认。
取舍与边界
这套链路的短板是问题会跨段传播:
- driver 连接池小 → 请求在客户端就排队,mongod 根本没收到。
- 优化器选错计划 → 本该走索引的走了
COLLSCAN,CPU 飙升。 - WiredTiger Cache 不足 → 索引页和数据页频繁换入换出,磁盘 IO 上升。
- 结果集太大 → 即使查询很快,网络回传和应用反序列化也会拖慢整体。
- 写入抖动 → 多半是 checkpoint、journal 刷盘或 oplog 追加在某个时刻集中发生。
版本演进上,MongoDB 4.4 引入了 hedged reads(同时发到两个节点读)、5.0 改进了优化器的稳定性(减少计划抖动)、7.x 持续优化时间序列和列压缩。但「driver → mongod → WiredTiger」这条五段链路的骨架始终稳定。
典型问题:用机制化例子排查
可以用一个机制化例子理解:一条 find 看起来简单,但耗时可能来自 driver 往返、请求排队、优化器选错索引、WiredTiger 读盘、大结果集回传。排查时先把链路拆开,比直接怀疑「MongoDB 慢」更可靠。
可以落到这些动作:
- 排查慢查询先用
explain("executionStats")看走的是IXSCAN还是COLLSCAN,看totalKeysExamined和totalDocsExamined。 - 用
mongostat看queuedReaders/queuedWriters,判断瓶颈在网络层还是执行层。 - 用
db.currentOp()看长查询,结合 WiredTiger 的 cache 使用率判断是否内存抖动。 - 写抖动要看 journal 刷盘间隔(
storage.journal.commitIntervalMs,默认 100ms)和 checkpoint 频率。 - 区分「查询慢」和「结果回传慢」——加
limit和投影往往比加索引更直接。
收束:链路是排障的总地图
一条查询的执行链路,是 MongoDB 排障的总地图。先看清 driver、mongod 五段路和 WiredTiger 访问的分工,再谈索引、事务、复制,才不会在黑盒外面绕圈。下一篇会沿着优化器这条线深入,拆索引结构。
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 MongoDB」,欢迎关注公众号 「十三Tech」。后续会按文档模型、索引优化、存储引擎、高可用和分片集群这条线更新。

