MongoDB核心技术深度解析题

目录

  • [1. MongoDB概述与核心概念](#1. MongoDB概述与核心概念)
  • [2. 数据模型与BSON数据结构](#2. 数据模型与BSON数据结构)
  • [3. WiredTiger存储引擎深度剖析](#3. WiredTiger存储引擎深度剖析)
  • [4. 索引原理与优化策略](#4. 索引原理与优化策略)
  • [5. 复制集架构与高可用](#5. 复制集架构与高可用)
  • [6. 分片集群架构](#6. 分片集群架构)
  • [7. 事务与一致性保证](#7. 事务与一致性保证)
  • [8. 查询执行引擎](#8. 查询执行引擎)
  • [9. Java集成与最佳实践](#9. Java集成与最佳实践)
  • [10. 生产环境优化建议](#10. 生产环境优化建议)

1. MongoDB概述与核心概念

1.1 什么是MongoDB

MongoDB是一个基于文档的分布式NoSQL数据库,由C++语言编写。它诞生于2009年,名字来源于"Humongous"(巨大的),寓意能够处理海量数据。

核心特性

  • 面向文档:数据以JSON风格的文档形式存储(BSON格式)
  • Schema灵活:无需预先定义表结构,同一集合的文档可以有不同字段
  • 水平扩展:原生支持分片(Sharding),轻松应对PB级数据
  • 高可用:通过副本集(Replica Set)实现自动故障转移
  • 丰富查询:支持聚合管道、地理空间查询、全文检索
  • ACID事务:从4.0版本开始支持多文档事务

1.2 MongoDB与关系型数据库对比

概念 MongoDB 关系型数据库(MySQL)
数据模型 文档(Document) 行(Row)
集合 Collection Table
字段 Field Column
主键 _id(自动生成ObjectId) Primary Key
关联 嵌入文档或引用 JOIN
Schema 动态Schema 固定Schema
扩展方式 水平扩展(Sharding) 垂直扩展为主

1.3 适用场景

适合MongoDB的场景

  1. 内容管理系统(CMS):文章、博客、评论等非结构化数据
  2. 实时分析:日志收集、用户行为分析
  3. 物联网(IoT):传感器数据、时序数据
  4. 电商系统:商品目录、购物车、订单(字段频繁变化)
  5. 社交网络:用户资料、动态、好友关系
  6. 大数据存储:需要水平扩展的海量数据

不适合MongoDB的场景

  1. 需要复杂多表JOIN的业务
  2. 需要严格ACID事务的金融核心系统(虽然支持事务,但性能不如关系型数据库)
  3. 数据结构高度规范化的场景

1.4 核心术语

MongoDB实例 Database 数据库 Collection 集合1 Collection 集合2 Document 文档1 Document 文档2 Document 文档3 Field: value Field: value Embedded Document

Document(文档)

  • MongoDB的基本数据单元,相当于关系型数据库的一行
  • 采用BSON格式(Binary JSON)
  • 文档大小限制:16MB

Collection(集合)

  • 文档的集合,相当于关系型数据库的表
  • 无需预定义Schema
  • 同一集合的文档可以有完全不同的结构

Database(数据库)

  • 集合的容器
  • 每个数据库有独立的文件存储
  • 系统保留数据库:admin、local、config

2. 数据模型与BSON数据结构

2.1 BSON数据格式

BSON(Binary JSON)是MongoDB存储数据的二进制编码格式,相比JSON有以下优势:

BSON vs JSON

特性 BSON JSON
编码方式 二进制 文本
空间效率 较低(包含类型和长度信息) 较高
解析速度 快(可跳过不需要的字段) 慢(需要完整解析)
数据类型 支持Date、Binary、ObjectId等 仅支持基本类型
可读性 不可读 可读

BSON支持的数据类型

javascript 复制代码
{
    // 基本类型
    "string": "Hello MongoDB",
    "integer": 42,
    "double": 3.14159,
    "boolean": true,
    "null": null,

    // MongoDB特有类型
    "_id": ObjectId("507f1f77bcf86cd799439011"),  // 12字节唯一标识符
    "date": ISODate("2025-12-22T10:30:00Z"),      // 日期时间
    "timestamp": Timestamp(1640000000, 1),         // 时间戳(用于内部)

    // 复合类型
    "array": [1, 2, 3, "mixed"],
    "embedded_doc": {
        "nested_field": "value"
    },

    // 特殊类型
    "binary": BinData(0, "base64data"),            // 二进制数据
    "regex": /pattern/i,                           // 正则表达式
    "code": function() { return 1; },              // JavaScript代码
    "minKey": MinKey(),                            // 最小值
    "maxKey": MaxKey()                             // 最大值
}

2.2 ObjectId详解

ObjectId是MongoDB默认的主键类型,12字节(96位)结构如下:

复制代码
ObjectId = 4字节时间戳 + 5字节随机值 + 3字节计数器

[   4字节   ][     5字节      ][  3字节  ]
[ Timestamp ][ Random Value   ][ Counter ]
[ 时间戳    ][ 机器ID+进程ID  ][ 计数器  ]

结构分解

  1. 时间戳(4字节):Unix时间戳(秒),表示ObjectId的创建时间
  2. 随机值(5字节):每个进程生成一次,保证不同机器/进程的唯一性
  3. 计数器(3字节):同一秒内生成的ObjectId递增,初始值随机

特性

  • 全局唯一:无需中心化ID生成器,分布式环境下不会冲突
  • 包含时间信息:可以从ObjectId中提取创建时间
  • 有序性:ObjectId大致按时间递增(不严格单调)
  • 性能高:本地生成,无需网络调用

Java代码示例

java 复制代码
import org.bson.types.ObjectId;
import java.util.Date;

// 生成ObjectId
ObjectId id = new ObjectId();

// 获取时间戳
Date timestamp = id.getDate();

// 从字符串解析
ObjectId parsed = new ObjectId("507f1f77bcf86cd799439011");

// 比较大小(可用于范围查询)
ObjectId id1 = new ObjectId();
ObjectId id2 = new ObjectId();
boolean isLater = id2.compareTo(id1) > 0;  // id2更晚创建

2.3 数据建模策略

MongoDB的数据建模核心在于权衡嵌入(Embedding)和引用(Referencing)

2.3.1 嵌入式文档(Embedding)

适用场景

  • 一对一关系
  • 一对少量关系(1:N,N < 100)
  • 数据经常一起读取
  • 数据更新频率低

示例:用户与地址

javascript 复制代码
{
    "_id": ObjectId("..."),
    "username": "zhangsan",
    "email": "zhangsan@example.com",
    "addresses": [                    // 嵌入式数组
        {
            "type": "home",
            "province": "广东省",
            "city": "深圳市",
            "detail": "南山区科技园"
        },
        {
            "type": "work",
            "province": "广东省",
            "city": "深圳市",
            "detail": "福田区CBD"
        }
    ],
    "created_at": ISODate("2025-01-01T00:00:00Z")
}

优势

  • ✅ 单次查询获取所有数据(无需JOIN)
  • ✅ 原子性更新(单文档事务)
  • ✅ 更好的读性能

劣势

  • ❌ 文档大小限制16MB
  • ❌ 嵌入数据过多导致文档膨胀
  • ❌ 嵌入数据重复存储
2.3.2 引用式文档(Referencing)

适用场景

  • 一对多关系(N很大)
  • 多对多关系
  • 数据独立更新频繁
  • 数据在多处被引用

示例:博客文章与评论

javascript 复制代码
// 文章集合(posts)
{
    "_id": ObjectId("post001"),
    "title": "MongoDB深度解析",
    "author": "张三",
    "content": "...",
    "created_at": ISODate("2025-12-01T00:00:00Z")
}

// 评论集合(comments)
{
    "_id": ObjectId("comment001"),
    "post_id": ObjectId("post001"),    // 引用文章ID
    "user_id": ObjectId("user001"),    // 引用用户ID
    "content": "写得真好!",
    "created_at": ISODate("2025-12-02T10:00:00Z")
}

优势

  • ✅ 文档大小可控
  • ✅ 数据不重复
  • ✅ 更新更灵活

劣势

  • ❌ 需要多次查询(类似JOIN)
  • ❌ 没有外键约束,需应用层维护一致性
2.3.3 混合模式

实战案例:电商订单建模

javascript 复制代码
{
    "_id": ObjectId("order001"),
    "order_no": "2025122201001",

    // 冗余用户核心信息(嵌入)
    "user": {
        "user_id": ObjectId("user001"),
        "username": "张三",
        "mobile": "13800138000"
    },

    // 收货地址(嵌入)
    "shipping_address": {
        "province": "广东省",
        "city": "深圳市",
        "detail": "南山区科技园",
        "mobile": "13800138000"
    },

    // 订单商品(嵌入快照)
    "items": [
        {
            "product_id": ObjectId("prod001"),
            "product_name": "MacBook Pro",    // 商品快照
            "price": 12999.00,                // 下单时价格
            "quantity": 1
        }
    ],

    "total_amount": 12999.00,
    "status": "pending",                       // 待支付
    "created_at": ISODate("2025-12-22T10:30:00Z")
}

设计原则

  1. 冗余热数据:订单保存商品快照,避免商品信息变更影响历史订单
  2. 引用主数据:保留product_id,方便关联查询最新商品信息
  3. 嵌入小集合:收货地址直接嵌入,避免额外查询

2.4 文档设计最佳实践

规则1:根据查询模式设计Schema

javascript 复制代码
// ❌ 错误:商品评论单独存储,查询商品需要JOIN
// products collection
{ "_id": 1, "name": "iPhone" }

// reviews collection
{ "product_id": 1, "content": "很好用" }

// ✅ 正确:嵌入少量评论,支持单次查询
{
    "_id": 1,
    "name": "iPhone",
    "reviews": [
        { "user": "张三", "content": "很好用", "rating": 5 }
    ]
}

规则2:避免文档无限增长

javascript 复制代码
// ❌ 错误:日志直接追加到用户文档,导致文档无限膨胀
{
    "_id": ObjectId("user001"),
    "username": "zhangsan",
    "logs": [                      // 可能增长到数万条
        { "action": "login", "time": "..." },
        { "action": "view", "time": "..." },
        // ... 数万条日志
    ]
}

// ✅ 正确:日志独立存储,按用户ID索引
// logs collection
{
    "_id": ObjectId("..."),
    "user_id": ObjectId("user001"),
    "action": "login",
    "created_at": ISODate("...")
}

规则3:合理使用数组

  • ✅ 数组元素 < 100时可以嵌入
  • ✅ 使用$slice限制返回的数组元素数量
  • ❌ 避免数组元素超过1000(性能急剧下降)

3. WiredTiger存储引擎深度剖析

3.1 存储引擎概述

MongoDB 3.2版本开始使用WiredTiger作为默认存储引擎,取代了早期的MMAPv1。WiredTiger是一个开源的、高性能的存储引擎,专为现代硬件优化。

WiredTiger核心特性

  1. 文档级并发控制(MVCC):支持无锁读写
  2. 数据压缩:支持Snappy、zlib、zstd压缩算法
  3. Checkpoint机制:定期持久化数据,保证崩溃恢复
  4. Journal日志:Write-Ahead Logging(WAL),保证数据不丢失
  5. 缓存管理:独立的缓存层,默认使用50%系统内存

3.2 存储架构

持久化存储 WiredTiger引擎 应用层 Data Files
collection-*.wt Index Files
index-*.wt Journal Files
WiredTigerLog.* Metadata
WiredTiger.wt Cache 缓存层
默认50%系统内存 Eviction 淘汰线程 Checkpoint 检查点线程
每60秒 Journal 日志系统
WAL预写日志 MongoDB Query Layer

3.3 MVCC多版本并发控制

核心思想

  • 每个事务看到的是数据的一个快照(Snapshot)
  • 读操作不阻塞写操作,写操作不阻塞读操作
  • 通过事务ID和版本号实现隔离

数据结构

可见性判断

python 复制代码
# 伪代码:判断某个版本是否对当前事务可见
def is_visible(doc_version, current_txn):
    if doc_version.txn_id <= current_txn.snapshot_id:
        if doc_version.txn_id not in current_txn.active_txns:
            return True
    return False

优势

  • ✅ 高并发:读写互不阻塞
  • ✅ 一致性读:事务看到的是一致的快照
  • ✅ 回滚快:直接丢弃新版本,恢复旧版本

劣势

  • ❌ 空间开销:需要保留历史版本
  • ❌ 清理成本:需要定期清理过期版本

3.4 Checkpoint机制

Checkpoint定义

将内存中的脏数据(Dirty Pages)批量刷入磁盘的过程,生成数据文件的一致性快照。

触发条件

  1. 定时触发:默认每60秒执行一次
  2. 日志大小触发:Journal日志超过2GB
  3. 手动触发 :执行db.fsyncLock()命令

执行流程
应用 WiredTiger Cache Checkpoint线程 磁盘Data Files 写入数据(更新缓存) 缓存中累积脏页 标记Checkpoint开始点 收集需要刷盘的脏页 后台异步写入 不阻塞读写 批量写入Data Files 更新Metadata元数据 生成一致性快照 loop [每60秒] Checkpoint完成 可安全删除旧Journal 应用 WiredTiger Cache Checkpoint线程 磁盘Data Files

Checkpoint与崩溃恢复

复制代码
时间轴:
T0: Checkpoint 1 完成
T1: 写入数据A(Journal记录)
T2: 写入数据B(Journal记录)
T3: Checkpoint 2 开始(标记T3时刻)
T4: 写入数据C(Journal记录)
T5: 【系统崩溃】
T6: 重启恢复

恢复过程:
1. 从Checkpoint 2快照恢复(包含T0-T3的数据)
2. 重放Journal日志(T3-T5的数据A、B、C)
3. 丢弃未提交的事务

3.5 Journal预写日志

Journal作用

  • 保证数据持久性(Durability)
  • 崩溃后快速恢复
  • 避免Checkpoint期间的数据丢失

写入流程
应用写入 WiredTiger Cache Journal Buffer Journal Files Data Files 1. 写入缓存 2. 写入Journal Buffer 每100ms或缓存满 3. fsync刷盘(持久化) ← 返回成功(保证不丢失) 后台Checkpoint 异步刷Data Files 4. 定期刷盘(每60秒) 应用写入 WiredTiger Cache Journal Buffer Journal Files Data Files

Journal配置

javascript 复制代码
// 启动时配置Journal
mongod --dbpath /data/db --journal                  // 启用Journal(默认)
mongod --dbpath /data/db --nojournal                // 禁用Journal(不推荐)

// 调整Journal刷盘频率
db.adminCommand({ setParameter: 1, journalCommitInterval: 100 })  // 默认100ms

性能权衡

配置 性能 持久性 适用场景
Journal启用 + 100ms刷盘 中等 高(最多丢失100ms数据) 生产环境(推荐)
Journal启用 + 每次写入刷盘 极高(零丢失) 金融系统
Journal禁用 低(可能丢失Checkpoint间隔数据) 开发测试环境

3.6 缓存管理

WiredTiger Cache配置

yaml 复制代码
# mongod.conf
storage:
  wiredTiger:
    engineConfig:
      cacheSizeGB: 4                     # 指定缓存大小(GB)
      # 默认值:(RAM - 1GB) * 0.5 或 256MB(取较大值)

缓存淘汰策略(Eviction)

复制代码
缓存状态:
├─ 使用率 < 80%:正常运行,无淘汰
├─ 使用率 80%-95%:后台淘汰线程工作
│   └─ 淘汰算法:LRU(Least Recently Used)
│       ├─ 优先淘汰:冷数据、已持久化的干净页
│       └─ 保留:热数据、脏页(需先写Journal)
└─ 使用率 > 95%:应用线程参与淘汰(性能下降)
    └─ 触发条件:高并发写入场景

脏页刷盘

python 复制代码
# 伪代码:脏页刷盘流程
def flush_dirty_pages():
    for page in cache.dirty_pages:
        if page.last_modify_time < (now - 60s):
            # 1. 确保Journal已写入
            wait_for_journal_sync(page)

            # 2. 写入Data Files
            disk.write(page)

            # 3. 标记为干净页
            page.mark_clean()

3.7 数据压缩

压缩算法对比

算法 压缩比 CPU消耗 速度 适用场景
Snappy 中等(3:1) 默认(推荐),均衡性能
zlib 高(5:1) 中等 存储敏感场景
zstd 很高(7:1) 中等 MongoDB 4.2+,最佳选择
无压缩 1:1 最快 CPU敏感场景

配置示例

javascript 复制代码
// 创建集合时指定压缩算法
db.createCollection("users", {
    storageEngine: {
        wiredTiger: {
            configString: "block_compressor=zstd"
        }
    }
})

// 索引压缩
db.collection.createIndex(
    { email: 1 },
    { storageEngine: { wiredTiger: { configString: "prefix_compression=true" } } }
)

压缩效果

复制代码
原始数据大小:100GB
├─ Snappy压缩:~33GB(节省67%)
├─ zlib压缩:~20GB(节省80%)
└─ zstd压缩:~14GB(节省86%)

查询性能影响:
├─ Snappy:几乎无影响(< 5%)
├─ zlib:轻微影响(~10%)
└─ zstd:轻微影响(~8%)

4. 索引原理与优化策略

4.1 B树索引结构

MongoDB使用B-Tree(平衡树)作为索引数据结构,与MySQL的B+Tree略有不同。

MongoDB B-Tree特点

  1. 数据存储在所有节点(非叶子节点也存数据)
  2. 支持双向遍历(叶子节点有双向指针)
  3. 键值对存储(索引键 → 文档指针)

B-Tree vs B+Tree

特性 MongoDB B-Tree MySQL B+Tree
数据存储位置 所有节点 仅叶子节点
叶子节点链表 双向链表 单向链表
范围查询效率 较低 高(叶子节点顺序扫描)
单点查询效率 高(可能提前终止) 中等(必须到叶子节点)

索引存储结构图

复制代码
                    [50]
                   /    \
              [20, 35]  [70, 90]
             /   |   \   /  |   \
          [10] [25] [40] [60] [80] [95]
            ↓    ↓    ↓    ↓    ↓    ↓
         Doc1 Doc2 Doc3 Doc4 Doc5 Doc6

每个节点存储:
[索引键, 文档位置指针]

4.2 索引类型

4.2.1 单字段索引(Single Field Index)
javascript 复制代码
// 创建单字段索引
db.users.createIndex({ email: 1 })          // 升序
db.users.createIndex({ created_at: -1 })    // 降序

// 使用索引查询
db.users.find({ email: "zhang@example.com" })

执行计划分析

javascript 复制代码
db.users.find({ email: "zhang@example.com" }).explain("executionStats")

// 输出关键指标
{
    "executionStats": {
        "executionSuccess": true,
        "nReturned": 1,                    // 返回文档数
        "executionTimeMillis": 2,          // 执行时间(毫秒)
        "totalKeysExamined": 1,            // 扫描索引键数
        "totalDocsExamined": 1,            // 扫描文档数
        "executionStages": {
            "stage": "FETCH",
            "inputStage": {
                "stage": "IXSCAN",         // 索引扫描
                "keyPattern": { "email": 1 },
                "indexName": "email_1"
            }
        }
    }
}
4.2.2 复合索引(Compound Index)

索引顺序的重要性

javascript 复制代码
// 创建复合索引:(status, created_at)
db.orders.createIndex({ status: 1, created_at: -1 })

// ✅ 可以使用索引的查询
db.orders.find({ status: "pending" })                               // 使用status前缀
db.orders.find({ status: "pending", created_at: { $gt: date } })    // 使用完整索引
db.orders.find({ status: "pending" }).sort({ created_at: -1 })      // 使用索引排序

// ❌ 无法使用索引的查询
db.orders.find({ created_at: { $gt: date } })                       // 跳过status前缀

最左前缀原则

复制代码
索引:(a, b, c)

支持的查询:
✅ WHERE a = 1
✅ WHERE a = 1 AND b = 2
✅ WHERE a = 1 AND b = 2 AND c = 3
✅ WHERE a = 1 AND c = 3          (部分使用,仅用到a)

不支持的查询:
❌ WHERE b = 2
❌ WHERE c = 3
❌ WHERE b = 2 AND c = 3

ESR规则(Equality, Sort, Range)

javascript 复制代码
// 最优索引设计:等值查询 → 排序字段 → 范围查询
// 查询:WHERE status = 'active' AND age > 18 ORDER BY created_at

// ❌ 错误顺序
db.users.createIndex({ age: 1, status: 1, created_at: -1 })

// ✅ 正确顺序(ESR)
db.users.createIndex({ status: 1, created_at: -1, age: 1 })
//                      ↑ Equality  ↑ Sort        ↑ Range
4.2.3 多键索引(Multikey Index)

数组字段索引

javascript 复制代码
// 文档结构
{
    "_id": 1,
    "tags": ["mongodb", "database", "nosql"]
}

// 创建多键索引
db.articles.createIndex({ tags: 1 })

// 查询(自动使用索引)
db.articles.find({ tags: "mongodb" })        // 匹配数组中的任意元素
db.articles.find({ tags: { $in: ["mongodb", "redis"] } })

多键索引限制

javascript 复制代码
// ❌ 禁止:同一个复合索引中包含两个数组字段
db.collection.createIndex({ tags: 1, categories: 1 })  // 报错

// ✅ 允许:一个数组字段 + 一个标量字段
db.collection.createIndex({ tags: 1, status: 1 })
4.2.4 文本索引(Text Index)
javascript 复制代码
// 创建文本索引(支持全文检索)
db.articles.createIndex({ title: "text", content: "text" })

// 全文搜索
db.articles.find({ $text: { $search: "mongodb database" } })

// 权重配置
db.articles.createIndex(
    { title: "text", content: "text" },
    { weights: { title: 10, content: 5 } }    // title权重更高
)

// 搜索结果评分
db.articles.find(
    { $text: { $search: "mongodb" } },
    { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
4.2.5 地理空间索引(Geospatial Index)

2dsphere索引(球面几何)

javascript 复制代码
// 创建地理空间索引
db.places.createIndex({ location: "2dsphere" })

// 文档结构(GeoJSON格式)
{
    "_id": 1,
    "name": "深圳市民中心",
    "location": {
        "type": "Point",
        "coordinates": [114.0579, 22.5455]    // [经度, 纬度]
    }
}

// 查询附近的地点(5公里内)
db.places.find({
    location: {
        $near: {
            $geometry: {
                "type": "Point",
                "coordinates": [114.06, 22.54]
            },
            $maxDistance: 5000    // 单位:米
        }
    }
})

// 查询多边形区域内的地点
db.places.find({
    location: {
        $geoWithin: {
            $geometry: {
                "type": "Polygon",
                "coordinates": [[
                    [114.0, 22.5],
                    [114.1, 22.5],
                    [114.1, 22.6],
                    [114.0, 22.6],
                    [114.0, 22.5]
                ]]
            }
        }
    }
})
4.2.6 部分索引(Partial Index)

仅索引符合条件的文档

javascript 复制代码
// 仅索引status='active'的用户
db.users.createIndex(
    { email: 1 },
    { partialFilterExpression: { status: "active" } }
)

// ✅ 使用索引
db.users.find({ email: "zhang@example.com", status: "active" })

// ❌ 不使用索引(缺少status条件)
db.users.find({ email: "zhang@example.com" })

优势

  • ✅ 节省存储空间(仅索引部分数据)
  • ✅ 提升索引维护性能
  • ✅ 适用于稀疏数据(如:仅索引未删除的文档)
4.2.7 唯一索引(Unique Index)
javascript 复制代码
// 创建唯一索引
db.users.createIndex({ email: 1 }, { unique: true })

// 插入重复数据会报错
db.users.insert({ email: "zhang@example.com" })    // 成功
db.users.insert({ email: "zhang@example.com" })    // E11000 duplicate key error

// 唯一复合索引
db.users.createIndex({ username: 1, tenant_id: 1 }, { unique: true })

注意事项

  • 唯一索引会拒绝重复值(包括null
  • 稀疏唯一索引:{ unique: true, sparse: true },允许多个文档缺少该字段

4.3 索引优化策略

4.3.1 覆盖索引(Covered Query)

定义:查询的所有字段都包含在索引中,无需回表查询文档。

javascript 复制代码
// 创建复合索引
db.users.createIndex({ username: 1, email: 1, status: 1 })

// ✅ 覆盖查询(仅从索引获取数据)
db.users.find(
    { username: "zhangsan" },
    { _id: 0, username: 1, email: 1, status: 1 }    // 投影仅包含索引字段
)

// 执行计划显示
{
    "executionStats": {
        "totalDocsExamined": 0,        // ← 未扫描任何文档(覆盖索引)
        "executionStages": {
            "stage": "PROJECTION_COVERED"
        }
    }
}

优化效果

  • 性能提升:3-10倍(避免磁盘随机IO)
  • 适用场景:高频查询的核心字段
4.3.2 索引选择性

选择性公式

复制代码
选择性 = 唯一值数量 / 文档总数

高选择性(好):email, mobile, order_no (接近1.0)
低选择性(差):status, gender, type (< 0.05)

实战案例

javascript 复制代码
// 用户表:100万文档
db.users.count()                              // 1000000
db.users.distinct("email").length             // 998000 (选择性:0.998)
db.users.distinct("status").length            // 3 (选择性:0.000003)

// ✅ 高选择性字段适合索引
db.users.createIndex({ email: 1 })

// ❌ 低选择性字段不适合单独索引
db.users.createIndex({ status: 1 })           // 浪费空间,效果差

// ✅ 低选择性字段放在复合索引后面
db.users.createIndex({ email: 1, status: 1 })
4.3.3 索引碎片整理

问题:频繁删除/更新导致索引碎片,影响性能。

javascript 复制代码
// 查看索引统计信息
db.users.stats().indexSizes
// 输出:{ "_id_": 10485760, "email_1": 5242880 }

// 重建索引(在线操作,MongoDB 4.2+)
db.users.reIndex()                            // 阻塞操作,不推荐生产环境

// 推荐:后台重建索引
db.users.dropIndex("email_1")
db.users.createIndex({ email: 1 }, { background: true })
4.3.4 索引提示(Index Hint)

强制使用指定索引

javascript 复制代码
// 查询优化器可能选错索引
db.orders.find({ user_id: 123, status: "pending" })

// 强制使用user_id索引
db.orders.find({ user_id: 123, status: "pending" }).hint({ user_id: 1 })

// 强制全表扫描(调试用)
db.orders.find({ user_id: 123 }).hint({ $natural: 1 })

4.4 索引设计反模式

❌ 反模式1:过度索引
javascript 复制代码
// 错误:为每个字段都创建索引
db.users.createIndex({ username: 1 })
db.users.createIndex({ email: 1 })
db.users.createIndex({ mobile: 1 })
db.users.createIndex({ status: 1 })
db.users.createIndex({ created_at: 1 })
// ... 10个索引

// 问题:
// 1. 写入性能下降(每次插入需要更新10个索引)
// 2. 占用大量存储空间
// 3. 查询优化器选择困难

// 正确做法:根据查询模式设计2-3个复合索引
db.users.createIndex({ email: 1 })                         // 登录查询
db.users.createIndex({ status: 1, created_at: -1 })        // 列表查询
❌ 反模式2:忽略索引顺序
javascript 复制代码
// 查询:WHERE status = 'active' AND age > 18 ORDER BY created_at DESC

// 错误索引
db.users.createIndex({ age: 1, status: 1, created_at: -1 })
// 问题:无法使用索引的status前缀

// 正确索引(ESR规则)
db.users.createIndex({ status: 1, created_at: -1, age: 1 })
❌ 反模式3:正则表达式前缀通配
javascript 复制代码
// ❌ 无法使用索引(前缀通配)
db.users.find({ username: /.*zhang/ })
db.users.find({ email: /$.*@example\.com/ })

// ✅ 可以使用索引(前缀匹配)
db.users.find({ username: /^zhang/ })
db.users.find({ email: /^zhang@example\.com/ })

5. 复制集架构与高可用

5.1 复制集概述

**Replica Set(复制集)**是MongoDB实现高可用的核心机制,通过数据冗余和自动故障转移保证服务不中断。

核心组件

节点类型

节点类型 功能 数量建议 说明
Primary 接收所有写操作,唯一的主节点 1个 自动选举产生
Secondary 复制主节点数据,可处理读请求 2个+ 可晋升为Primary
Arbiter 仅参与投票,不存储数据 0-1个 节省成本(不推荐)
Hidden 隐藏节点,不接受读请求 可选 用于备份或分析
Delayed 延迟复制节点 可选 防止误删除数据

5.2 Oplog复制机制

Oplog(Operations Log)

  • 存储在本地数据库local.oplog.rs集合中
  • 记录主节点的所有写操作
  • 固定大小的Capped Collection(循环覆盖)
  • 幂等性操作(可重复执行)

Oplog结构

javascript 复制代码
{
    "ts": Timestamp(1703001234, 1),    // 操作时间戳(全局唯一)
    "t": NumberLong(3),                // 选举任期号
    "h": NumberLong("..."),            // 操作哈希值
    "v": 2,                            // oplog版本
    "op": "i",                         // 操作类型:i=insert, u=update, d=delete
    "ns": "mydb.users",                // 命名空间(数据库.集合)
    "o": {                             // 操作内容
        "_id": ObjectId("..."),
        "username": "zhangsan",
        "email": "zhang@example.com"
    },
    "o2": {                            // 查询条件(仅update/delete)
        "_id": ObjectId("...")
    }
}

复制流程
客户端 Primary节点 Secondary节点 1. 写入请求 insertOne(doc) 2. 写入数据 写入本地oplog 3. 返回成功 WriteConcern=1 4. 拉取oplog getMore请求 5. 返回新oplog 6. 回放oplog 应用到本地数据 loop [异步复制(每2秒拉- 取)] 复制延迟:通常< 1秒 客户端 Primary节点 Secondary节点

Oplog大小配置

javascript 复制代码
// 查看Oplog大小
db.oplog.rs.stats().maxSize        // 默认:磁盘空间的5%

// 修改Oplog大小(需重启)
// mongod.conf
replication:
  oplogSizeMB: 10240               // 10GB

Oplog大小建议

复制代码
计算公式:
Oplog大小 >= 峰值写入速率 * 预期恢复时间

示例:
- 峰值写入:100MB/小时
- 预期恢复时间:24小时(一天)
- Oplog大小 >= 100 * 24 = 2.4GB

推荐值:
- 小型应用:1-5GB
- 中型应用:10-50GB
- 大型应用:100GB+

5.3 选举机制

选举触发条件

  1. 复制集初始化
  2. 主节点故障(心跳超时,默认10秒)
  3. 新节点加入集群
  4. 手动触发:rs.stepDown()

选举规则

复制代码
选举优先级(按顺序判断):

1. 节点优先级(priority)
   ├─ priority = 0:永不成为Primary
   └─ priority > 0:数值越大优先级越高

2. 数据新鲜度(oplog时间戳)
   └─ 数据越新越有可能当选

3. 多数派投票(Majority)
   └─ 得票数 > 总节点数 / 2

4. 网络延迟
   └─ 心跳延迟越低越有可能当选

选举示例

javascript 复制代码
// 配置节点优先级
rs.conf()
{
    "members": [
        { "_id": 0, "host": "mongo1:27017", "priority": 2 },    // 优先成为Primary
        { "_id": 1, "host": "mongo2:27017", "priority": 1 },
        { "_id": 2, "host": "mongo3:27017", "priority": 1 }
    ]
}

// 禁止节点成为Primary
rs.reconfig({
    "members": [
        { "_id": 0, "host": "mongo1:27017", "priority": 2 },
        { "_id": 1, "host": "mongo2:27017", "priority": 0 },    // 永不当选
        { "_id": 2, "host": "mongo3:27017", "priority": 1 }
    ]
})

选举时间线

复制代码
T0: Primary故障(网络断开)
T10s: Secondary节点检测到心跳超时
T11s: 发起选举投票
T12s: 收集多数派投票
T13s: 新Primary当选
T14s: 客户端重新连接新Primary

总耗时:约 10-15秒(不可用窗口)

5.4 读写关注

5.4.1 写关注(Write Concern)

定义:控制写操作的确认级别,权衡性能和数据安全性。

级别选项

Write Concern 含义 持久性 性能
w: 0 不等待确认(fire-and-forget) 最低 最高
w: 1 等待Primary确认(默认)
w: 2 等待1个Secondary确认
w: "majority" 等待多数节点确认
w: 3 等待2个Secondary确认 很高 很低

配置示例

java 复制代码
// Java Driver配置
MongoClientSettings settings = MongoClientSettings.builder()
    .applyConnectionString(new ConnectionString("mongodb://localhost:27017"))
    .writeConcern(WriteConcern.MAJORITY)    // 多数派写关注
    .build();

MongoClient mongoClient = MongoClients.create(settings);

// 单次操作覆盖
collection.insertOne(document, new InsertOneOptions()
    .writeConcern(WriteConcern.W2));        // 等待2个节点确认

与Journal的组合

java 复制代码
// w=1 + j=true:等待Primary写入Journal(推荐)
WriteConcern.JOURNALED

// w=majority + j=true:等待多数节点写入Journal(金融系统)
WriteConcern.MAJORITY.withJournal(true)
5.4.2 读关注(Read Concern)

定义:控制读取数据的一致性级别。

级别选项

Read Concern 含义 一致性 适用场景
local 读取本节点最新数据(默认) 最终一致 高性能场景
available 读取可用数据(可能未复制) 弱一致 分片集群
majority 读取多数节点确认的数据 强一致 事务、金融系统
linearizable 线性一致性读(串行化) 最强一致 分布式锁
snapshot 快照读(事务内) 事务隔离 多文档事务

示例

java 复制代码
// 设置读关注
collection.find()
    .readConcern(ReadConcern.MAJORITY)
    .into(new ArrayList<>());
5.4.3 读偏好(Read Preference)

定义:控制读请求路由到哪个节点。

模式选项

模式 行为 适用场景
primary 仅从Primary读(默认) 强一致性要求
primaryPreferred 优先Primary,不可用时读Secondary 高可用场景
secondary 仅从Secondary读 分离读写压力
secondaryPreferred 优先Secondary,不可用时读Primary 读多写少场景
nearest 读取网络延迟最低的节点 跨地域部署

Java配置

java 复制代码
// 配置读偏好
MongoClientSettings settings = MongoClientSettings.builder()
    .readPreference(ReadPreference.secondaryPreferred())
    .build();

// 单次查询覆盖
collection.find()
    .readPreference(ReadPreference.secondary())
    .into(new ArrayList<>());

// 带标签的读偏好(读指定机房的Secondary)
ReadPreference.secondary(
    TagSet.builder()
        .add("datacenter", "east")
        .build()
);

5.5 复制集部署实战

部署拓扑

初始化复制集

bash 复制代码
# 1. 启动3个MongoDB实例
mongod --replSet rs0 --port 27017 --dbpath /data/mongo1 --bind_ip 0.0.0.0
mongod --replSet rs0 --port 27018 --dbpath /data/mongo2 --bind_ip 0.0.0.0
mongod --replSet rs0 --port 27019 --dbpath /data/mongo3 --bind_ip 0.0.0.0

# 2. 连接到第一个节点
mongosh --port 27017

# 3. 初始化复制集
rs.initiate({
    _id: "rs0",
    members: [
        { _id: 0, host: "mongo1:27017", priority: 2 },
        { _id: 1, host: "mongo2:27018", priority: 1 },
        { _id: 2, host: "mongo3:27019", priority: 1 }
    ]
})

# 4. 检查状态
rs.status()

# 5. 添加新节点
rs.add("mongo4:27020")

# 6. 移除节点
rs.remove("mongo4:27020")

连接字符串

java 复制代码
// Java Driver连接复制集
String connectionString = "mongodb://mongo1:27017,mongo2:27018,mongo3:27019/"
    + "?replicaSet=rs0"
    + "&readPreference=secondaryPreferred"
    + "&w=majority";

MongoClient mongoClient = MongoClients.create(connectionString);

6. 分片集群架构

6.1 分片概述

**Sharding(分片)**是MongoDB实现水平扩展的机制,通过将数据分散到多个服务器(Shard)来突破单机存储和性能瓶颈。

适用场景

✅ 数据量超过单机存储能力(> 500GB)

✅ 读写QPS超过单机性能上限(> 10万/秒)

✅ 需要地理分布式部署

架构组件
分片层 配置服务器 路由层 客户端层 Shard 1
复制集
数据分片1 Shard 2
复制集
数据分片2 Shard 3
复制集
数据分片3 Config Server
复制集
存储元数据 Mongos
路由节点1 Mongos
路由节点2 应用1 应用2 应用3

组件职责

组件 职责 数量
Mongos 路由查询到对应分片,聚合结果 2个+(无状态,可横向扩展)
Config Server 存储集群元数据(分片键、Chunk分布) 3个复制集(CSRS)
Shard 存储实际数据,每个Shard是一个复制集 2个+(通常3-10个)

6.2 分片键选择

分片键(Shard Key):决定文档分布到哪个分片的字段,一旦设置不可更改。

选择原则

  1. 高基数(High Cardinality):唯一值数量多
  2. 低频率(Low Frequency):值分布均匀,避免热点
  3. 非单调递增(Non-Monotonic):避免所有写入集中在最后一个分片

常见分片键模式

模式1:哈希分片(Hashed Sharding)
javascript 复制代码
// 启用分片
sh.enableSharding("mydb")

// 创建哈希分片索引
sh.shardCollection("mydb.users", { _id: "hashed" })

特点

  • ✅ 数据分布均匀(哈希值随机分布)
  • ✅ 写入性能高(无热点)
  • ❌ 范围查询性能差(需要scatter-gather)
  • ❌ 无法利用排序(哈希后无序)

适用场景

  • 写多读少的日志系统
  • 没有明显范围查询需求的场景
模式2:范围分片(Range Sharding)
javascript 复制代码
// 使用user_id作为分片键
sh.shardCollection("mydb.orders", { user_id: 1 })

特点

  • ✅ 范围查询性能好(数据连续存储)
  • ✅ 支持排序优化
  • ❌ 容易产生热点(如时间戳递增)
  • ❌ 需要选择合适的分片键

适用场景

  • 有明确范围查询需求
  • 数据有自然分组(如地域、租户)
模式3:复合分片键
javascript 复制代码
// 组合分片键:tenant_id + created_at
sh.shardCollection("mydb.logs", { tenant_id: 1, created_at: 1 })

特点

  • ✅ 第一个字段保证数据分组(租户隔离)
  • ✅ 第二个字段支持时间范围查询
  • ✅ 避免单一维度热点

适用场景

  • SaaS多租户系统
  • 需要租户隔离且有时间查询的场景

6.3 Chunk分裂与均衡

Chunk(块)

  • MongoDB将分片键空间划分为多个Chunk
  • 默认大小:128MB(MongoDB 6.0+,早期版本64MB)
  • 每个Chunk存储一个范围的分片键值

Chunk分裂流程

复制代码
初始状态(单Shard):
Shard1: [minKey ────────────────────────────────────── maxKey]

写入数据后Chunk增长:
Shard1: [minKey ─────────────────── Chunk(150MB) ─────────────── maxKey]
                                        ↓ 超过阈值,触发分裂

分裂后:
Shard1: [minKey ─── Chunk1(75MB) ─── Chunk2(75MB) ─── maxKey]

均衡器工作(迁移Chunk):
Shard1: [minKey ─── Chunk1(75MB) ─── maxKey]
Shard2: [minKey ─── Chunk2(75MB) ─── maxKey]

均衡器(Balancer)

javascript 复制代码
// 查看均衡器状态
sh.getBalancerState()

// 启用/禁用均衡器
sh.setBalancerState(true)
sh.setBalancerState(false)

// 设置均衡窗口(仅在低峰期运行)
db.settings.update(
    { _id: "balancer" },
    { $set: {
        activeWindow: {
            start: "23:00",    // 晚上11点开始
            stop: "06:00"      // 早上6点结束
        }
    }},
    { upsert: true }
)

6.4 查询路由

Mongos路由逻辑
应用 Mongos路由 Config Server Shard 1 Shard 2 查询请求 find({user_id: 123}) 获取Chunk分布 user_id=123在Shard1 路由到Shard1 返回结果 需要查询所有分片 并行查询 并行查询 结果1 结果2 聚合、排序、合并 alt [包含分片键(Targeted Query)] [不包含分片键(Scatter-Gather)] 返回最终结果 应用 Mongos路由 Config Server Shard 1 Shard 2

查询性能对比

查询类型 是否包含分片键 路由方式 性能
精确查询 ✅ 包含 Targeted(单分片)
范围查询 ✅ 包含 Targeted(部分分片)
全表扫描 ❌ 不包含 Scatter-Gather(所有分片)

优化建议

javascript 复制代码
// ✅ 好:包含分片键,路由到单个分片
db.orders.find({ user_id: 123, status: "pending" })

// ❌ 差:不包含分片键,广播到所有分片
db.orders.find({ status: "pending" })

// ✅ 好:聚合操作下推到各分片
db.orders.aggregate([
    { $match: { user_id: 123 } },        // 路由到单分片
    { $group: { _id: "$status", count: { $sum: 1 } } }
])

6.5 分片集群部署

部署架构(最小配置)

复制代码
Config Server 复制集(3节点):
- config1:27019
- config2:27019
- config3:27019

Shard 1 复制集(3节点):
- shard1-1:27018
- shard1-2:27018
- shard1-3:27018

Shard 2 复制集(3节点):
- shard2-1:27018
- shard2-2:27018
- shard2-3:27018

Mongos 路由(2节点):
- mongos1:27017
- mongos2:27017

初始化脚本

bash 复制代码
# 1. 启动Config Server复制集
mongod --configsvr --replSet configRS --port 27019 --dbpath /data/config1
# ... 启动config2, config3

# 初始化Config Server
mongosh --port 27019
rs.initiate({
    _id: "configRS",
    configsvr: true,
    members: [
        { _id: 0, host: "config1:27019" },
        { _id: 1, host: "config2:27019" },
        { _id: 2, host: "config3:27019" }
    ]
})

# 2. 启动Shard复制集
mongod --shardsvr --replSet shard1RS --port 27018 --dbpath /data/shard1-1
# ... 启动其他节点并初始化

# 3. 启动Mongos
mongos --configdb configRS/config1:27019,config2:27019,config3:27019 --port 27017

# 4. 添加分片
mongosh --port 27017
sh.addShard("shard1RS/shard1-1:27018,shard1-2:27018,shard1-3:27018")
sh.addShard("shard2RS/shard2-1:27018,shard2-2:27018,shard2-3:27018")

# 5. 启用分片
sh.enableSharding("mydb")
sh.shardCollection("mydb.users", { _id: "hashed" })

7. 事务与一致性保证

7.1 事务支持演进

MongoDB事务发展历程

版本 事务支持 说明
< 4.0 单文档原子性 仅支持单个文档的原子操作
4.0 (2018) 复制集多文档事务 支持单个复制集内的多文档事务
4.2 (2019) 分片集群事务 支持跨分片的分布式事务
5.0+ 改进性能 优化事务性能,降低锁粒度

7.2 单文档原子性

原子操作示例

javascript 复制代码
// ✅ 单文档更新是原子的
db.users.updateOne(
    { _id: ObjectId("...") },
    {
        $set: { status: "active" },
        $inc: { login_count: 1 },
        $currentDate: { last_login: true }
    }
)

// ✅ 嵌入式文档更新是原子的
db.orders.updateOne(
    { _id: ObjectId("...") },
    {
        $push: { items: { product_id: 123, quantity: 2 } },
        $inc: { total_amount: 199.00 }
    }
)

7.3 多文档事务

基本用法

java 复制代码
// Java Driver事务示例
try (ClientSession session = mongoClient.startSession()) {
    session.startTransaction();

    try {
        // 1. 扣减库存
        collection.updateOne(
            session,
            eq("_id", productId),
            inc("stock", -1)
        );

        // 2. 创建订单
        collection.insertOne(
            session,
            new Document("order_no", orderNo)
                .append("product_id", productId)
                .append("status", "pending")
        );

        // 3. 提交事务
        session.commitTransaction();

    } catch (Exception e) {
        // 4. 回滚事务
        session.abortTransaction();
        throw e;
    }
}

事务选项

java 复制代码
TransactionOptions options = TransactionOptions.builder()
    .readConcern(ReadConcern.SNAPSHOT)           // 快照隔离
    .writeConcern(WriteConcern.MAJORITY)         // 多数派写
    .readPreference(ReadPreference.primary())    // 从主节点读
    .maxCommitTime(60L, TimeUnit.SECONDS)        // 最大提交时间
    .build();

session.startTransaction(options);

7.4 事务隔离级别

MongoDB事务隔离

复制代码
Snapshot Isolation(快照隔离):
- 事务开始时创建一个数据快照
- 事务内的所有读操作看到同一快照
- 写操作对其他事务不可见,直到提交

避免的异常:
✅ 脏读(Dirty Read):不会读到未提交的数据
✅ 不可重复读(Non-Repeatable Read):同一事务内重复读取结果一致
✅ 幻读(Phantom Read):同一事务内范围查询结果一致

不保证:
❌ 写偏序(Write Skew):需要应用层处理

示例:转账事务

java 复制代码
// 转账事务(避免脏读、幻读)
try (ClientSession session = mongoClient.startSession()) {
    session.startTransaction(TransactionOptions.builder()
        .readConcern(ReadConcern.SNAPSHOT)
        .writeConcern(WriteConcern.MAJORITY)
        .build());

    try {
        // 1. 读取账户余额(快照读)
        Document fromAccount = accounts.find(session, eq("user_id", fromUser)).first();
        Document toAccount = accounts.find(session, eq("user_id", toUser)).first();

        if (fromAccount.getDouble("balance") < amount) {
            throw new RuntimeException("余额不足");
        }

        // 2. 扣减转出账户
        accounts.updateOne(
            session,
            eq("user_id", fromUser),
            inc("balance", -amount)
        );

        // 3. 增加转入账户
        accounts.updateOne(
            session,
            eq("user_id", toUser),
            inc("balance", amount)
        );

        // 4. 提交事务
        session.commitTransaction();

    } catch (Exception e) {
        session.abortTransaction();
        throw e;
    }
}

7.5 事务性能优化

最佳实践

  1. 减少事务范围
java 复制代码
// ❌ 错误:事务包含非必要操作
session.startTransaction();
// 查询用户信息(耗时)
// 调用外部API(耗时)
// 更新订单
session.commitTransaction();

// ✅ 正确:仅包含必须原子执行的操作
// 先查询和调用API
session.startTransaction();
// 仅更新订单
session.commitTransaction();
  1. 避免长事务
java 复制代码
// 事务超时时间:默认60秒
// 超时后自动回滚
TransactionOptions.builder()
    .maxCommitTime(10L, TimeUnit.SECONDS)    // 设置10秒超时
    .build();
  1. 批量操作
java 复制代码
// ✅ 使用批量操作减少往返次数
List<WriteModel<Document>> writes = Arrays.asList(
    new UpdateOneModel<>(eq("_id", 1), inc("stock", -1)),
    new UpdateOneModel<>(eq("_id", 2), inc("stock", -2))
);
collection.bulkWrite(session, writes);
  1. 处理冲突重试
java 复制代码
// 事务冲突时自动重试
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
    try {
        session.startTransaction();
        // 事务操作...
        session.commitTransaction();
        break;    // 成功则跳出
    } catch (MongoCommandException e) {
        if (e.hasErrorLabel("TransientTransactionError") && i < maxRetries - 1) {
            continue;    // 瞬态错误,重试
        }
        throw e;
    }
}

7.6 分布式事务原理

两阶段提交(Two-Phase Commit)
客户端 协调者 (Primary Shard) Shard 1 Shard 2 开始事务 Phase 1: Prepare阶段 Prepare请求 Prepare请求 Vote: Yes(已准备好) Vote: Yes(已准备好) Phase 2: Commit阶段 Commit请求 Commit请求 Committed Committed 事务成功 客户端 协调者 (Primary Shard) Shard 1 Shard 2

失败恢复

复制代码
场景1:Shard1 Prepare失败
└─> Coordinator发送Abort到所有Shard
    └─> 事务回滚

场景2:Shard1 Commit超时
└─> Coordinator重试Commit
    └─> 幂等性保证不会重复提交

场景3:Coordinator崩溃
└─> 新Coordinator读取事务日志
    └─> 继续完成未完成的事务

8. 查询执行引擎

8.1 查询优化器

MongoDB查询优化器(Query Optimizer)

  • 基于成本的优化器(Cost-Based Optimizer)
  • 自动选择最优执行计划
  • 支持多种执行策略(索引扫描、全表扫描、Covered Query等)

执行计划生成流程

复制代码
1. 解析查询(Parse Query)
   └─> 生成查询树(Query Tree)

2. 生成候选执行计划(Candidate Plans)
   ├─> 计划1:使用索引A
   ├─> 计划2:使用索引B
   └─> 计划3:全表扫描

3. 试运行(Trial Run)
   └─> 执行前N个文档(默认101个)
   └─> 计算每个计划的成本

4. 选择最优计划
   └─> 缓存执行计划(Plan Cache)

5. 执行查询
   └─> 返回结果

8.2 Explain分析

Explain模式

模式 说明 输出内容
queryPlanner 仅返回执行计划(不执行) 索引选择、查询策略
executionStats 执行查询并返回统计信息 扫描文档数、耗时
allPlansExecution 返回所有候选计划的统计 所有计划的性能对比

实战案例

javascript 复制代码
// 查询语句
db.users.find({ status: "active", age: { $gt: 18 } })
    .sort({ created_at: -1 })
    .limit(10)
    .explain("executionStats")

// 输出分析(简化版)
{
    "executionStats": {
        "executionSuccess": true,
        "nReturned": 10,                   // 返回文档数
        "executionTimeMillis": 15,         // 执行时间(毫秒)
        "totalKeysExamined": 150,          // 扫描索引键数
        "totalDocsExamined": 150,          // 扫描文档数

        "executionStages": {
            "stage": "LIMIT",              // 第4步:限制返回10条
            "inputStage": {
                "stage": "FETCH",          // 第3步:回表获取文档
                "inputStage": {
                    "stage": "IXSCAN",     // 第1步:索引扫描
                    "keyPattern": { "status": 1, "created_at": -1 },
                    "indexName": "status_1_created_at_-1",
                    "direction": "backward" // 逆序扫描(利用索引排序)
                }
            }
        }
    }
}

关键指标解读

复制代码
性能评估公式:

1. 索引效率 = nReturned / totalKeysExamined
   ├─> 理想值:1.0(每扫描1个索引键返回1个文档)
   └─> 警戒值:< 0.1(扫描了太多无效索引键)

2. 回表效率 = nReturned / totalDocsExamined
   ├─> 理想值:1.0(每扫描1个文档返回1个结果)
   └─> 警戒值:< 0.5(扫描了太多无效文档)

3. 执行阶段(stage):
   ├─> IXSCAN:索引扫描(好)
   ├─> FETCH:回表查询(正常)
   ├─> COLLSCAN:全表扫描(差,需要加索引)
   ├─> SORT:内存排序(差,应使用索引排序)
   └─> PROJECTION_COVERED:覆盖索引(最好)

8.3 聚合管道

Aggregation Pipeline(聚合管道)

  • MongoDB的数据分析框架
  • 类似Unix管道:db.collection | $match | $group | $sort
  • 支持复杂的数据转换和统计

核心操作符

操作符 功能 类比SQL
$match 过滤文档 WHERE
$project 投影字段 SELECT
$group 分组聚合 GROUP BY
$sort 排序 ORDER BY
$limit 限制结果数 LIMIT
$skip 跳过文档 OFFSET
$lookup 关联查询 LEFT JOIN
$unwind 展开数组 UNNEST

实战案例:订单统计

javascript 复制代码
db.orders.aggregate([
    // 1. 过滤:仅统计已完成订单
    {
        $match: {
            status: "completed",
            created_at: { $gte: ISODate("2025-01-01") }
        }
    },

    // 2. 展开:订单商品数组
    {
        $unwind: "$items"
    },

    // 3. 分组:按商品ID统计销量
    {
        $group: {
            _id: "$items.product_id",
            total_quantity: { $sum: "$items.quantity" },
            total_revenue: { $sum: "$items.amount" },
            order_count: { $sum: 1 }
        }
    },

    // 4. 排序:按销量降序
    {
        $sort: { total_quantity: -1 }
    },

    // 5. 限制:Top 10
    {
        $limit: 10
    },

    // 6. 关联:获取商品名称
    {
        $lookup: {
            from: "products",
            localField: "_id",
            foreignField: "_id",
            as: "product_info"
        }
    },

    // 7. 投影:格式化输出
    {
        $project: {
            _id: 0,
            product_id: "$_id",
            product_name: { $arrayElemAt: ["$product_info.name", 0] },
            total_quantity: 1,
            total_revenue: 1,
            order_count: 1
        }
    }
])

输出结果

json 复制代码
[
    {
        "product_id": ObjectId("..."),
        "product_name": "iPhone 15 Pro",
        "total_quantity": 1234,
        "total_revenue": 12340000.00,
        "order_count": 1100
    },
    ...
]

8.4 聚合管道优化

优化规则

  1. 提前过滤(Early Filtering)
javascript 复制代码
// ❌ 错误:在最后过滤
db.orders.aggregate([
    { $group: { _id: "$user_id", total: { $sum: "$amount" } } },
    { $match: { total: { $gt: 1000 } } }    // 已经聚合了所有数据
])

// ✅ 正确:尽早过滤
db.orders.aggregate([
    { $match: { status: "completed" } },    // 先过滤
    { $group: { _id: "$user_id", total: { $sum: "$amount" } } },
    { $match: { total: { $gt: 1000 } } }
])
  1. 下推索引(Index Push-down)
javascript 复制代码
// MongoDB自动优化:将$match下推到索引扫描阶段
db.orders.aggregate([
    { $match: { user_id: 123 } },           // 使用索引
    { $group: { _id: "$status", count: { $sum: 1 } } }
])
  1. 避免大文档$lookup
javascript 复制代码
// ❌ 错误:关联后再过滤
db.orders.aggregate([
    { $lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user" } },
    { $match: { "user.status": "active" } }
])

// ✅ 正确:先过滤users,再关联
db.orders.aggregate([
    { $lookup: {
        from: "users",
        let: { user_id: "$user_id" },
        pipeline: [
            { $match: {
                $expr: { $eq: ["$_id", "$$user_id"] },
                status: "active"
            }}
        ],
        as: "user"
    }}
])
  1. 使用$project减少数据传输
javascript 复制代码
// ✅ 仅投影需要的字段
db.orders.aggregate([
    { $match: { status: "completed" } },
    { $project: { user_id: 1, amount: 1, _id: 0 } },    // 减少数据传输
    { $group: { _id: "$user_id", total: { $sum: "$amount" } } }
])

9. Java集成与最佳实践

9.1 MongoDB Java Driver

依赖配置(Spring Boot 3.x)

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    <version>3.2.0</version>
</dependency>

连接配置

yaml 复制代码
# application.yml
spring:
  data:
    mongodb:
      uri: mongodb://username:password@mongo1:27017,mongo2:27017,mongo3:27017/mydb?replicaSet=rs0&w=majority
      # 或者分开配置
      host: mongo1,mongo2,mongo3
      port: 27017
      database: mydb
      username: admin
      password: password
      authentication-database: admin
      # 连接池配置
      max-pool-size: 100
      min-pool-size: 10
      max-wait-time: 5000ms
      # 读写配置
      read-preference: secondaryPreferred
      write-concern: majority

9.2 Spring Data MongoDB实战

9.2.1 实体类定义
java 复制代码
import org.springframework.data.annotation.*;
import org.springframework.data.mongodb.core.index.*;
import org.springframework.data.mongodb.core.mapping.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@Document(collection = "users")    // 映射到users集合
@CompoundIndex(def = "{'email': 1, 'status': 1}", name = "idx_email_status")
public class User {

    @Id    // 映射到_id字段
    private String id;

    @Indexed(unique = true)    // 唯一索引
    private String email;

    @Field("user_name")    // 字段名映射
    private String username;

    private Integer age;

    @Indexed
    private String status;

    @DBRef    // 引用其他集合
    private Role role;

    @CreatedDate    // 自动填充创建时间
    private LocalDateTime createdAt;

    @LastModifiedDate    // 自动填充更新时间
    private LocalDateTime updatedAt;

    @Version    // 乐观锁版本号
    private Long version;

    @Transient    // 不持久化到数据库
    private String tempField;
}
9.2.2 Repository接口
java 复制代码
import org.springframework.data.mongodb.repository.*;
import org.springframework.data.domain.*;
import java.util.List;

public interface UserRepository extends MongoRepository<User, String> {

    // 1. 方法名查询(自动生成查询)
    User findByEmail(String email);
    List<User> findByStatus(String status);
    List<User> findByAgeGreaterThan(Integer age);
    List<User> findByStatusAndAgeGreaterThan(String status, Integer age);

    // 2. @Query查询(JSON查询语法)
    @Query("{ 'email': ?0, 'status': ?1 }")
    User findByEmailAndStatus(String email, String status);

    // 3. 分页查询
    Page<User> findByStatus(String status, Pageable pageable);

    // 4. 排序查询
    List<User> findByStatusOrderByCreatedAtDesc(String status);

    // 5. 聚合查询
    @Aggregation(pipeline = {
        "{ $match: { 'status': ?0 } }",
        "{ $group: { _id: '$role', count: { $sum: 1 } } }",
        "{ $sort: { count: -1 } }"
    })
    List<RoleCountDTO> countUsersByRole(String status);

    // 6. 更新查询
    @Query("{ 'email': ?0 }")
    @Update("{ $set: { 'status': ?1, 'updatedAt': ?2 } }")
    long updateStatusByEmail(String email, String status, LocalDateTime updatedAt);
}
9.2.3 MongoTemplate使用
java 复制代码
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.*;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import java.util.List;

@Service
@RequiredArgsConstructor
public class UserService {

    private final MongoTemplate mongoTemplate;

    // 1. 基础查询
    public List<User> findActiveUsers() {
        Query query = new Query();
        query.addCriteria(Criteria.where("status").is("active"));
        query.addCriteria(Criteria.where("age").gte(18));
        query.with(Sort.by(Sort.Direction.DESC, "createdAt"));
        query.limit(10);

        return mongoTemplate.find(query, User.class);
    }

    // 2. 复杂条件查询
    public List<User> complexQuery(String keyword, Integer minAge, List<String> statuses) {
        Query query = new Query();

        // 多条件组合
        Criteria criteria = new Criteria();
        if (keyword != null) {
            criteria.orOperator(
                Criteria.where("username").regex(keyword, "i"),    // 正则匹配
                Criteria.where("email").regex(keyword, "i")
            );
        }
        if (minAge != null) {
            criteria.and("age").gte(minAge);
        }
        if (statuses != null && !statuses.isEmpty()) {
            criteria.and("status").in(statuses);
        }

        query.addCriteria(criteria);
        return mongoTemplate.find(query, User.class);
    }

    // 3. 更新操作
    public long updateUserStatus(String userId, String newStatus) {
        Query query = new Query(Criteria.where("id").is(userId));
        Update update = new Update();
        update.set("status", newStatus);
        update.set("updatedAt", LocalDateTime.now());
        update.inc("version", 1);    // 乐观锁版本号+1

        return mongoTemplate.updateFirst(query, update, User.class)
            .getModifiedCount();
    }

    // 4. Upsert操作(存在则更新,不存在则插入)
    public void upsertUser(User user) {
        Query query = new Query(Criteria.where("email").is(user.getEmail()));
        Update update = new Update();
        update.set("username", user.getUsername());
        update.set("age", user.getAge());
        update.set("updatedAt", LocalDateTime.now());

        mongoTemplate.upsert(query, update, User.class);
    }

    // 5. 聚合查询
    public List<UserStatDTO> getUserStatsByRole() {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("status").is("active")),
            Aggregation.group("role")
                .count().as("userCount")
                .avg("age").as("avgAge"),
            Aggregation.sort(Sort.Direction.DESC, "userCount"),
            Aggregation.limit(10)
        );

        return mongoTemplate.aggregate(aggregation, "users", UserStatDTO.class)
            .getMappedResults();
    }

    // 6. 地理空间查询
    public List<Store> findNearbyStores(double longitude, double latitude, double maxDistance) {
        Query query = new Query();
        query.addCriteria(Criteria.where("location").nearSphere(
            new Point(longitude, latitude)
        ).maxDistance(maxDistance / 6378137));    // 转换为弧度

        return mongoTemplate.find(query, Store.class);
    }

    // 7. 批量操作
    public void batchInsert(List<User> users) {
        mongoTemplate.insertAll(users);    // 批量插入(更高效)
    }

    public void batchUpdate(List<User> users) {
        BulkOperations bulkOps = mongoTemplate.bulkOps(
            BulkOperations.BulkMode.ORDERED,    // 有序执行
            User.class
        );

        for (User user : users) {
            Query query = new Query(Criteria.where("id").is(user.getId()));
            Update update = new Update()
                .set("status", user.getStatus())
                .set("updatedAt", LocalDateTime.now());
            bulkOps.updateOne(query, update);
        }

        bulkOps.execute();    // 批量执行
    }
}

9.3 事务集成

java 复制代码
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class OrderService {

    private final MongoTemplate mongoTemplate;
    private final ProductRepository productRepository;
    private final OrderRepository orderRepository;

    /**
     * Spring声明式事务
     * 要求:MongoDB 4.0+ 复制集 或 4.2+ 分片集群
     */
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(String userId, String productId, Integer quantity) {
        // 1. 扣减库存
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("商品不存在"));

        if (product.getStock() < quantity) {
            throw new RuntimeException("库存不足");
        }

        product.setStock(product.getStock() - quantity);
        productRepository.save(product);

        // 2. 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setAmount(product.getPrice() * quantity);
        order.setStatus("pending");
        order.setCreatedAt(LocalDateTime.now());

        return orderRepository.save(order);

        // 事务自动提交,任何异常自动回滚
    }

    /**
     * 编程式事务(更灵活)
     */
    public Order createOrderWithProgrammaticTx(String userId, String productId, Integer quantity) {
        return mongoTemplate.getMongoDbFactory()
            .getMongoDatabase()
            .runCommand(new Document("ping", 1));    // 确保连接正常

        // 使用SessionCallback
        return mongoTemplate.execute(new SessionCallback<Order>() {
            @Override
            public Order doInSession(ClientSession session) throws MongoException {
                session.startTransaction();

                try {
                    // 事务操作...
                    Order order = new Order();
                    orderRepository.save(order);

                    session.commitTransaction();
                    return order;

                } catch (Exception e) {
                    session.abortTransaction();
                    throw e;
                }
            }
        });
    }
}

9.4 性能优化实践

9.4.1 连接池优化
java 复制代码
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.connection.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
public class MongoConfig {

    @Bean
    public MongoClient mongoClient() {
        ConnectionString connString = new ConnectionString(
            "mongodb://mongo1:27017,mongo2:27017,mongo3:27017/mydb?replicaSet=rs0"
        );

        MongoClientSettings settings = MongoClientSettings.builder()
            .applyConnectionString(connString)

            // 连接池配置
            .applyToConnectionPoolSettings(builder -> builder
                .maxSize(100)                           // 最大连接数
                .minSize(10)                            // 最小连接数
                .maxWaitTime(5000, TimeUnit.MILLISECONDS)    // 最大等待时间
                .maxConnectionIdleTime(60000, TimeUnit.MILLISECONDS)    // 连接空闲时间
                .maxConnectionLifeTime(300000, TimeUnit.MILLISECONDS))  // 连接最大生命周期

            // Socket配置
            .applyToSocketSettings(builder -> builder
                .connectTimeout(10000, TimeUnit.MILLISECONDS)    // 连接超时
                .readTimeout(30000, TimeUnit.MILLISECONDS))      // 读超时

            // Server配置
            .applyToServerSettings(builder -> builder
                .heartbeatFrequency(10000, TimeUnit.MILLISECONDS)    // 心跳频率
                .minHeartbeatFrequency(500, TimeUnit.MILLISECONDS))  // 最小心跳频率

            // 读写配置
            .readPreference(ReadPreference.secondaryPreferred())
            .writeConcern(WriteConcern.MAJORITY)
            .readConcern(ReadConcern.LOCAL)

            .build();

        return MongoClients.create(settings);
    }
}
9.4.2 批量操作优化
java 复制代码
@Service
@RequiredArgsConstructor
public class UserBatchService {

    private final MongoTemplate mongoTemplate;

    /**
     * 批量插入(高效)
     */
    public void batchInsertOptimized(List<User> users) {
        // 方式1:insertAll(推荐)
        mongoTemplate.insertAll(users);

        // 方式2:BulkOperations(更灵活)
        BulkOperations bulkOps = mongoTemplate.bulkOps(
            BulkOperations.BulkMode.UNORDERED,    // 无序执行(更快)
            User.class
        );
        bulkOps.insert(users);
        bulkOps.execute();
    }

    /**
     * 批量更新(高效)
     */
    public void batchUpdateOptimized(List<User> users) {
        BulkOperations bulkOps = mongoTemplate.bulkOps(
            BulkOperations.BulkMode.UNORDERED,
            User.class
        );

        for (User user : users) {
            Query query = new Query(Criteria.where("id").is(user.getId()));
            Update update = new Update()
                .set("status", user.getStatus())
                .set("updatedAt", LocalDateTime.now());
            bulkOps.updateOne(query, update);
        }

        BulkWriteResult result = bulkOps.execute();
        System.out.println("更新数量: " + result.getModifiedCount());
    }

    /**
     * 分批处理大数据集(避免OOM)
     */
    public void processBigDataset(int batchSize) {
        Query query = new Query();
        long total = mongoTemplate.count(query, User.class);

        for (int skip = 0; skip < total; skip += batchSize) {
            query.skip(skip).limit(batchSize);
            List<User> users = mongoTemplate.find(query, User.class);

            // 处理这批数据...
            processUsers(users);
        }
    }

    private void processUsers(List<User> users) {
        // 业务逻辑...
    }
}
9.4.3 索引管理
java 复制代码
import org.springframework.data.mongodb.core.index.*;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class MongoIndexManager {

    private final MongoTemplate mongoTemplate;

    @PostConstruct
    public void ensureIndexes() {
        IndexOperations indexOps = mongoTemplate.indexOps(User.class);

        // 1. 创建单字段索引
        indexOps.ensureIndex(new Index()
            .on("email", Sort.Direction.ASC)
            .unique()
            .named("idx_email"));

        // 2. 创建复合索引
        indexOps.ensureIndex(new Index()
            .on("status", Sort.Direction.ASC)
            .on("createdAt", Sort.Direction.DESC)
            .named("idx_status_created"));

        // 3. 创建文本索引
        indexOps.ensureIndex(new Index()
            .on("username", Sort.Direction.ASC)
            .on("email", Sort.Direction.ASC)
            .text()
            .named("idx_text_search"));

        // 4. 创建部分索引
        indexOps.ensureIndex(new Index()
            .on("email", Sort.Direction.ASC)
            .partial(PartialIndexFilter.of(Criteria.where("status").is("active")))
            .named("idx_email_active"));

        // 5. 创建TTL索引(自动删除过期文档)
        indexOps.ensureIndex(new Index()
            .on("createdAt", Sort.Direction.ASC)
            .expire(30, TimeUnit.DAYS)    // 30天后自动删除
            .named("idx_ttl"));
    }

    /**
     * 查看索引信息
     */
    public void printIndexes() {
        IndexOperations indexOps = mongoTemplate.indexOps(User.class);
        indexOps.getIndexInfo().forEach(indexInfo -> {
            System.out.println("索引名称: " + indexInfo.getName());
            System.out.println("索引字段: " + indexInfo.getIndexFields());
            System.out.println("是否唯一: " + indexInfo.isUnique());
        });
    }
}

10. 生产环境优化建议

10.1 硬件配置

推荐配置(中型应用)

组件 规格 说明
CPU 8核+ WiredTiger利用多核并发
内存 32GB+ Cache默认使用50%内存
磁盘 SSD(NVMe优先) 随机IO性能至关重要
网络 万兆网卡 减少复制集延迟

10.2 操作系统调优

bash 复制代码
# 1. 禁用THP(Transparent Huge Pages)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# 2. 调整文件描述符限制
ulimit -n 64000

# 3. 禁用NUMA(多核服务器)
numactl --interleave=all mongod ...

# 4. 使用XFS文件系统(推荐)
mkfs.xfs /dev/sdb1
mount -o noatime /dev/sdb1 /data/mongodb

10.3 MongoDB配置优化

yaml 复制代码
# mongod.conf(生产环境推荐配置)
storage:
  dbPath: /data/mongodb
  journal:
    enabled: true                      # 启用Journal
    commitIntervalMs: 100              # 100ms刷盘
  wiredTiger:
    engineConfig:
      cacheSizeGB: 16                  # 缓存大小(总内存50%)
      journalCompressor: snappy        # Journal压缩
      directoryForIndexes: true        # 索引独立目录
    collectionConfig:
      blockCompressor: zstd            # 数据压缩(MongoDB 4.2+)
    indexConfig:
      prefixCompression: true          # 索引前缀压缩

systemLog:
  destination: file
  path: /var/log/mongodb/mongod.log
  logAppend: true
  verbosity: 0                         # 日志级别(0=INFO)

net:
  port: 27017
  bindIp: 0.0.0.0                      # 生产环境绑定内网IP
  maxIncomingConnections: 10000        # 最大连接数

replication:
  replSetName: rs0
  oplogSizeMB: 51200                   # Oplog大小(50GB)

setParameter:
  enableLocalhostAuthBypass: false     # 禁用本地认证绕过

10.4 监控指标

关键监控指标

类别 指标 告警阈值
性能 QPS(每秒查询数) > 10000
平均响应时间 > 100ms
慢查询数量 > 10/分钟
资源 CPU使用率 > 80%
内存使用率 > 90%
磁盘使用率 > 85%
磁盘IO等待 > 10%
复制集 复制延迟 > 5秒
Oplog窗口 < 6小时
主节点切换次数 > 0/天
连接 活跃连接数 > 8000
连接池使用率 > 90%

监控工具

  1. MongoDB Ops Manager(官方,商业版)
  2. Prometheus + MongoDB Exporter(开源)
yaml 复制代码
# docker-compose.yml
services:
  mongodb-exporter:
    image: percona/mongodb_exporter:latest
    command:
      - '--mongodb.uri=mongodb://admin:password@mongo1:27017'
      - '--collect-all'
    ports:
      - 9216:9216
  1. Grafana Dashboard(可视化)

10.5 备份策略

备份方式对比

方式 优点 缺点 适用场景
mongodump 简单易用,跨平台 速度慢,锁库 小数据量(< 100GB)
文件系统快照 速度快,一致性好 需要LVM/云快照 中大型数据(> 100GB)
复制集延迟节点 防止误删除 占用资源 结合其他方式使用
Ops Manager 自动化,增量备份 需要商业版 企业级应用

示例:mongodump备份脚本

bash 复制代码
#!/bin/bash

# 备份目录
BACKUP_DIR="/data/mongodb_backup"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="${BACKUP_DIR}/${DATE}"

# 执行备份
mongodump \
  --host=mongo1:27017,mongo2:27017,mongo3:27017 \
  --username=admin \
  --password=password \
  --authenticationDatabase=admin \
  --out="${BACKUP_PATH}" \
  --oplog                         # 备份oplog(时间点恢复)

# 压缩备份
tar -czf "${BACKUP_PATH}.tar.gz" -C "${BACKUP_DIR}" "${DATE}"
rm -rf "${BACKUP_PATH}"

# 删除7天前的备份
find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +7 -delete

echo "备份完成: ${BACKUP_PATH}.tar.gz"

恢复示例

bash 复制代码
# 解压备份
tar -xzf /data/mongodb_backup/20251222_100000.tar.gz -C /tmp

# 恢复数据
mongorestore \
  --host=mongo1:27017 \
  --username=admin \
  --password=password \
  --authenticationDatabase=admin \
  --oplogReplay                   # 重放oplog
  /tmp/20251222_100000

10.6 安全加固

安全检查清单

yaml 复制代码
# 1. 启用认证
security:
  authorization: enabled
  keyFile: /etc/mongodb/keyfile       # 复制集认证密钥

# 2. 启用TLS/SSL
net:
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/mongodb/server.pem
    CAFile: /etc/mongodb/ca.pem

# 3. 绑定内网IP(不要用0.0.0.0)
net:
  bindIp: 10.0.1.10

# 4. 启用审计日志(企业版)
auditLog:
  destination: file
  format: JSON
  path: /var/log/mongodb/audit.json

# 5. 配置角色权限
db.createUser({
    user: "app_user",
    pwd: "strong_password",
    roles: [
        { role: "readWrite", db: "mydb" },
        { role: "read", db: "mydb_readonly" }
    ]
})

总结

本文深入剖析了MongoDB的核心技术,涵盖以下内容:

  1. 数据模型:BSON格式、文档设计模式、嵌入vs引用
  2. 存储引擎:WiredTiger架构、MVCC、Checkpoint、Journal
  3. 索引优化:B-Tree结构、索引类型、ESR规则、覆盖查询
  4. 高可用:复制集架构、Oplog复制、选举机制、读写关注
  5. 水平扩展:分片集群、分片键选择、Chunk管理、查询路由
  6. 事务:多文档事务、快照隔离、两阶段提交
  7. 查询优化:执行计划分析、聚合管道优化
  8. Java集成:Spring Data MongoDB、事务管理、性能优化
  9. 生产实践:硬件配置、监控告警、备份恢复、安全加固

推荐阅读

相关推荐
宠..2 小时前
为单选按钮绑定事件
运维·服务器·开发语言·数据库·c++·qt·microsoft
宠..2 小时前
对单选按钮分组
开发语言·数据库·c++·qt·安全·安全性测试
Ashley_Amanda2 小时前
SAP ABAP 开发全攻略:从核心编程到最佳实践
大数据·数据库·sql
黎相思2 小时前
附录:SQLite介绍
数据库·sqlite
毕设十刻2 小时前
基于Vue的新生入学报道管理系统(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
Vic101012 小时前
Redis防重复点击与分布式锁
java·数据库·redis·分布式
罗政2 小时前
mybatis-plus插件解决sql报错:this is incompatible with sql_mode=only_full_group_by ”
数据库·sql·mybatis
leo_qiu_s3 小时前
MERGE INTO语句
数据库
未来之窗软件服务3 小时前
幽冥大陆(六十二) 多数据库交叉链接系统Go语言—东方仙盟筑基期
数据库·人工智能·oracle·golang·数据库集群·仙盟创梦ide·东方仙盟