MongoDB 0-∞
本文最后更新于77 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

一、基础概念

作为基础

一、定义

MongoDB 是一种文档型 NoSQL 数据库,特点是数据结构灵活、扩展性强,适合高并发和快速迭代场景。

二、对比

NoSQL(Not Only SQL)泛指非关系型数据库,它们在数据结构、可扩展性和事务模型上与传统的关系型数据库(如 MySQL)有显著不同。

关系型数据库 vs NoSQL 数据库

特性关系型数据库 (RDBMS)NoSQL 数据库
数据模型基于表(Table)和行(Row)的结构化数据多样化模型(文档、键值、列族、图)
Schema固定(Schema-on-Write),需预先定义表结构动态(Schema-on-Read),数据结构灵活
扩展性主要通过垂直扩展(Scale-Up)提升单机性能主要通过水平扩展(Scale-Out)构建分布式集群
事务遵循 ACID 原则,保证强一致性通常遵循 BASE 原则,保证最终一致性
适用场景事务性要求高、数据结构稳定的应用大数据、高并发、需要快速迭代的应用

重点:

  1. Schema :字段灵活并且文档的结构可以不同
  2. 扩展性:天然适合水平扩展
  3. 事务:偏向最终一致性

三、NoSQL数据库的分类

NoSQL 数据库根据其数据模型的不同,主要分为以下几类:

  1. 文档型数据库 (Document-Oriented)
    • 特点:以文档(通常是 JSON 或 BSON 格式)为单位存储数据,结构灵活。
    • 代表:MongoDB, CouchDB
  2. 键值型数据库 (Key-Value)
    • 特点:数据以简单的键值对形式存储,查询速度快。
    • 代表:Redis, Memcached
  3. 列族型数据库 (Column-Family)
    • 特点:数据按列族存储,适合大规模数据聚合分析。
    • 代表:Cassandra, HBase
  4. 图形型数据库 (Graph)
    • 特点:专注于节点和边的关系,适合社交网络、推荐系统等场景。
    • 代表:Neo4j, ArangoDB
  5. MongoDB在NoSQL生态中的定位是?
    1. MongoDB是文档性的数据库的杰出代表,它凭借着灵活的数据模型,强大的查询语言以及高可扩展性,在NoSQL生态中占据了很重要的地位,并且很适合需要快速开发,数据结构多变的,高并发读写的一些应用场景。

四、核心概念

理解MongoDB的核心概念是掌握其使用的第一步

文档(Document)与 BSON 格式

  • 文档 (Document):是 MongoDB 中数据的基本单元,由一组键值对(key-value)组成,类似于 JSON 对象。文档的结构是动态的,同一个集合中的文档可以有不同的字段。{
     “_id”: ObjectId(“60c72b2f9b1d8b3b8c8b4567”),
     “name”: “Alice”,
     “age”: 30,
     “email”: “alice@example.com”,
     “tags”: [“mongodb”, “database”, “nosql”]
    }
  • BSON (Binary JSON):MongoDB 在内部使用 BSON 格式存储文档。BSON 是 JSON 的二进制表示形式,它支持更多的数据类型(如日期、二进制数据),并优化了存储空间和查询性能。

为什么文档的部分被加强了?因为MongoDB的一条文档就可以完整描述对象,这是MongoDB的最大的特点之一

集合(Collection)概念

  • 集合 (Collection):是 MongoDB 文档的容器,可以看作是关系型数据库中的表(Table)。但与表不同,集合不需要预先定义结构(schema-less)。

数据库(Database)结构

  • 数据库 (Database):是集合的物理容器。一个 MongoDB 实例可以承载多个数据库,每个数据库都有自己独立的权限和文件。

MongoDB 与关系型数据库术语对比

MongoDB关系型数据库 (RDBMS)
DatabaseDatabase
CollectionTable
DocumentRow (or Record)
FieldColumn (or Attribute)
IndexIndex
Replica SetMaster-Slave Replication
ShardingPartitioning

架构原理

了解 MongoDB 的底层架构有助于我们更好地进行性能调优和故障排查。

存储引擎(WiredTiger)

WiredTiger 是 MongoDB 默认的存储引擎,它提供了文档级别的并发控制、数据压缩和快照等高级功能,是 MongoDB 高性能的关键。

内存映射文件系统

MongoDB 使用内存映射文件(Memory-Mapped Files)来处理数据。它将磁盘上的数据文件映射到内存中,使得 MongoDB 可以像访问内存一样访问数据,从而将数据管理委托给操作系统的虚拟内存管理器,简化了代码并提升了性能。

索引机制

为了提高查询效率,MongoDB 支持在任意字段上创建索引。与关系型数据库类似,MongoDB 的索引也采用 B-Tree 数据结构,能够极大地加速数据检索过程。

查询优化器

MongoDB 内置了一个查询优化器,它会分析查询请求,并从多个可能的查询计划中选择一个最优的执行计划,以确保查询的高效性。

二、环境搭建(ARM 架构)

这里将会详细介绍如何在Rocky Linux的操作系统上安装以及配置MongoDB以及如何进行管理MongoDB服务,正确的环境的搭建是后续学习的基础

安装过程

  1. 创建ARM架构的专属的repo的文件
cat > /etc/yum.repos.d/mongodb-org-7.0.repo <<EOF
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/7.0/aarch64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-7.0.asc
EOF
  1. 清理缓存并安装:
yum clean all && yum makecache
yum install -y mongodb-org

这个命令会安装以下几个包:

  • mongodb-org-server: mongod 守护进程、配置文件和初始化脚本。
  • mongodb-org-mongos: mongos 守护进程。
  • mongodb-mongosh: MongoDB Shell (mongosh)。
  • mongodb-org-tools: MongoDB 的命令行工具,如 mongodump, mongorestore, mongoimport, mongoexport 等。

相关配置

MongoDB 的主配置文件位于 /etc/mongod.conf,默认的配置文件采用 YAML 格式。以下是一些核心配置项的说明:

# mongod.conf

# 存储设置
storage:
dbPath: /var/lib/mongo  # 数据文件存放目录
journal:
  enabled: true      # 是否启用 journal 日志,建议始终开启

# 系统日志设置
systemLog:
destination: file      # 日志输出到文件
logAppend: true        # 日志以追加方式写入
path: /var/log/mongodb/mongod.log  # 日志文件路径

# 网络设置
net:
port: 27017            # 监听端口
bindIp: 127.0.0.1      # 绑定的 IP 地址,默认为本地回环地址。如需远程访问,可设置为 0.0.0.0

# 进程管理
processManagement:
timeZoneInfo: /usr/share/zoneinfo # 时区信息

# 安全设置 (默认禁用)
#security:
# authorization: enabled # 启用访问控制
  • storage.dbPath: MongoDB 数据文件的存储路径,需要确保 mongod 用户对此目录有读写权限。
  • systemLog.path: 日志文件路径,用于记录数据库的操作和错误信息。
  • net.bindIp: 这是非常重要的安全配置。默认 127.0.0.1 只允许本机连接。如果需要从其他服务器连接,应谨慎地将其设置为服务器的内网 IP 或 0.0.0.0(监听所有网络接口),并配合防火墙和安全组规则使用。

服务管理

在现代 Linux 系统中,我们使用 systemd 来管理 mongod 服务。

# 启动 MongoDB 服务
systemctl start mongod
# 查看服务状态
systemctl status mongod
# 停止 MongoDB 服务
systemctl stop mongod
# 重启 MongoDB 服务
systemctl restart mongod
# 启用开机自启动
systemctl enable mongod
# 禁用开机自启动
systemctl disable mongod

生产配置

这是一个更贴近生产环境的配置文件示例,增加了安全和性能相关的配置。

# /etc/mongod.conf - Production Example

storage:
dbPath: /var/lib/mongo
journal:
  enabled: true
engine: wiredTiger
wiredTiger:
  engineConfig:
    cacheSizeGB: 1  # 根据服务器内存调整,建议为物理内存的 50% - 60%

systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log

net:
port: 27017
bindIp: 127.0.0.1,192.168.1.100 # 绑定本地和内网 IP
maxIncomingConnections: 1000 # 最大连接数

processManagement:
fork: true # 后台运行
pidFilePath: /var/run/mongodb/mongod.pid
timeZoneInfo: /usr/share/zoneinfo

security:
authorization: enabled # 开启认证

replication:
replSetName: rs0 # 副本集名称

服务管理

在现代 Linux 系统中,我们使用 systemd 来管理 mongod 服务。

# 启动 MongoDB 服务
systemctl start mongod
# 查看服务状态
systemctl status mongod
# 停止 MongoDB 服务
systemctl stop mongod
# 重启 MongoDB 服务
systemctl restart mongod
# 启用开机自启动
systemctl enable mongod
# 禁用开机自启动
systemctl disable mongod

生产配置

这是一个更贴近生产环境的配置文件示例,增加了安全和性能相关的配置。

# /etc/mongod.conf - Production Example

storage:
dbPath: /var/lib/mongo
journal:
  enabled: true
engine: wiredTiger
wiredTiger:
  engineConfig:
    cacheSizeGB: 1  # 根据服务器内存调整,建议为物理内存的 50% - 60%

systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log

net:
port: 27017
bindIp: 127.0.0.1,192.168.1.100 # 绑定本地和内网 IP
maxIncomingConnections: 1000 # 最大连接数

processManagement:
fork: true # 后台运行
pidFilePath: /var/run/mongodb/mongod.pid
timeZoneInfo: /usr/share/zoneinfo

security:
authorization: enabled # 开启认证

replication:
replSetName: rs0 # 副本集名称

代码实践:

mkdir -p /data/mongodb
sed -i 's|dbPath: /var/lib/mongo|dbPath: /data/mongodb|' /etc/mongod.conf
sed -i 's|bindIp: 127.0.0.1|bindIp: 0.0.0.0|' /etc/mongod.conf
cp /lib/systemd/system/mongod.service /etc/systemd/system/mongod.service
sed -i 's/User=mongod/User=root/' /etc/systemd/system/mongod.service
sed -i 's/Group=mongod/Group=root/' /etc/systemd/system/mongod.service
systemctl start mongod && systemctl enable mongod

# 测试验证
mongod --version

三、基础操作

本节将会学会MongoDB的基础操作,包括如何使用MongoDB Shell 管理数据库和集合以及对文档进行核心的增删改查的操作(CRUD),这些事前提以及基础

  • 在开始操作之前的最最基础的数据库的连接的错误,以及针对ARM架构的不同的处理的办法。

MongoDB ARM 架构(CentOS 9)启动失败故障复盘

一、故障现象

  1. 执行 systemctl start mongod 启动服务失败,systemctl status mongod 显示 failed (Result: exit-code),进程状态 code=exited, status=1/FAILURE
  2. 日志关键报错:Failed to open /var/log/mongodb/mongod.logFileNotOpen 错误码)。
  3. 手动 mongosh 连接提示 ECONNREFUSED 127.0.0.1:27017,端口无监听。

二、根因分析

核心原因是 systemd 服务运行用户权限不匹配,结合 ARM 架构安装的特殊场景,形成连锁问题:

  1. 权限主体不匹配:MongoDB 服务默认以系统用户 mongod 运行,但 /var/log/mongodb/var/lib/mongo 目录 / 文件均由 root 创建,mongod 用户无写入权限,导致服务启动时无法初始化日志文件,直接退出。
  2. 配置与架构不兼容(前置坑):最初安装时误用了 x86_64 架构的 yum 源,导致依赖包安装不全,后续虽修正为 aarch64 源,但服务权限问题未同步修复。
  3. 服务模式与手动模式的差异systemd 服务有独立的用户隔离机制,手动 root 启动可直接写入文件,但服务模式下用户切换后权限失效。

三、完整复现流程(踩坑全过程)

  1. 错误安装:直接执行 yum install mongodb-org,默认拉取 x86_64 包,因架构不兼容报错,依赖(mongodb-mongoshmongodb-org-database)无法安装。
  2. 修正安装:配置 aarch64 架构 yum 源,成功安装 MongoDB 7.0,但未处理目录权限。
  3. 服务启动失败:执行 systemctl start mongod 报错,日志显示无法打开日志文件。
  4. 临时启动验证:用 mongod --fork --dbpath /tmp/xxx 手动 root 启动,服务正常运行,确认 MongoDB 本身无故障,仅服务模式权限问题。

四、解决方案(分两种场景)

方案 1:彻底修复 systemd 服务(推荐,生产 / 长期使用)

# 1. 停止服务并禁用旧配置
systemctl stop mongod
systemctl disable mongod

# 2. 修正目录所有权与权限
mkdir -p /var/lib/mongo /var/log/mongodb
chown -R mongod:mongod /var/lib/mongo /var/log/mongodb
chmod 700 /var/lib/mongo
chmod 755 /var/log/mongodb
touch /var/log/mongodb/mongod.log
chown mongod:mongod /var/log/mongodb/mongod.log
chmod 644 /var/log/mongodb/mongod.log

# 3. 重载 systemd 配置并启动
systemctl daemon-reload
systemctl start mongod
systemctl enable mongod

方案 2:临时手动启动(测试 / 开发用,绕开权限问题)

# 用 root 直接启动,避免用户权限隔离
mongod --dbpath /var/lib/mongo --logpath /var/log/mongodb/mongod.log --bind_ip 127.0.0.1 --port 27017 --noauth --fork

五、避坑总结(给后人的关键提示)

  1. 架构优先检查:ARM 架构(aarch64)安装 MongoDB,必须配置对应架构的 yum 源,否则依赖包会全部缺失,不要直接用默认源安装。
  2. 权限是服务模式的核心:所有数据目录、日志目录的所有权必须与服务运行用户一致(MongoDB 默认是 mongod:mongod),不要用 root 创建后忘记修改权限。
  3. 故障定位三步走
    • 第一步:journalctl -u mongod 看日志,优先找文件 / 权限相关报错;
    • 第二步:用 root 手动启动 mongod 验证数据库本身是否正常;
    • 第三步:检查 systemd 服务文件的 User/Group 配置是否正确。
  4. 测试用临时方案兜底:如果服务模式一直报错,先手动启动确认数据库可用,再回头排查服务配置,避免卡在启动阶段无法验证功能。

六、额外提示(可选优化)

启动后若出现 Soft rlimits for open file descriptors too low 警告,可通过以下方式优化文件句柄限制:

# 临时生效(重启失效)
ulimit -n 64000
# 永久生效(修改 limits.conf)
echo "* soft nofile 64000" >> /etc/security/limits.conf
echo "* hard nofile 64000" >> /etc/security/limits.con

MongoDB Shell使用

  • 连接数据库:mongosh
  • 基本命令:
    • show dbs:显示所有的
    • use <db_name>: 切换到指定数据库,如果不存在则在首次插入数据时创建。
    • show collections: 显示当前数据库中的所有集合。
    • db: 显示当前所在的数据库。
    • db.stats(): 显示当前数据库的状态信息。
    • exitquit(): 退出 Shell。

数据库操作

  • 创建数据库: 无需显式创建,当向一个不存在的数据库中的集合插入第一条数据时,该数据库会自动创建。
  • 查看数据库: show dbs
  • 切换数据库: use myNewDB
  • 删除数据库: 首先切换到要删除的数据库,然后执行 db.dropDatabase()

集合操作

  • 创建集合:
    • 隐式创建: 当向一个不存在的集合插入第一条数据时,集合会自动创建。
    • 显式创建: 使用 db.createCollection() 方法,可以指定更多选项,如大小限制、验证规则等。db.createCollection(“myCollection”, { capped: true, size: 100000 })
  • 查看集合: show collections
  • 删除集合: db.myCollection.drop()

文档 CRUD 操作

CRUD 代表创建 (Create)、读取 (Read)、更新 (Update) 和删除 (Delete) 操作。

插入文档 (Create)

  • insertOne(): 插入单个文档。db.inventory.insertOne({ item: “canvas”, qty: 100, tags: [“cotton”], size: { h: 28, w: 35.5, uom: “cm” } })
  • insertMany(): 插入多个文档。db.inventory.insertMany([
    { item: “journal”, qty: 25, tags: [“blank”, “red”], size: { h: 14, w: 21, uom: “cm” } },
    { item: “mat”, qty: 85, tags: [“gray”], size: { h: 27.9, w: 35.5, uom: “cm” } }
    ])

查询文档 (Read)

  • find(): 查询集合中所有匹配的文档。// 查询所有文档
    db.inventory.find({})

    // 查询 qty 大于 50 的文档
    db.inventory.find({ qty: { $gt: 50 } })
  • findOne(): 只返回匹配的第一个文档。db.inventory.findOne({ item: “journal” })

更新文档 (Update)

  • updateOne(): 更新匹配的第一个文档。db.inventory.updateOne(
    { item: “journal” },
    { $set: { “size.uom”: “in” }, $currentDate: { lastModified: true } }
    )
  • updateMany(): 更新所有匹配的文档。db.inventory.updateMany(
    { qty: { $gt: 50 } },
    { $set: { “size.uom”: “in” }, $currentDate: { lastModified: true } }
    )
  • replaceOne(): 替换匹配的第一个文档。

删除文档 (Delete)

  • deleteOne(): 删除匹配的第一个文档。db.inventory.deleteOne({ item: “journal” })
  • deleteMany(): 删除所有匹配的文档。db.inventory.deleteMany({ qty: { $gt: 50 } })

实践操作:

  1. 创建与切换: 创建一个名为 bookstore 的数据库并切换过去。
  2. 插入数据: 在 bookstore 数据库中创建一个 books 集合,并批量插入至少 5 本书的数据,每本书包含 title, author, published_year, genres (数组), stock (库存) 字段。
  3. 查询练习:
    • 查询所有库存量小于 10 本的书。
    • 查询所有 Science Fiction 类型的书。
  4. 更新练习: 将指定一本书的库存量增加 5。
  5. 删除练习: 删除所有 published_year 在 1950 年之前的书。
  6. 数据导入导出: 使用 mongoexportbooks 集合导出为 JSON 文件,然后使用 mongoimport 将其导入到一个新的集合 books_backup 中。

代码如下:

先将data.js导入到Linux中:
两个方案,自行摸索
下面的是直接将data.js导入到MongoDB中(直接将data.js的全部的内容直接进行文件的写入)
vi /root/data.js
将文件中的内容直接进行复制粘贴即可
记得在文件中写下:db = db.getSiblingDB("bookstore");
这个是JS脚本里面的切换数据库的标准语法

// 1. 创建与切换数据库
use bookstore;

// 2. 插入数据
// 参考 data.js 文件中 Data for: MongoDB基础操作.md 下 books 集合的插入数据部分

// 3. 查询练习
// 查询所有库存量小于 10 本的书
db.books.find({ stock: { $lt: 10 } });

// 查询所有 Science Fiction 类型的书
db.books.find({ genres: "Science Fiction" });

// 4. 更新练习
// 将指定一本书的库存量增加 5
db.books.updateOne(
  { title: "Dune" },
  { $inc: { stock: 5 } }
);

// 5. 删除练习
// 删除所有 published_year 在 1950 年之前的书
db.books.deleteMany({ published_year: { $lt: 1950 } });

四、进阶查询

⚠️:这部分属于数据库的基础,未来几乎每天都会用到,很重要,需要熟悉并且记忆!

查询进阶

本章节将带大家深入了解 MongoDB 强大的查询功能,包括使用查询操作符、投影、排序、分页以及正则表达式查询,可以更精确、更高效地从数据库中检索数据。


查询操作符(知识库,不需要强记忆)

查询操作符是 MongoDB 查询语言的核心,它们可以帮助大家构建更复杂的查询条件。

比较操作符

操作符描述示例
$eq等于 (Equal)db.inventory.find({ qty: { $eq: 20 } })
$ne不等于 (Not Equal)db.inventory.find({ qty: { $ne: 20 } })
$gt大于 (Greater Than)db.inventory.find({ qty: { $gt: 20 } })
$gte大于等于 (Greater Than or Equal)db.inventory.find({ qty: { $gte: 20 } })
$lt小于 (Less Than)db.inventory.find({ qty: { $lt: 20 } })
$lte小于等于 (Less Than or Equal)db.inventory.find({ qty: { $lte: 20 } })
$in在指定数组内 (In)db.inventory.find({ qty: { $in: [5, 15] } })
$nin不在指定数组内 (Not In)db.inventory.find({ qty: { $nin: [5, 15] } })

逻辑操作符

操作符描述示例
$and逻辑与,连接多个查询条件db.inventory.find({ $and: [{ qty: { $lt: 20 } }, { price: { $gt: 10 } }] })
$or逻辑或,满足任一查询条件db.inventory.find({ $or: [{ qty: { $lt: 20 } }, { price: { $gt: 50 } }] })
$nor逻辑非或,所有条件都不满足db.inventory.find({ $nor: [{ price: 1.99 }, { sale: true }] })
$not逻辑非,对指定条件取反db.inventory.find({ price: { $not: { $gt: 1.99 } } })

元素操作符

操作符描述示例
$exists判断字段是否存在db.inventory.find({ qty: { $exists: true } })
$type判断字段的数据类型db.inventory.find({ qty: { $type: "number" } })

数组操作符

操作符描述示例
$all匹配包含所有指定元素的数组db.inventory.find({ tags: { $all: ["appliance", "school"] } })
$elemMatch数组中至少有一个元素满足所有指定条件db.inventory.find({ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } })
$size匹配指定大小的数组db.inventory.find({ tags: { $size: 3 } })

投影(Projection)

投影用于限制查询结果中返回的字段,可以减少网络传输的数据量,并保护敏感字段。

  • 包含字段: 在 find() 方法的第二个参数中,将需要返回的字段设置为 1。// 只返回 name 和 price 字段,_id 默认返回
    db.products.find({}, { name: 1, price: 1 })
  • 排除字段: 将不需要返回的字段设置为 0。// 返回除 description 之外的所有字段
    db.products.find({}, { description: 0 })
  • 排除 _id 字段:db.products.find({}, { name: 1, price: 1, _id: 0 })

排序(Sorting)

使用 sort() 方法对查询结果进行排序。

  • 升序 (Ascending): 将字段值设置为 1
  • 降序 (Descending): 将字段值设置为 -1
// 按价格升序排序
db.products.find().sort({ price: 1 })

// 按库存降序、名称升序排序
db.products.find().sort({ stock: -1, name: 1 })

分页(Pagination)

通过组合使用 limit()skip() 方法,可以实现对查询结果的分页。

  • limit(): 限制返回的文档数量。
  • skip(): 跳过指定数量的文档。
// 获取第 2 页数据,每页 10 条 (跳过前 10 条,返回 10 条)
db.products.find().skip(10).limit(10)

注意: 对于大数据量的集合,使用 skip() 进行深度分页可能会有性能问题。在这种情况下,建议使用基于范围的查询(如使用 _id 或时间戳)。


正则表达式查询

MongoDB 支持使用 PCRE (Perl Compatible Regular Expression) 语法的正则表达式进行字符串匹配。

// 查询 name 字段以 'A' 开头的文档 (不区分大小写)
db.products.find({ name: { $regex: '^A', $options: 'i' } })

// 查询 name 字段包含 'pro' 的文档
db.products.find({ name: /pro/ })

实践操作

假设有一个 students 集合,包含以下字段:name, age, major, gpa, courses (数组)。

需求描述

  1. 查询练习:
    • 查询所有主修 ‘Computer Science’ 且 GPA 大于 3.5 的学生。
    • 查询年龄在 20 到 22 岁之间(含)的学生。
    • 查询选修了 ‘Database Systems’ 和 ‘Data Structures’ 两门课的学生。
  2. 投影练习:
    • 只返回学生的姓名和专业。
  3. 排序和分页练习:
    • 按 GPA 降序显示前 10 名学生。
  4. 正则表达式练习:
    • 查询所有姓 ‘Li’ 的学生。

实践细节和结果验证

// 1. 查询练习
// 查询所有主修 'Computer Science' 且 GPA 大于 3.5 的学生
db.students.find({ major: 'Computer Science', gpa: { $gt: 3.5 } });

// 查询年龄在 20 到 22 岁之间(含)的学生
db.students.find({ age: { $gte: 20, $lte: 22 } });

// 查询选修了 'Database Systems' 和 'Data Structures' 两门课的学生
db.students.find({ courses: { $all: ['Database Systems', 'Data Structures'] } });

// 2. 投影练习
// 只返回学生的姓名和专业
db.students.find({}, { name: 1, major: 1, _id: 0 });

// 3. 排序和分页练习
// 按 GPA 降序显示前 10 名学生
db.students.find().sort({ gpa: -1 }).limit(10);

// 4. 正则表达式练习
// 查询所有姓 'Li' 的学生
db.students.find({ name: /^Li/ });

1. 比较操作符($gt / $lt / $gte / $lte)

使用率:★★★★★

几乎所有列表接口、筛选接口都要用。

db.orders.find({ createTime: { $gte: 开始时间, $lte: 结束时间 } })

2. 逻辑操作符($and / $or)

使用率:★★★★★

多条件筛选必写。

3. 投影(只返回需要的字段)

使用率:★★★★★

性能优化、接口返回控制必备。

db.users.find({}, { password: 0 }) // 不返回密码

4. 排序 + 分页(sort + skip + limit)

使用率:★★★★★

所有列表接口都要写。

db.articles.find().sort({ createTime: -1 }).skip(0).limit(10)

5. 正则查询(搜索功能)

使用率:★★★★☆

搜索、模糊匹配必用。

db.goods.find({ name: /华为/i })

6. 数组查询($all / $elemMatch)

使用率:★★★★

标签、课程、权限、多选筛选必用。

db.users.find({ tags: { $all: ["vip", "active"] } })

五、索引优化

可以帮助大家构建高性能的数据库应用

索引基础

什么是索引?

索引是一种特殊的数据结构,它以易于遍历的形式存储了集合中一小部分的数据。通过索引,MongoDB 可以直接定位到符合查询条件的文档,而无需扫描整个集合,从而极大地提高了查询速度。

索引的类型

MongoDB 支持多种类型的索引,以适应不同的查询需求。

  1. 单字段索引 (Single Field Index)
    • 最基础的索引类型,针对单个字段创建。
    • 示例: db.inventory.createIndex({ price: 1 }) (按价格升序)
  2. 复合索引 (Compound Index)
    • 在多个字段上创建的索引。字段的顺序非常重要,决定了索引的查询效率。
    • 示例: db.inventory.createIndex({ price: 1, qty: -1 }) (先按价格升序,再按数量降序)
  3. 多键索引 (Multikey Index)
    • 为数组字段创建的索引。MongoDB 会为数组中的每个元素创建一条索引项。
    • 示例: db.inventory.createIndex({ tags: 1 }) (为 tags 数组中的每个标签创建索引)
  4. 文本索引 (Text Index)
    • 用于支持对字符串内容的文本搜索查询。
    • 示例: db.inventory.createIndex({ description: "text" })
  5. 地理空间索引 (Geospatial Index)
    • 用于高效地查询地理空间坐标数据。
    • 类型: 2dsphere (用于球面几何) 和 2d (用于平面几何)。
    • 示例: db.places.createIndex({ location: "2dsphere" })
  6. 哈希索引 (Hashed Index)
    • 计算字段值的哈希值并对哈希值建立索引,主要用于哈希分片。
    • 示例: db.inventory.createIndex({ status: "hashed" })

一、索引是什么(我的理解)

索引就是给数据库建目录,避免全表扫描(COLLSCAN),让查询从慢到快。

不加索引 = 逐行翻书

加索引 = 看目录直接定位


二、创建索引(重点)

db.集合.createIndex({ 字段: 1 }, { 配置 })
// 1=升序,-1=降序

常用配置:

  • unique: true唯一索引,不能重复
  • background: true后台创建,不阻塞业务
  • sparse: true稀疏索引,只索引有字段的文档
  • expireAfterSeconds自动过期删除(日志专用)

三、复合索引最关键:ESR 法则(超级重要)

ESR = 等值 → 排序 → 范围

必须按这个顺序建索引,否则索引废一半。

  • E(Equality):等值查询放第一(=)
  • S(Sort):排序放第二(sort)
  • R(Range):范围查询放最后(、lt、$in)

例:

查询:category=电子 → 按brand排序 → price>500
索引:{ category:1, brand:1, price:1 }

我的理解

先缩小范围 → 再排好序 → 最后过滤,效率最高。


四、查询性能分析(explain 必看 3 个指标)

explain("executionStats")

重点看:

  1. stage
    • IXSCAN:索引命中 ✅
    • COLLSCAN:全表扫描 ❌ 慢死
  2. totalDocsExamined = 0 → 覆盖查询 ✅ 极致快
  3. executionTimeMillis:执行速度

五、覆盖查询(性能天花板)

查询所需所有字段都在索引里

MongoDB 只查索引,不查文档,速度极快。

实现条件:

  • 查询字段 = 索引字段
  • 不查询 _id(或把 _id 也加入索引)
  • 投影只返回索引内字段

例子:

db.students.createIndex({ student_id:1, name:1 })
db.students.find({student_id:1},{name:1,_id:0})

效果:totalDocsExamined: 0


六、索引维护(必背)

查看索引:

db.集合.getIndexes()

删除索引:

db.集合.dropIndex("索引名")

查看索引使用率:

db.集合.aggregate([{ $indexStats: {} }])

七、核心原则

  1. 查询频繁的字段必须加索引
  2. 复合索引严格遵循 ESR 顺序
  3. 能做覆盖查询就一定要做
  4. explain 必须看,COLLSCAN 绝对不允许上线
  5. 索引不是越多越好,多了会拖慢写入
  6. 唯一、稀疏、TTL 根据业务场景选择

八、极简一句话总结

索引 = 加速查询

复合索引 = ESR 顺序

覆盖查询 = 性能天花板

explain = 性能照妖镜

六、数据建模

与关系型数据库不同,MongoDB 灵活的文档模型为数据建模提供了更多的选择。本章节将介绍 MongoDB 数据建模的核心思想、常见模式以及如何根据应用需求选择合适的模型,以实现最佳的性能和可扩展性。

核心思想为:嵌入(Embedding) vs. 引用(Referencing)

这是 MongoDB 数据建模中最核心的决策点。

  • 嵌入 (Embedding / Denormalization)
    • 描述: 将相关的数据直接嵌入到主文档中,形成一个嵌套的文档结构。
    • 优点:
      • 性能好: 只需一次数据库查询即可获取所有相关数据,减少了读操作的次数。
      • 数据原子性: 对单个文档的更新是原子操作。
    • 缺点:
      • 文档体积大: 可能导致文档超过 16MB 的大小限制。
      • 数据冗余: 如果嵌入的数据在多处被使用,更新时需要修改所有包含它的文档。
    • 适用场景: “一对一”或“一对多”关系,且“多”的一方数据不经常变动,或者总是和“一”的一方一起被查询。
  • 引用 (Referencing / Normalization)
    • 描述: 将数据存储在不同的集合中,通过在文档中存储对另一个文档的引用(通常是 _id)来建立关系。
    • 优点:
      • 数据一致性: 更新数据时只需修改一处。
      • 文档体积小: 避免了数据冗余。
    • 缺点:
      • 查询性能较低: 需要多次查询(或使用 $lookup)来获取关联数据。
    • 适用场景: “多对多”关系,或者“一对多”关系中“多”的一方数据量巨大、经常变动或经常被独立查询。

决策依据

选择嵌入还是引用,主要取决于应用的数据访问模式

  • 读多写少: 优先考虑嵌入,优化读取性能。
  • 写操作频繁: 优先考虑引用,避免更新大量冗余数据。
  • 数据一致性要求高: 优先考虑引用。
  • 数据关联性强,总是一起访问: 优先考虑嵌入。

自己理解

嵌入(将相关的数据直接塞在一起,修改一个数据需要将相关的所有的数据都进行修改)和引用(分开存储,使用ID进行关联)
形象化理解下:一、Embedding 嵌入模式
可以把它想象成朋友圈动态和评论放在一起:一条朋友圈动态,所有评论直接内嵌在这条动态文档内部,全部数据聚合在一个文档里。
示例结构:
json
{
"post": "今天学习 MongoDB",
"comments": [
  {
    "user": "Alice",
    "text": "厉害"
  }
]
}
本质:把关联数据一锅炖,全部嵌套在同一个文档中。
优点
所有关联数据都在一个文档里,只需一次查询就能拿到全部信息,查询速度非常快。
缺点
随着内嵌的子数据(比如评论)越来越多,主文档体积会持续膨胀;只适合数据量小、关联层级简单、不会无限增长的小规模关联场景。
二、Referencing 引用模式
同样是朋友圈场景,但把动态和评论分开存储:朋友圈主文档只存评论的 ID 列表,评论单独作为独立文档存放,通过 ID 建立关联。
示例结构:
朋友圈主文档:
json
{
"post": "今天学习 MongoDB",
"comment_ids": [1,2,3]
}
评论独立文档:
json
{
"_id": 1,
"user": "Alice",
"text": "厉害"
}
本质:类似关系型数据库分表存储,通过主键 ID 做关联。
优点
子数据(评论)无限新增也不会撑大主文档,结构清晰;非常适合大型项目、高并发场景、子数据数量会持续爆炸增长的业务。
缺点
无法一次拿到所有关联数据,需要多次查询;也可以用 $lookup 做联表查询,复杂度比嵌入模式更高。
三、总结
嵌入模式就是聚合嵌套,查得快、结构简单,但怕数据无限膨胀;
引用模式就是拆分独立、ID 关联,支持海量数据扩展、适合大型项目,但查询需要多步关联,复杂度更高。(数据总是一起访问,并且数据量不大的时候适合使用嵌入,如果数据量巨大,更新频繁,关系繁杂的话,就适合于Referencing(引用))

下面是常见的数据建模的模式

  1. 属性模式
  • 问题: 当文档有大量字段,但大部分查询只关心其中一小部分时。
  • 解决方案: 将不常用或具有相似特征的字段分组到一个子文档中。
  • 示例: 一个产品文档,将详细规格参数放到 specs 子文档中。{
     “product_id”: “123”,
     “name”: “Laptop”,
     “specs”: {
       “cpu”: “Intel i7”,
       “ram_gb”: 16,
       “storage_gb”: 512
    }
    }

扩展引用模式 (Extended Reference Pattern)

  • 问题: 在引用模型中,为了获取被引用文档的某个常用字段,需要执行额外的查询。
  • 解决方案: 在引用文档中,除了存储 _id 外,还冗余存储一些经常需要一起显示的字段。
  • 示例: 在文章(posts)集合中,除了存储作者的 author_id,还冗余存储 author_name。// posts collection
    {
     “title”: “My First Post”,
     “author_id”: “xyz”,
     “author_name”: “John Doe” // Extended Reference
    }

子集模式 (Subset Pattern)

  • 问题: 一个文档中有一个巨大的数组(如评论、日志),导致文档过大,且大部分时间只需要访问数组的最新一部分。
  • 解决方案: 将数组的一个子集(如最新的 10 条评论)与主文档存储在一起,完整的数组存储在另一个集合中。
  • 示例: 产品文档中存储最新的 5 条评论,所有评论存储在 reviews 集合。// products collection
    {
     “product_id”: “abc”,
     “name”: “Super Widget”,
     “reviews_subset”: [
      { “review_id”: 1, “text”: “Great!” },
      { “review_id”: 2, “text”: “Awesome!” }
    ]
    }

计算模式 (Computed Pattern)

  • 问题: 需要频繁计算某些值(如总数、平均值),每次读取时计算会消耗大量资源。
  • 解决方案: 在写操作时预先计算好这些值,并将其存储在文档中。当数据更新时,同步更新计算结果。
  • 示例: 在用户文档中存储其发布的帖子总数 post_count。{
     “user_id”: “user123”,
     “name”: “Jane Doe”,
     “post_count”: 42 // Computed value
    }

七、聚合框架

聚合框架是 MongoDB 提供的一个强大的数据处理工具,它允许对集合中的数据进行一系列的转换和计算,最终得到聚合后的结果。本章节将深入探讨聚合管道、常用的聚合阶段以及如何利用聚合框架进行复杂的数据分析。

聚合管道

聚合操作的核心是聚合管道,管道是由一个或者是多个阶段组成的,每个阶段都会对输入的文档的进行处理,并将结果传递给下一个阶段

聚合管道的语法

聚合操作使用 aggregate() 方法,其参数是一个包含所有阶段的数组。

db.collection.aggregate([
{ <stage1> },
{ <stage2> },
 ...
])

聚合管道的优势

  • 功能强大: 支持复杂的数据转换、分组、计算和重塑。
  • 性能高效: 许多操作在数据库服务端以原生代码执行,减少了数据在网络中的传输。
  • 灵活性高: 可以通过组合不同的阶段来满足各种复杂的数据处理需求。

下面的几个不同的聚合阶段比较常用,通过组合他们就可以实现强大的数据的处理的能力

$match

  • 功能: 过滤文档,只将满足条件的文档传递给下一个阶段。类似于 find() 方法的查询条件。
  • 建议: 尽可能将 $match 放在管道的开头,以尽早减少需要处理的文档数量,提高效率。
  • 示例: 筛选出状态为 “A” 的订单。{ $match: { status: “A” } }

$project

  • 功能: 重塑文档流。可以包含、排除、重命名字段,或者通过表达式计算新字段。
  • 示例: 只保留 _idname 字段,并创建一个新的 bonus 字段。{ $project: { name: 1, bonus: { $multiply: [“$salary”, 0.1] } } }

$group

  • 功能: 按指定的 _id 表达式对文档进行分组,并对每个分组应用累加器表达式进行计算。
  • 核心: _id 字段定义了分组的键。
  • 示例: 按 cust_id 分组,并计算每个客户的订单总金额。{
     $group: {
       _id: “$cust_id”,
       totalAmount: { $sum: “$amount” }
    }
    }

$sort

  • 功能: 对文档流进行排序,与 find() 中的 sort() 类似。
  • 建议: 如果需要排序,尽量在管道的早期阶段进行,特别是当排序字段有索引时。
  • 示例: 按 totalAmount 降序排序。{ $sort: { totalAmount: -1 } }

$limit$skip

  • 功能: 分别用于限制和跳过文档数量,实现分页。
  • 示例: 返回排序后的前 5 个结果。{ $limit: 5 }

$unwind

  • 功能: 将文档中的数组字段拆分成多个文档,数组中的每个元素都会生成一个新的文档(与其他字段组合)。
  • 示例: 将 tags 数组拆分。// 输入: { _id: 1, item: “A”, tags: [“x”, “y”] }
    { $unwind: “$tags” }
    // 输出:
    // { _id: 1, item: “A”, tags: “x” }
    // { _id: 1, item: “A”, tags: “y” }

$lookup

  • 功能: 实现左外连接(Left Outer Join),将当前集合与另一个集合的文档进行关联。
  • 示例: 将 orders 集合与 inventory 集合关联起来。{
     $lookup: {
       from: “inventory”,
       localField: “item”,
       foreignField: “sku”,
       as: “inventory_docs”
    }
    }

1. $match → 【过滤器】

就像筛子,只留下符合条件的文档,其他全部丢掉。

  • 越早用越好,少处理垃圾数据。

2. $project → 【裁剪 / 化妆】

把文档改头换面

  • 只留需要的字段
  • 删掉不要的
  • 改名
  • 算新字段

3. $group → 【分组统计】

把文档按类别堆成几堆,然后每堆算总数、平均值、最大值。

  • 按班级分组 → 每班总分
  • 按用户分组 → 每人消费总额

4. $sort → 【排队】

让文档按大小 / 高低排队

  • 1 升序(小→大)
  • -1 降序(大→小)

5. $limit & $skip → 【分页】

  • $skip:跳过前面几个人
  • $limit:只取接下来几个人合起来就是翻页。

6. $unwind → 【拆数组】

把数组拆成一条一条文档

  • 一袋糖 → 一颗一颗摆出来
  • 一个人多个标签 → 每人每条标签单独一行

7. $lookup → 【连表】

把两个集合拼在一起,像 Excel 的 VLOOKUP。

  • 订单表 + 商品表 = 完整信息

一句话终极记忆

  • $match:筛选
  • $project:改字段
  • $group:分组算
  • $sort:排序
  • *limit/*skip:分页
  • $unwind:拆数组
  • $lookup:联表

MongoDB聚合的本质就是:一条流水线,这些不同的聚合阶段就像是流水线中不同的工位

聚合累加器表达式

累加器主要在 $group 阶段使用,用于对分组后的数据进行计算。

累加器描述
$sum计算总和
$avg计算平均值
$min获取最小值
$max获取最大值
$first获取每个分组的第一条文档的字段值
$last获取每个分组的最后一条文档的字段值
$push将字段值添加到一个数组中
$addToSet将唯一的字段值添加到一个数组中

聚合管道优化

  • 尽早过滤: 将 $match 阶段放在管道的最前面。
  • 尽早投影: 使用 $project 移除不需要的字段,减少后续阶段的数据处理量。
  • 利用索引: 如果 $match$sort 阶段的字段有索引,MongoDB 可以利用它来优化性能。
  • 避免在分片键上 $unwind: 这可能会导致性能问题。

八、副本集

为了实现高可用以及数据冗余,MongoDB

什么是副本集?

副本集是一组维护相同数据集的 mongod 进程。它由一个主节点 (Primary) 和多个从节点 (Secondary) 组成,共同保证了数据的冗余和高可用性。

  • 主节点 (Primary): 接收所有的写操作。一个副本集在任何时候最多只能有一个主节点。
  • 从节点 (Secondary): 从主节点异步复制数据。从节点可以接受读操作,从而分担主节点的读负载。

副本集的目标

  1. 数据冗余 (Data Redundancy): 数据在多个服务器上有副本,防止因单台服务器硬件故障导致的数据丢失。
  2. 高可用性 (High Availability): 当主节点发生故障时,副本集会自动进行故障转移(Failover),从剩下的从节点中选举出一个新的主节点,从而保证服务的持续可用。
  3. 读写分离 (Read Scaling): 客户端可以将读请求路由到从节点,从而分散读负载,提高读取性能。

副本集架构与工作原理

副本集成员

一个典型的副本集包含以下成员:

  • 主节点 (Primary): 唯一的写操作入口。
  • 从节点 (Secondary): 复制主节点的数据,可以处理读请求。从节点也可以被配置为:
    • 优先级为 0 的成员 (Priority 0 Member): 不能被选举为主节点,适合用于备份或离线分析。
    • 隐藏成员 (Hidden Member): 对客户端不可见,不能处理读请求,通常用于备份。
    • 延迟成员 (Delayed Member): 数据会比主节点延迟一段时间,可用于恢复误操作的数据。
  • 仲裁者 (Arbiter): 只参与选举投票,不存储数据副本。它的存在是为了在成员数量为偶数时,打破平局,确保能够选举出主节点。

数据同步(复制)

  • 主节点记录其所有的操作到一个特殊的 capped collection 中,称为 oplog (operations log)
  • 从节点持续地从主节点的 oplog 中拉取新的操作,并在自己的数据集上应用这些操作,从而保持与主节点的数据同步。
  • 这个过程是异步的,因此从节点的数据可能会有轻微的延迟。

选举过程(Failover)

当主节点在一定时间内(默认为 10 秒)无法与副本集中的其他成员通信时,会触发一次选举。

  1. 触发选举: 从节点发现主节点不可达。
  2. 选举投票: 剩下的健康成员会进行投票,选举一个新的主节点。投票的依据包括成员的优先级、oplog 的新旧程度等。
  3. 产生新的主节点: 获得大多数票数((N/2) + 1,其中 N 是副本集总成员数)的从节点会成为新的主节点。
  4. 恢复同步: 旧的主节点恢复后,会作为从节点重新加入副本集。

读写关注点(Read and Write Concern)

写关注点 (Write Concern)

写关注点决定了写操作在向客户端确认成功之前,需要被多少个副本集成员确认。

  • w: 1 (默认): 写操作只需在主节点成功即可返回。
  • w: "majority": 写操作需要被大多数((N/2) + 1)数据承载成员确认后才返回。这是推荐的设置,可以防止在故障转移期间发生数据回滚。
  • j: true: 要求写操作在返回前写入到磁盘日志(journal)。

读偏好 (Read Preference)

读偏好决定了客户端从哪个成员读取数据。

  • primary (默认): 只从主节点读取,保证数据最新。
  • primaryPreferred: 优先从主节点读取,如果主节点不可用,则从从节点读取。
  • secondary: 只从从节点读取,可以分担主节点负载,但可能读到稍有延迟的数据。
  • secondaryPreferred: 优先从从节点读取,如果没有可用的从节点,则从主节点读取。
  • nearest: 从网络延迟最低的成员读取,不关心是主节点还是从节点。

副本集 = 1 个老板 + 多个员工 + 1 个裁判(可选)

  • 老板(主节点):只负责写数据,所有新增 / 修改都找他
  • 员工(从节点):抄老板的数据,帮着读数据
  • 裁判(仲裁者):不存数据,只在老板挂了时投票选新老板
  • 老板挂了 → 自动选新老板(10 秒内)
  • 读写分离:写找老板,读找员工,减轻压力
  • 数据不丢:员工都有一模一样的数据

一句话:副本集 = 备份 + 高可用 + 读写分离

九、分片

当数据量增长到单个副本集无法承载,或者写操作的吞吐量达到单台主节点的极限时,就需要通过分片(Sharding)来进行水平扩展。本章节将深入探讨 MongoDB 分片集群的架构、核心组件、分片键的选择策略以及如何部署和管理一个分片集群。


分片概述

什么是分片?

分片是一种将大型数据集水平分区到多个服务器(或分片)上的数据库架构模式。每个分片都是一个独立的副本集,存储着整个数据集的一部分。通过分片,MongoDB 可以将读写负载分布到多个服务器上,从而实现近乎无限的水平扩展能力。

为什么需要分片?

  1. 存储容量扩展: 当数据量超过单台服务器的磁盘容量时,可以通过增加分片来扩展存储空间。
  2. 读写吞吐量提升: 通过将负载分布到多个分片,可以显著提高整个集群的读写处理能力。
  3. 高可用性: 分片集群的每个分片本身就是一个副本集,因此继承了副本集的高可用性特性。

分片集群架构

一个 MongoDB 分片集群由以下三个核心组件构成:

  1. 分片 (Shard)
    • 作用: 存储数据的单元。每个分片都是一个独立的 MongoDB 副本集,以保证其高可用性。
    • 职责: 存储集合数据的一个子集(Chunk)。
  2. 查询路由 (Query Router / mongos)
    • 作用: 客户端的入口。mongos 是一个轻量级的无状态进程,它接收客户端的请求,并将其路由到正确的分片上。
    • 职责: 从配置服务器获取元数据,根据分片键将查询路由到目标分片,并聚合来自多个分片的结果返回给客户端。
  3. 配置服务器 (Config Server)
    • 作用: 存储集群的元数据。这些元数据包含了数据在各个分片上的分布情况(哪个 Chunk 在哪个 Shard)。
    • 职责: 管理集群的配置信息。从 MongoDB 3.4 开始,配置服务器必须部署为副本集(CSRS),以保证其高可用性。
Sharded Cluster Architecture

分片键(Shard Key)

分片键是决定数据如何在各个分片之间分布的关键。选择一个好的分片键至关重要,它直接影响到分片集群的性能和效率。

分片键的选择策略

一个理想的分片键应该具备以下特征:

  • 高基数 (High Cardinality): 分片键应该有大量可能的值,以便将数据均匀地分布到多个 Chunk 中。
  • 低频率 (Low Frequency): 分片键的值应该被均匀地访问,避免出现热点数据(Hot Spot)。
  • 非单调变化 (Non-Monotonic): 分片键的值不应随时间单调递增或递减,这会导致所有的写操作都集中在最后一个分片上。

分片策略

  1. 范围分片 (Ranged Sharding)
    • 描述: 根据分片键的范围将数据分成不同的块(Chunk)。
    • 优点: 对于基于范围的查询(如 find({ x: { $gt: 10, $lt: 20 } }))非常高效,因为 mongos 可以直接将查询路由到存储该范围数据的分片。
    • 缺点: 如果分片键是单调变化的(如时间戳),容易导致写操作集中在单个分片上。
  2. 哈希分片 (Hashed Sharding)
    • 描述: 计算分片键的哈希值,并根据哈希值的范围来分片。
    • 优点: 能够将数据在各个分片之间均匀分布,保证了写操作的负载均衡。
    • 缺点: 对于范围查询不友好,因为相邻的分片键值可能被哈希到不同的分片上,导致查询需要广播到所有分片。
  3. 标签感知分片 (Tag Aware Sharding)
    • 描述: 允许管理员通过标签(Tag)将特定范围的数据块(Chunk)分配到特定的分片上。例如,可以将美国用户的数据放在位于美国的服务器上,以降低延迟。

Chunks 和 Balancer

数据块 (Chunk)

  • Chunk 是分片集合中一段连续的数据范围(基于分片键)。MongoDB 会试图保持 Chunk 的大小在一个可配置的范围内(默认为 64MB)。
  • 当一个 Chunk 的大小超过配置值时,它会分裂成两个更小的 Chunk。

均衡器 (Balancer)

  • Balancer 是一个后台进程,它负责在各个分片之间迁移 Chunk,以确保数据在整个集群中均匀分布。
  • 当某个分片的 Chunk 数量远多于其他分片时,Balancer 会自动启动,并将一些 Chunk 从最拥挤的分片迁移到最空闲的分片。
  • 均衡过程会消耗 I/O 和网络资源,可以在业务高峰期临时禁用 Balancer。

分片 = 把一个超大数据库,切成多块,存在不同服务器

  • 数据太大 / 访问太猛 → 一台机器扛不住 → 分片
  • 每个分片都是一个副本集(自己也能高可用)
  • mongos(路由):你不用管数据在哪,它帮你找
  • 配置服务器:记录哪块数据存在哪
  • 分片键:决定数据怎么切(最重要)
  • 均衡器:自动把数据挪均匀,不让某台机器太累

一句话:分片 = 水平扩容 + 扛海量数据 + 高并发

十、安全管理

确保数据库的安全是任何的软件都需要考虑并且重视的。而mongodb就提供一系列的安全保障,比如说:认证、授权、加密等部分,以避免一些数据未经过授权就被访问或者篡改,所以学好本节内容对于保护数据库的安全至关重要!

  • 认证:验证身份确认你是某某
  • 授权:确定你是某某之后看你能干什么事情
  • 加密:保护数据在传输过程(TLS/SSL)和静态存储时(Encryption at Rest)的安全。
  • 审计:记录数据库活动并进行安全分析以及合规性的检查

SSL /TLS 部分复习:

一、基础概念

1. SSL / TLS 区别

  • SSL:老旧加密协议,现已淘汰
  • TLS:SSL 升级版,现在统称SSL 加密,实际用的都是 TLS作用:客户端和数据库之间通信数据全程加密,别人抓包看不懂内容。

2. 实现原理

  1. 服务端 (MongoDB) 持有证书 + 私钥
  2. 客户端连接时先校验证书合法性
  3. 双方协商加密算法,建立加密通道
  4. 所有 SQL、账号密码、业务数据密文传输

3. 两种证书

  • 自签名证书:内网、测试、企业内网自用,免费,SRE 运维最常用
  • CA 正规证书:公网对外服务使用,需付费申请

二、MongoDB 开启 TLS/SSL 完整清晰步骤(生产可用)

环境

MongoDB 4.0+ 通用,单实例 / 副本集都能用

步骤 1:创建证书存放目录

mkdir -p /data/mongodb/ssl
cd /data/mongodb/ssl

步骤 2:生成自签名 SSL 证书(一键生成)

# 生成 pem 整合证书(包含证书+私钥)
openssl req -newkey rsa:2048 -nodes -keyout mongodb.key -x509 -days 3650 -out mongodb.crt

# 合并成 MongoDB 需要的 pem 文件
cat mongodb.crt mongodb.key > mongodb.pem

一路回车填空即可,内网不需要填真实信息。

步骤 3:授权安全权限(SRE 必做)

chmod 600 mongodb.pem
chown mongod:mongod mongodb.pem

步骤 4:修改 mongod.conf 开启 TLS

日常说配置 SSL、HTTPS,本质全是 TLS

net:
port: 27017
bindIp: 0.0.0.0
tls:
mode: requireTLS # 强制所有连接必须走TLS,拒绝明文
certificateKeyFile: /data/mongodb/ssl/mongodb.pem
# CAFile: 第三方证书才配置,自签名不用

步骤 5:重启 MongoDB 生效

systemctl restart mongod

步骤 6:SSL 方式连接数据库

1)本地命令行连接

mongosh --ssl --sslPEMKeyFile /data/mongodb/ssl/mongodb.pem -u 用户名 -p 密码 --authenticationDatabase admin

2)禁止明文连接

不开--ssl直接连接会直接报错拒绝,达到安全目的。

步骤 7:程序代码连接(Java/Go/Python 统一逻辑)

核心参数只加两个:

  1. 开启 ssl=true
  2. 信任自签名证书 allowInvalidCertificates=true

三、三种 TLS 模式(SRE 必记)

  1. disabled:关闭 SSL,明文传输(开发环境用)
  2. preferTLS:优先加密,也允许明文(不推荐生产)
  3. requireTLS强制加密,所有连接必须 SSL(生产唯一标准)

四、极简总结

  1. SSL=TLS,就是给数据库通信加锁
  2. 内网运维直接用openssl 生成自签名证书最省事
  3. 生产一律 requireTLS 强制加密
  4. 只加密传输流量,硬盘数据依旧明文
  5. 搭配账号认证 = MongoDB 最基础双线安全防护

1. 认证

启用认证是保护数据库的第一步。当认证启用后,所有客户端和数据库节点之间的连接都必须提供有效的凭据。

启用认证

mongod.conf 配置文件中或通过命令行参数启用认证:

# mongod.conf
security:
authorization: enabled

或者

mongod --auth

认证方法

MongoDB 支持多种认证机制,最常用的是 SCRAM (Salted Challenge Response Authentication Mechanism)

  • SCRAM-SHA-1: 默认机制。
  • SCRAM-SHA-256: 更强的加密算法,建议在 MongoDB 4.0 及以上版本中使用。

创建管理员用户

在启用认证之前,必须先创建一个具有 userAdminAnyDatabase 角色的用户管理员。这个用户将用于创建和管理其他用户。

  1. 以无认证模式启动 mongod
  2. 连接到 admin 数据库并创建用户:use admin
    db.createUser({
    user: “myUserAdmin”,
    pwd: passwordPrompt(), // or a plain-text password
    roles: [ { role: “userAdminAnyDatabase”, db: “admin” } ]
    })
  3. 重启 mongod 并启用认证

自己的理解:

启用认证(开密码锁)

创建管理员用户(配一把总钥匙)

重启 MongoDB 让认证生效(锁上门,开始查身份)

1)启用认证 = 给 MongoDB 装上门锁

  • 默认 MongoDB 没有门锁,谁都能连、谁都能删库
  • authorization: enabled--auth意思就是:从此以后,不刷卡不让进

我的理解:

认证就是 “登录验证”,

不开认证 = 数据库裸奔

开了认证 = 必须用户名密码才能访问


2)创建管理员用户 = 配唯一的总控钥匙

为什么必须先建管理员?

因为开了锁之后,第一个能进门的人必须提前建好

我的理解:

userAdminAnyDatabase唯一能管理所有用户的账号

它只能管人,不能随便改业务数据

这是安全基线:先有管理员,才能创建其他业务用户

一句话:

不开锁 → 先配钥匙 → 再锁门


3)重启 MongoDB = 把门锁真正扣上

你改了配置、建了用户,

不重启,配置不生效

重启后:

  • 所有连接必须验证账号密码
  • 无凭据直接拒绝
  • 管理员可以继续创建业务用户

你的理解:

重启 = 让安全配置 “落地”

从这一刻开始,数据库进入安全模式

为什么要按这个顺序?不能乱?

错误顺序会发生什么?

  1. 先开启认证 → 再想创建用户→ 直接被锁死,永远进不去

正确顺序为什么是:

无认证启动 → 创建管理员 → 开启认证 → 重启

因为:

  1. 无认证启动:才能进去创建用户
  2. 创建管理员:拥有唯一的用户管理权
  3. 开启认证:告诉 MongoDB 要验证身份
  4. 重启:让安全策略生效

(管理开放>管理锁的人>配锁>上锁 )

这是数据库安全的标准 “初始化流程”

所有数据库(MySQL、PostgreSQL、Redis)都这个逻辑。

2. 授权

MongoDB 使用基于角色的访问控制(Role-Based Access Control, RBAC)来管理用户权限。权限被定义为角色 (Role),然后将角色分配给用户。

内置角色 (Built-In Roles)

MongoDB 提供了一系列预定义的角色,涵盖了常见的管理和操作需求。

  • 数据库用户角色: read, readWrite
  • 数据库管理员角色: dbAdmin, dbOwner, userAdmin
  • 集群管理员角色: clusterAdmin, clusterManager, hostManager
  • 备份与恢复角色: backup, restore
  • 所有数据库角色: readAnyDatabase, readWriteAnyDatabase, userAdminAnyDatabase, dbAdminAnyDatabase
  • 超级用户角色: root (拥有所有权限)

自定义角色 (Custom Roles)

如果内置角色无法满足精细化权限控制需求,可以创建自定义角色。

use myAppDB
db.createRole({
 role: "salesDataViewer",
 privileges: [
  { resource: { db: "myAppDB", collection: "sales" }, actions: ["find"] }
],
 roles: []
})

管理用户和角色

  • db.createUser(): 创建用户。
  • db.updateUser(): 更新用户信息(如密码、角色)。
  • db.dropUser(): 删除用户。
  • db.createRole(): 创建角色。
  • db.grantRolesToUser(): 为用户授予角色。
  • db.revokeRolesFromUser(): 撤销用户的角色。

3.网络加密 (TLS/SSL)

为了保护数据在网络传输过程中的安全,防止窃听和中间人攻击,应该为 MongoDB 部署启用 TLS/SSL 加密。

配置 TLS/SSL

  1. 获取 TLS/SSL 证书: 可以使用自签名证书(用于内部测试)或从证书颁发机构 (CA) 获取证书。
  2. 配置 mongodmongos: 在配置文件中指定证书文件、私钥文件和 CA 文件。net:
    tls:
    mode: requireTLS
    certificateKeyFile: /path/to/mongodb.pem
    CAFile: /path/to/ca.pem
  3. 客户端连接: 客户端在连接时也需要指定 TLS/SSL 选项。mongo –ssl –sslCAFile /path/to/ca.pem –sslPEMKeyFile /path/to/client.pem …

4. 静态数据加密

除了网络加密,还有躺在磁盘中的数据等待加密

防止物理服务器数据泄露

服务器硬盘被盗、机房人员私自拷贝数据盘、运维私自拖库,拿到文件也读不出明文数据

满足等保、金融、政务合规要求

涉密业务、用户隐私、交易数据,政策强制要求磁盘落地加密

防止离线窃取

别人直接挂载你的数据盘,无法解析库文件内容

双层防护

传输防抓包 + 落地防窃取,安全等级拉满

TLS(传输)

客户端与服务端通信加密,硬盘数据依旧明文

硬盘被偷走直接泄露数据

静态加密(落地)

硬盘文件全是密文,内网明文访问不受影响

只防本地离线窃取

总结:

静态数据加密是数据库离线安全防护手段,专门应对服务器硬盘被盗、数据文件被私自拷贝导致的数据泄露。

分为数据库引擎层原生加密与操作系统磁盘级加密两种,前者精细化、依赖企业版,后者通用免费适配所有版本,在 TLS 网络加密之外,筑牢数据落地最后一道安全防线。

十一、MongoDB 的备份与恢复

制定可靠的备份以及恢复的策略是数据库管理中的至关重要的一节,他可以帮助在发生数据的损坏、误操作、或者是灾难性故障的时候恢复数据,这节会介绍MongoDB的常用的备份的方法以及的恢复流程以及灾难恢复的最佳的实践。

选取不同的备份的恢复的策略是根据需求来的,如恢复时间目标 (RTO)、恢复点目标 (RPO)、数据库规模以及部署架构(独立实例、副本集、分片集群)。

备份方法

  1. mongodumpmongorestore
    • 描述: MongoDB 提供的官方命令行工具,用于创建数据库的 BSON 文件备份,并可以从这些文件恢复数据。
    • 优点: 简单易用,适合小型数据集或对备份窗口要求不高的场景。
    • 缺点: 备份期间可能会影响数据库性能。对于大型数据集,备份和恢复过程可能非常耗时。在恢复 mongodump 的备份时,它会重建索引,这会增加恢复时间。
  2. 文件系统快照 (Filesystem Snapshots)
    • 描述: 利用文件系统(如 LVM)或云存储(如 AWS EBS)的快照功能,对 MongoDB 的数据文件进行即时备份。
    • 优点: 备份速度快,对数据库性能影响小。恢复速度也很快,因为数据和索引都无需重建。
    • 缺点: 必须确保在创建快照时,数据处于一致性状态。这通常需要开启 journaling 并确保在快照前执行 fsync 和 lock 操作。
  3. MongoDB Cloud Manager / Ops Manager
    • 描述: MongoDB 提供的企业级管理工具,提供持续的、时间点恢复的备份服务。
    • 优点: 提供图形化界面,管理方便。支持副本集和分片集群的持续备份和时间点恢复,可以恢复到任意时刻。
    • 缺点: 是商业服务,需要额外成本。
  1. 文件系统快照(LVM 快照 / 云盘快照) —— 互联网大厂最常用
  2. MongoDB 官方云管理平台(Ops Manager/Cloud Manager) —— 中大型企业、政企、付费业务线
  3. mongodump/mongorestore —— 测试环境、小业务、临时备份

某些应用场景

互联网高并发业务:优先文件系统快照,性能损耗极小,恢复快,满足线上 7×24 小时不停服备份。

金融 / 支付 / 核心交易:使用MongoDB Ops Manager,依靠时间点恢复保障数据零丢失。

测试环境、离线业务:直接使用mongodump简单快捷。

线上禁止高峰期执行 mongodump,容易锁库拖慢业务。

mongodump

mongodump 可以导出整个数据库、特定数据库或特定集合。

mongorestore

mongorestore 用于将 mongodump 创建的备份恢复到数据库中。

备份副本集

  • 使用 mongodump: 建议连接到一个从节点进行备份,以减少对主节点的影响。可以使用 --oplog 选项来创建一个包含 oplog 条目的时间点快照,这对于在恢复后与其他副本集成员同步非常重要。mongodump –host secondary.example.com –oplog –out /data/backup/repl_set_backup
  • 使用文件系统快照: 可以在一个被停止(或锁定)的从节点上进行,以保证数据一致性。

备份分片集群

备份分片集群要复杂得多,因为必须保证整个集群的数据是一致的。

  • 核心挑战: 必须在同一时刻捕获所有分片和配置服务器的数据快照。
  • 推荐方法: 强烈建议使用 MongoDB Cloud Manager 或 Ops Manager 来处理分片集群的备份,因为它们专门设计用于处理这种复杂性。
  • 手动备份(不推荐,风险高):
    1. 禁用 Balancer。
    2. 对配置服务器进行 mongodump
    3. 对每个分片副本集进行 mongodump
    4. 重新启用 Balancer。 恢复过程同样复杂且容易出错。

恢复策略与灾难恢复

恢复误操作的数据

  • 使用延迟从节点: 如果配置了一个延迟从节点,可以停止它的复制,从其数据文件中恢复误删或误改的数据。
  • 使用时间点恢复: 如果使用 Cloud Manager / Ops Manager,可以将数据库恢复到误操作发生前的任意时间点。

灾难恢复 (Disaster Recovery)

灾难恢复计划旨在应对整个数据中心或区域级别的故障。

  • 异地备份: 将备份数据存储在与生产数据中心物理位置不同的地方。
  • 多数据中心部署: 将副本集的成员分布在不同的地理位置。例如,一个主节点和从节点在主数据中心,另一个从节点在灾备数据中心。当主数据中心发生故障时,可以手动或自动将灾备中心的从节点提升为新的主节点。

十二、性能优化与调优

为了确保MongoDB数据库的持续稳定高效运行,必须进行有效的性能的监控以及及时的调优。

这节会介绍MongoDB的关键性的指标,常用的监控的工具以及针对常见的性能的问题的调优的策略。

关键的性能指标

它们的实际用途 = 帮你快速定位:MongoDB 为什么变慢、为什么卡顿、为什么宕机、为什么副本集同步失败

操作计数器 (Operation Counters)

  • opcounters: 显示自 mongod 启动以来执行的数据库操作(insert, query, update, delete, getmore, command)的总数。通过观察其增长率,可以了解数据库的负载情况。

锁 (Locks)

  • globalLock: 反映全局锁的使用情况。在 WiredTiger 存储引擎中,全局锁的使用已大大减少,但仍需关注。
  • locks: 提供数据库、集合等更细粒度锁的信息。长时间的锁等待(timeAcquiringMicros)可能表示存在锁竞争,需要优化查询或索引。

网络 (Network)

  • network.bytesIn / network.bytesOut: 进出数据库的网络流量。
  • network.numRequests: 接收到的请求总数。
  • 监控网络指标有助于发现网络瓶颈或异常的客户端行为。

内存 (Memory)

  • mem.resident: 进程占用的物理内存(RAM)大小。
  • mem.virtual: 进程使用的虚拟内存大小。
  • mem.mapped: 内存映射文件的大小。
  • 监控内存使用情况,特别是 WiredTiger 的内部缓存(wiredTiger.cache),对于确保性能至关重要。

Oplog

  • Oplog Window: Oplog 中记录的操作所覆盖的时间范围。如果 oplog window 太小,从节点可能会因为跟不上主节点的更新速度而掉队(stale)。

慢查询 (Slow Queries)

  • system.profile 集合: 当开启数据库分析(profiling)后,执行时间超过阈值的查询会被记录到该集合中。这是识别和优化慢查询的主要工具。

这六个指标:

opcounters → 看忙不忙

locks → 看是不是排队、阻塞

network → 看是不是流量太大

memory → 看是不是内存不够

oplog → 看副本会不会掉队

slow queries → 抓出慢查询元凶(最常用)

这些指标 = 你排查 MongoDB 性能问题的全部武器

99% 的线上卡顿、变慢、超时,都靠这 6 项定位解决。

监控工具

mongostat

  • 描述: 一个命令行工具,可以实时地、逐秒地显示 MongoDB 实例的主要性能指标,类似于 Linux 的 vmstat
  • 用途: 快速了解数据库当前的操作负载和性能状况。mongostat

mongotop

  • 描述: 一个命令行工具,用于跟踪 MongoDB 实例花费时间最多的读写操作。
  • 用途: 快速定位哪些集合是当前系统的性能热点。mongotop 10 # 每 10 秒刷新一次

db.serverStatus()

  • 描述: 一个数据库命令,返回一个包含大量服务器状态信息的文档。
  • 用途: 获取详细的、全面的性能指标,是大多数监控系统的主要数据来源。db.serverStatus()

MongoDB Cloud Manager / Ops Manager

  • 描述: 提供了一个功能强大的图形化监控界面,可以收集、可视化并告警各种性能指标。
  • 用途: 长期、系统化的性能监控和趋势分析。

性能调优策略

索引优化

  • 识别缺失的索引: 通过分析 system.profile 集合中的慢查询日志,找出那些因为缺少合适索引而导致全集合扫描(COLLSCAN)的查询。
  • 评估现有索引: 使用 $indexStats 聚合阶段检查索引的使用频率。对于很少使用或从不使用的索引,应考虑删除以减少写操作的开销和存储空间。
  • 创建复合索引: 根据 ESR 法则设计高效的复合索引,使其能够覆盖多种查询模式。

查询优化

  • 使用投影 (Projection): 只返回查询所需的字段,减少网络传输量和客户端处理数据的负担。
  • 避免使用 $where$function: 这类操作无法使用索引,并且会在每个文档上执行 JavaScript,性能极差。
  • 优化正则表达式: 尽量使用前缀表达式(如 /^prefix/),这样可以利用索引。避免使用不区分大小写的正则表达式,因为它无法有效利用索引。

Schema 设计调优

  • 遵循数据建模模式: 根据应用的读写模式选择合适的建模策略(嵌入 vs. 引用),可以从根本上提升性能。
  • 避免大文档和无界数组: 过大的文档会增加内存使用和网络传输,无限制增长的数组会给更新操作带来性能问题。

硬件和拓扑结构调优

  • 内存: 确保 WiredTiger 的缓存大小(默认为 (RAM - 1GB) / 2)足够容纳工作集(Working Set),即最常访问的数据和索引。
  • 磁盘: 使用 SSD 可以显著提升 I/O 性能,特别是对于写密集型应用。
  • 网络: 确保数据库服务器之间的网络延迟尽可能低,特别是在分片集群和地理分布的副本集中。
  • 读写分离: 在读密集型应用中,通过将读请求路由到从节点来扩展读取能力。
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇