MongoDB笔记

82 阅读7分钟

MongoDB笔记

最近在整理mongodb相关的知识,正好写一篇文章。主要介绍mongodb的结构,索引相关的理论。

MongoDB架构

+---------------------+
| 客户端请求入口        | ← 客户端连接(TCP/IP)
+---------------------++---------------------+
| 网络接口层            | ← 处理连接池、协议解析(如BSON)
+---------------------++---------------------+
| 查询处理器            | ← 解析命令(CRUD、聚合等)
| - 查询优化器          | ← 生成执行计划,选择索引
| - 聚合管道引擎        | ← 处理$match、$group等阶段
+---------------------++---------------------+
| 存储引擎接口层         | ← 抽象层(支持WiredTiger、In-Memory等引擎)
+---------------------++---------------------+
| 存储引擎(WiredTiger) | ← 实际数据管理
| - 缓存管理(Cache)    | ← 数据内存缓存(LRU策略)
| - 事务控制            | ← ACID事务支持
| - 压缩与加密          | ← 数据压缩(Snappy/Zlib)和加密
| - 日志系统(Journal)  | ← 预写日志(WAL)确保崩溃恢复
+---------------------++---------------------+
| 数据文件系统层         | ← 持久化存储(.wt文件)
+---------------------+

应用通过提供的mongodb-driver客户端连接到mongodb,传输bson数据。mongodb接收到请求后,解析查询语句,分析索引,调用存储引擎查询数据。

查询过程如下:

  • 将查询条件bson构建为语法树(query_tree),然后将操作符展开,如填充$eq , 将and条件展开等等

    // 原始查询
    { age: { $gt: 18 }, status: "active" }
    
    // 内部解析为
    { $and: [ { age: { $gt: 18 } }, { status: { $eq: "active" } } ] }
    
  • 查询预估与优化,然后索引选择。

    db.collection.find({ a: 5, b: { $lt: 10 } }).explain("executionStats") 通过 queryPlanner 分析不同索引的执行成本(如扫描文档数、索引键大小)
    
  • 存储引擎处理。

  • 内存筛选,如果无法直接通过索引判断数据是否符合,则需要加载到内存中进一步判断。

    // 索引无法直接处理正则表达式(除非左锚定)
    { name: { $regex: /^Al/ } }  // 可以使用索引
    { name: { $regex: /son$/ } } // 无法使用索引,触发全集合扫描
    
  • 游标生成与projection,返回匹配的文档的游标,可以通过batchSize分批获取。如果设置了project,会在这一步剔除字段。

关键特性

  • 缓存配置:

    # mongod.conf
    storage:
      wiredTiger:
        engineConfig:
          cacheSizeGB: 2  # 默认占用内存的50%
    
  • 监控指标:

    • ​db.serverStatus().wiredTiger.cache​ 查看缓存命中率。
    • 监控锁竞争: db.currentOp({ "locks": { $exists: true } })
    • 压缩效率: db.collection.stats().wiredTiger["block-manager"]["bytes compressed"]

存储引擎

默认为WiredTiger。

  • 核心特性:

    • 文档级并发控制:通过 MVCC(多版本并发控制)实现读写操作的并发执行。
    • 压缩算法:支持 Snappy(默认)和 Zlib 压缩,减少磁盘占用。
    • 事务支持:提供多文档 ACID 事务(4.0+ 版本)。
    • 内存缓存:使用 LRU 缓存管理热数据,通过 wiredTigerCacheSizeGB​ 配置。
    • 索引结构:基于 B-Tree 和 LSM Tree(针对写入优化场景)。
  • 适用场景:生产环境通用型引擎,适合高并发读写和事务需求。

  • 配置示例

    # mongod.conf
    storage:
      engine: wiredTiger
      wiredTiger:
        engineConfig:
          cacheSizeGB: 8
          journalCompressor: snappy
        collectionConfig:
          blockCompressor: zlib  # 集合数据压缩算法
        indexConfig:
          prefixCompression: true  # 索引前缀压缩
    

MongoDB使用命令

具体各个操作命令的使用可以看这篇文章。

golang操作mongodb:juejin.cn/post/690806…

操作符

操作符是mongodb中处理数据的行为,类似于一个个方法,不同的操作符作用与不同的阶段,可以发挥不同的作用。

  • 查询操作符:www.mongodb.com/zh-cn/docs/…
  • 更新操作符:www.mongodb.com/zh-cn/docs/…
  • 聚合操作符:www.mongodb.com/zh-cn/docs/…
  • 维度查询操作符更新操作符聚合操作符
    执行阶段数据读取阶段数据写入阶段数据处理阶段
    作用对象文档集合单文档或批量文档文档流(Pipeline)
    核心目标筛选目标文档修改文档内容或结构数据转换与计算
    原子性单文档级别原子性无(管道整体可事务化)
    性能影响依赖索引优化写锁竞争优化内存与管道阶段优化
    典型场景条件查询字段级增量更新OLAP 分析、数据清洗

MongoDB索引

mongodb的集合可以设置索引,加快数据查找。索引的使用与关系型数据库的索引知识一样,遵循的范式也类似。

操作符类型索引支持情况优化建议
等式操作符eq,eq​, in​ 可高效使用索引优先作为复合索引的前缀字段
范围操作符gt,gt​, lt​, gte,gte​, lte​ 可使用索引,但复合索引中需放在等式字段之后避免范围查询字段在复合索引的前列
逻辑操作符and​可通过复合索引优化;and​ 可通过复合索引优化;or​ 可能导致多个索引扫描对 $or​ 子句分别建索引
数组操作符elemMatch​可使用多键索引;elemMatch​ 可使用多键索引;size​ 无法使用索引对数组字段建多键索引,避免 $size​ 全扫描
正则表达式左锚定正则(如 /^prefix/​)可使用索引,其他情况触发全扫描优先使用左锚定正则表达式
地理空间操作符必须依赖 2dsphere​ 或 2d​ 索引确保地理字段有对应索引

MongoDB 默认使用 B树(B-Tree) 作为索引的底层存储结构,而非 B+树.(这是官网的说法,也有人说是B+树)。

为什么使用B树?由于关系型数据库和非关系型数据的设计方式上的不同。在关系型数据中,遍历操作比较常见,因此采用B+树作为索引比较合适。而在非关系型数据库中,单一查询比较常见,因此采用B树作为索引比较合适。B树是平衡树并且有序,树的节点存有数据key与对应data指针,可以快速查找到具体的值,适合非关系型数据库面向点对点的查找场景。

关系型数据库会使用B+树而不是B树,因为B+树在B树的基础上,只有叶子节点存数据并将叶子节点通过链表串起来,这种结构设计适合关系型数据库对范围查询的场景。

索引使用

db.collection.createIndex({ field: 1 }, { options })

db.collection.dropIndex("index_name")

db.collection.reIndex({ name: "index_name" }) 注意事项:重建期间会阻塞写入操作,建议在低峰期执行

查看集合所有索引
db.collection.getIndexes()

db.collection.find(query).explain("executionStats")
- queryPlanner(默认):展示索引选择逻辑。
- executionStats:统计实际执行耗时和扫描文档数。
- allPlansExecution:对比不同候选索引的执行情况。

索引优化

  • 数组元素的快速查找:$elemetMatch (可以触发索引扫描,但是仍然需要二次过滤)

    // 正确用法 
    db.test.find({ scores: { $elemMatch: { $gt: 10, $lt: 20 } } })
    
    // 错误用法(会匹配不同元素分别满足条件的文档)
    db.test.find({ scores: { $gt: 10, $lt: 20 } })
    
  • 高效覆盖索引

    // 低效写法(可能触发全表扫描)
    db.users.find({ age: { $ne: null } })
    
    // 高效替代(明确要求字段存在)
    db.users.find({ age: { $exists: true, $ne: null } })
    
  • 分页查询使用_id区分,避免skip的性能问题

    // 第一页
    const firstPage = db.logs.find().sort({ _id: 1 }).limit(100);
    const lastId = firstPage[99]._id;
    
    // 下一页
    const nextPage = db.logs.find({ _id: { $gt: lastId } }).sort({ _id: 1 }).limit(100);
    

MongoDB事务

Mongodb支持事务操作,在批处理数据的时候能够形成原子操作。并且,在mongodb集群中,也支持分布式事务。

ACID 支持:

  • 原子性(Atomicity):事务内的操作要么全部成功,要么全部回滚。
  • 一致性(Consistency):事务保证数据从一种有效状态转换到另一种有效状态。
  • 隔离性(Isolation):默认隔离级别为 快照隔离(Snapshot Isolation),避免脏读和不可重复读。
  • 持久性(Durability):提交后的事务数据持久化到磁盘。

事务流程

  • 创建session

  • 设置读写关注(read concern , write concern)

    配置一致性可用性适用场景
    ​readConcern: "local"​实时性要求高,容忍脏读
    ​writeConcern: { w: 1 }​快速写入,允许数据短暂丢失
    ​readConcern: "majority"​强一致性场景
  • 提交或者回滚

事务使用

事务监控

查看活跃事务
db.currentOp({ "lsid": { $exists: true } })

事务统计
db.serverStatus().transactions

MongoDB集群模式

mongodb以扩展能力强著称,可以配置副本集(replica)集群模式支持高可用,也可以使用分片集群模式(sharding)支持海量数据的分片存储。

  • Replica

    • 由一主(Primary)、多从(Secondary)和可选仲裁节点(Arbiter)组成,节点数建议为奇数以支持自动选举。
    • 各个节点之间数据一致。
    • 主节点异常时,副本集可以通过选举,得到新的主节点,实现高可用。
  • Sharding

    • 由分片服务器(Shard)、配置服务器(Config Server)和路由服务器(mongos)组成。
    • shard存储实际数据,每个分片通常是一个副本集,避免单点故障。
    • config server 存储集群元数据(如分片键范围),需部署为副本集以确保元数据安全
    • mogons 无状态组件,负责将客户端请求路由到对应分片,并聚合结果
    • 客户端 → mongos路由层  
                      ↓  
      配置服务器(副本集,存储元数据)  
                      ↓  
      分片1(副本集:Primary + Secondary + Arbiter)  
      分片2(副本集:Primary + Secondary + Arbiter)  
      ...其他分片
      
  • 集群模式下的核心机制

    • 通过副本集之间的选举兜底机制,实现高可用
    • oplog, 主节点记录所有写操作,从节点异步拉取并重放日志,保证最终一致性
    • 读写关注(Read/Write Concern):可配置数据写入的确认级别(如“多数节点确认”),平衡性能与一致性

参考

www.geeksforgeeks.org/mongodb-arc…

附录

B树与B+树

B树

          [根节点]
        /    |     \
      10    30     50
     /     / \      \
 [叶]   [叶][叶]   [叶]
  • 每个节点存储键值(Key)与数据指针(Data Pointer)
  • 叶子节点和非叶子节点均可能包含数据
  • 所有叶子节点位于同一层级(平衡树)

B+树

          [根节点]
        /    |     \
      10    30     50
     /     / \      \
 [叶][叶][叶][叶]→...
  • 非叶子节点仅存储键值(不存数据指针)
  • 所有数据存储在叶子节点,并通过指针串联成链表
  • 叶子节点包含全量键值副本