一、基础概念
作为基础
一、定义
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 原则,保证最终一致性 |
| 适用场景 | 事务性要求高、数据结构稳定的应用 | 大数据、高并发、需要快速迭代的应用 |
重点:
- Schema :字段灵活并且文档的结构可以不同
- 扩展性:天然适合水平扩展
- 事务:偏向最终一致性
三、NoSQL数据库的分类
NoSQL 数据库根据其数据模型的不同,主要分为以下几类:
- 文档型数据库 (Document-Oriented)
- 特点:以文档(通常是 JSON 或 BSON 格式)为单位存储数据,结构灵活。
- 代表:MongoDB, CouchDB
- 键值型数据库 (Key-Value)
- 特点:数据以简单的键值对形式存储,查询速度快。
- 代表:Redis, Memcached
- 列族型数据库 (Column-Family)
- 特点:数据按列族存储,适合大规模数据聚合分析。
- 代表:Cassandra, HBase
- 图形型数据库 (Graph)
- 特点:专注于节点和边的关系,适合社交网络、推荐系统等场景。
- 代表:Neo4j, ArangoDB
- MongoDB在NoSQL生态中的定位是?
- 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) |
|---|---|
| Database | Database |
| Collection | Table |
| Document | Row (or Record) |
| Field | Column (or Attribute) |
| Index | Index |
| Replica Set | Master-Slave Replication |
| Sharding | Partitioning |
架构原理
了解 MongoDB 的底层架构有助于我们更好地进行性能调优和故障排查。
存储引擎(WiredTiger)
WiredTiger 是 MongoDB 默认的存储引擎,它提供了文档级别的并发控制、数据压缩和快照等高级功能,是 MongoDB 高性能的关键。
内存映射文件系统
MongoDB 使用内存映射文件(Memory-Mapped Files)来处理数据。它将磁盘上的数据文件映射到内存中,使得 MongoDB 可以像访问内存一样访问数据,从而将数据管理委托给操作系统的虚拟内存管理器,简化了代码并提升了性能。
索引机制
为了提高查询效率,MongoDB 支持在任意字段上创建索引。与关系型数据库类似,MongoDB 的索引也采用 B-Tree 数据结构,能够极大地加速数据检索过程。
查询优化器
MongoDB 内置了一个查询优化器,它会分析查询请求,并从多个可能的查询计划中选择一个最优的执行计划,以确保查询的高效性。
二、环境搭建(ARM 架构)
这里将会详细介绍如何在Rocky Linux的操作系统上安装以及配置MongoDB以及如何进行管理MongoDB服务,正确的环境的搭建是后续学习的基础
安装过程
- 创建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
- 清理缓存并安装:
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)启动失败故障复盘
一、故障现象
- 执行
systemctl start mongod启动服务失败,systemctl status mongod显示failed (Result: exit-code),进程状态code=exited, status=1/FAILURE。 - 日志关键报错:
Failed to open /var/log/mongodb/mongod.log(FileNotOpen错误码)。 - 手动
mongosh连接提示ECONNREFUSED 127.0.0.1:27017,端口无监听。
二、根因分析
核心原因是 systemd 服务运行用户权限不匹配,结合 ARM 架构安装的特殊场景,形成连锁问题:
- 权限主体不匹配:MongoDB 服务默认以系统用户
mongod运行,但/var/log/mongodb、/var/lib/mongo目录 / 文件均由root创建,mongod用户无写入权限,导致服务启动时无法初始化日志文件,直接退出。 - 配置与架构不兼容(前置坑):最初安装时误用了
x86_64架构的 yum 源,导致依赖包安装不全,后续虽修正为aarch64源,但服务权限问题未同步修复。 - 服务模式与手动模式的差异:
systemd服务有独立的用户隔离机制,手动root启动可直接写入文件,但服务模式下用户切换后权限失效。
三、完整复现流程(踩坑全过程)
- 错误安装:直接执行
yum install mongodb-org,默认拉取x86_64包,因架构不兼容报错,依赖(mongodb-mongosh、mongodb-org-database)无法安装。 - 修正安装:配置
aarch64架构 yum 源,成功安装 MongoDB 7.0,但未处理目录权限。 - 服务启动失败:执行
systemctl start mongod报错,日志显示无法打开日志文件。 - 临时启动验证:用
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
五、避坑总结(给后人的关键提示)
- 架构优先检查:ARM 架构(aarch64)安装 MongoDB,必须配置对应架构的 yum 源,否则依赖包会全部缺失,不要直接用默认源安装。
- 权限是服务模式的核心:所有数据目录、日志目录的所有权必须与服务运行用户一致(MongoDB 默认是
mongod:mongod),不要用root创建后忘记修改权限。 - 故障定位三步走:
- 第一步:
journalctl -u mongod看日志,优先找文件 / 权限相关报错; - 第二步:用
root手动启动mongod验证数据库本身是否正常; - 第三步:检查
systemd服务文件的User/Group配置是否正确。
- 第一步:
- 测试用临时方案兜底:如果服务模式一直报错,先手动启动确认数据库可用,再回头排查服务配置,避免卡在启动阶段无法验证功能。
六、额外提示(可选优化)
启动后若出现 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(): 显示当前数据库的状态信息。exit或quit(): 退出 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 } })
实践操作:
- 创建与切换: 创建一个名为
bookstore的数据库并切换过去。 - 插入数据: 在
bookstore数据库中创建一个books集合,并批量插入至少 5 本书的数据,每本书包含title,author,published_year,genres(数组),stock(库存) 字段。 - 查询练习:
- 查询所有库存量小于 10 本的书。
- 查询所有
Science Fiction类型的书。
- 更新练习: 将指定一本书的库存量增加 5。
- 删除练习: 删除所有
published_year在 1950 年之前的书。 - 数据导入导出: 使用
mongoexport将books集合导出为 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 (数组)。
需求描述
- 查询练习:
- 查询所有主修 ‘Computer Science’ 且 GPA 大于 3.5 的学生。
- 查询年龄在 20 到 22 岁之间(含)的学生。
- 查询选修了 ‘Database Systems’ 和 ‘Data Structures’ 两门课的学生。
- 投影练习:
- 只返回学生的姓名和专业。
- 排序和分页练习:
- 按 GPA 降序显示前 10 名学生。
- 正则表达式练习:
- 查询所有姓 ‘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 支持多种类型的索引,以适应不同的查询需求。
- 单字段索引 (Single Field Index)
- 最基础的索引类型,针对单个字段创建。
- 示例:
db.inventory.createIndex({ price: 1 })(按价格升序)
- 复合索引 (Compound Index)
- 在多个字段上创建的索引。字段的顺序非常重要,决定了索引的查询效率。
- 示例:
db.inventory.createIndex({ price: 1, qty: -1 })(先按价格升序,再按数量降序)
- 多键索引 (Multikey Index)
- 为数组字段创建的索引。MongoDB 会为数组中的每个元素创建一条索引项。
- 示例:
db.inventory.createIndex({ tags: 1 })(为tags数组中的每个标签创建索引)
- 文本索引 (Text Index)
- 用于支持对字符串内容的文本搜索查询。
- 示例:
db.inventory.createIndex({ description: "text" })
- 地理空间索引 (Geospatial Index)
- 用于高效地查询地理空间坐标数据。
- 类型:
2dsphere(用于球面几何) 和2d(用于平面几何)。 - 示例:
db.places.createIndex({ location: "2dsphere" })
- 哈希索引 (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")
重点看:
- stage
IXSCAN:索引命中 ✅COLLSCAN:全表扫描 ❌ 慢死
- totalDocsExamined = 0 → 覆盖查询 ✅ 极致快
- 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: {} }])
七、核心原则
- 查询频繁的字段必须加索引
- 复合索引严格遵循 ESR 顺序
- 能做覆盖查询就一定要做
- explain 必须看,COLLSCAN 绝对不允许上线
- 索引不是越多越好,多了会拖慢写入
- 唯一、稀疏、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(引用))
下面是常见的数据建模的模式
- 属性模式
- 问题: 当文档有大量字段,但大部分查询只关心其中一小部分时。
- 解决方案: 将不常用或具有相似特征的字段分组到一个子文档中。
- 示例: 一个产品文档,将详细规格参数放到
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
- 功能: 重塑文档流。可以包含、排除、重命名字段,或者通过表达式计算新字段。
- 示例: 只保留
_id、name字段,并创建一个新的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): 从主节点异步复制数据。从节点可以接受读操作,从而分担主节点的读负载。
副本集的目标
- 数据冗余 (Data Redundancy): 数据在多个服务器上有副本,防止因单台服务器硬件故障导致的数据丢失。
- 高可用性 (High Availability): 当主节点发生故障时,副本集会自动进行故障转移(Failover),从剩下的从节点中选举出一个新的主节点,从而保证服务的持续可用。
- 读写分离 (Read Scaling): 客户端可以将读请求路由到从节点,从而分散读负载,提高读取性能。
副本集架构与工作原理
副本集成员
一个典型的副本集包含以下成员:
- 主节点 (Primary): 唯一的写操作入口。
- 从节点 (Secondary): 复制主节点的数据,可以处理读请求。从节点也可以被配置为:
- 优先级为 0 的成员 (Priority 0 Member): 不能被选举为主节点,适合用于备份或离线分析。
- 隐藏成员 (Hidden Member): 对客户端不可见,不能处理读请求,通常用于备份。
- 延迟成员 (Delayed Member): 数据会比主节点延迟一段时间,可用于恢复误操作的数据。
- 仲裁者 (Arbiter): 只参与选举投票,不存储数据副本。它的存在是为了在成员数量为偶数时,打破平局,确保能够选举出主节点。
数据同步(复制)
- 主节点记录其所有的操作到一个特殊的 capped collection 中,称为 oplog (operations log)。
- 从节点持续地从主节点的 oplog 中拉取新的操作,并在自己的数据集上应用这些操作,从而保持与主节点的数据同步。
- 这个过程是异步的,因此从节点的数据可能会有轻微的延迟。
选举过程(Failover)
当主节点在一定时间内(默认为 10 秒)无法与副本集中的其他成员通信时,会触发一次选举。
- 触发选举: 从节点发现主节点不可达。
- 选举投票: 剩下的健康成员会进行投票,选举一个新的主节点。投票的依据包括成员的优先级、oplog 的新旧程度等。
- 产生新的主节点: 获得大多数票数(
(N/2) + 1,其中 N 是副本集总成员数)的从节点会成为新的主节点。 - 恢复同步: 旧的主节点恢复后,会作为从节点重新加入副本集。
读写关注点(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 可以将读写负载分布到多个服务器上,从而实现近乎无限的水平扩展能力。
为什么需要分片?
- 存储容量扩展: 当数据量超过单台服务器的磁盘容量时,可以通过增加分片来扩展存储空间。
- 读写吞吐量提升: 通过将负载分布到多个分片,可以显著提高整个集群的读写处理能力。
- 高可用性: 分片集群的每个分片本身就是一个副本集,因此继承了副本集的高可用性特性。
分片集群架构
一个 MongoDB 分片集群由以下三个核心组件构成:
- 分片 (Shard)
- 作用: 存储数据的单元。每个分片都是一个独立的 MongoDB 副本集,以保证其高可用性。
- 职责: 存储集合数据的一个子集(Chunk)。
- 查询路由 (Query Router /
mongos)- 作用: 客户端的入口。
mongos是一个轻量级的无状态进程,它接收客户端的请求,并将其路由到正确的分片上。 - 职责: 从配置服务器获取元数据,根据分片键将查询路由到目标分片,并聚合来自多个分片的结果返回给客户端。
- 作用: 客户端的入口。
- 配置服务器 (Config Server)
- 作用: 存储集群的元数据。这些元数据包含了数据在各个分片上的分布情况(哪个 Chunk 在哪个 Shard)。
- 职责: 管理集群的配置信息。从 MongoDB 3.4 开始,配置服务器必须部署为副本集(CSRS),以保证其高可用性。
分片键(Shard Key)
分片键是决定数据如何在各个分片之间分布的关键。选择一个好的分片键至关重要,它直接影响到分片集群的性能和效率。
分片键的选择策略
一个理想的分片键应该具备以下特征:
- 高基数 (High Cardinality): 分片键应该有大量可能的值,以便将数据均匀地分布到多个 Chunk 中。
- 低频率 (Low Frequency): 分片键的值应该被均匀地访问,避免出现热点数据(Hot Spot)。
- 非单调变化 (Non-Monotonic): 分片键的值不应随时间单调递增或递减,这会导致所有的写操作都集中在最后一个分片上。
分片策略
- 范围分片 (Ranged Sharding)
- 描述: 根据分片键的范围将数据分成不同的块(Chunk)。
- 优点: 对于基于范围的查询(如
find({ x: { $gt: 10, $lt: 20 } }))非常高效,因为mongos可以直接将查询路由到存储该范围数据的分片。 - 缺点: 如果分片键是单调变化的(如时间戳),容易导致写操作集中在单个分片上。
- 哈希分片 (Hashed Sharding)
- 描述: 计算分片键的哈希值,并根据哈希值的范围来分片。
- 优点: 能够将数据在各个分片之间均匀分布,保证了写操作的负载均衡。
- 缺点: 对于范围查询不友好,因为相邻的分片键值可能被哈希到不同的分片上,导致查询需要广播到所有分片。
- 标签感知分片 (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. 实现原理
- 服务端 (MongoDB) 持有证书 + 私钥
- 客户端连接时先校验证书合法性
- 双方协商加密算法,建立加密通道
- 所有 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 统一逻辑)
核心参数只加两个:
- 开启
ssl=true - 信任自签名证书
allowInvalidCertificates=true
三、三种 TLS 模式(SRE 必记)
disabled:关闭 SSL,明文传输(开发环境用)preferTLS:优先加密,也允许明文(不推荐生产)requireTLS:强制加密,所有连接必须 SSL(生产唯一标准)
四、极简总结
- SSL=TLS,就是给数据库通信加锁
- 内网运维直接用openssl 生成自签名证书最省事
- 生产一律
requireTLS强制加密 - 只加密传输流量,硬盘数据依旧明文
- 搭配账号认证 = 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 角色的用户管理员。这个用户将用于创建和管理其他用户。
- 以无认证模式启动
mongod。 - 连接到
admin数据库并创建用户:use admin
db.createUser({
user: “myUserAdmin”,
pwd: passwordPrompt(), // or a plain-text password
roles: [ { role: “userAdminAnyDatabase”, db: “admin” } ]
}) - 重启
mongod并启用认证。
自己的理解:
启用认证(开密码锁)
创建管理员用户(配一把总钥匙)
重启 MongoDB 让认证生效(锁上门,开始查身份)
1)启用认证 = 给 MongoDB 装上门锁
- 默认 MongoDB 没有门锁,谁都能连、谁都能删库
authorization: enabled或--auth意思就是:从此以后,不刷卡不让进
我的理解:
认证就是 “登录验证”,
不开认证 = 数据库裸奔
开了认证 = 必须用户名密码才能访问
2)创建管理员用户 = 配唯一的总控钥匙
为什么必须先建管理员?
因为开了锁之后,第一个能进门的人必须提前建好。
我的理解:
userAdminAnyDatabase是唯一能管理所有用户的账号它只能管人,不能随便改业务数据
这是安全基线:先有管理员,才能创建其他业务用户
一句话:
不开锁 → 先配钥匙 → 再锁门
3)重启 MongoDB = 把门锁真正扣上
你改了配置、建了用户,
不重启,配置不生效。
重启后:
- 所有连接必须验证账号密码
- 无凭据直接拒绝
- 管理员可以继续创建业务用户
你的理解:
重启 = 让安全配置 “落地”
从这一刻开始,数据库进入安全模式
为什么要按这个顺序?不能乱?
错误顺序会发生什么?
- 先开启认证 → 再想创建用户→ 直接被锁死,永远进不去
正确顺序为什么是:
无认证启动 → 创建管理员 → 开启认证 → 重启
因为:
- 无认证启动:才能进去创建用户
- 创建管理员:拥有唯一的用户管理权
- 开启认证:告诉 MongoDB 要验证身份
- 重启:让安全策略生效
(管理开放>管理锁的人>配锁>上锁 )
这是数据库安全的标准 “初始化流程”
所有数据库(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
- 获取 TLS/SSL 证书: 可以使用自签名证书(用于内部测试)或从证书颁发机构 (CA) 获取证书。
- 配置
mongod和mongos: 在配置文件中指定证书文件、私钥文件和 CA 文件。net:
tls:
mode: requireTLS
certificateKeyFile: /path/to/mongodb.pem
CAFile: /path/to/ca.pem - 客户端连接: 客户端在连接时也需要指定 TLS/SSL 选项。mongo –ssl –sslCAFile /path/to/ca.pem –sslPEMKeyFile /path/to/client.pem …
4. 静态数据加密
除了网络加密,还有躺在磁盘中的数据等待加密
防止物理服务器数据泄露
服务器硬盘被盗、机房人员私自拷贝数据盘、运维私自拖库,拿到文件也读不出明文数据
满足等保、金融、政务合规要求
涉密业务、用户隐私、交易数据,政策强制要求磁盘落地加密
防止离线窃取
别人直接挂载你的数据盘,无法解析库文件内容
双层防护
传输防抓包 + 落地防窃取,安全等级拉满
TLS(传输)
客户端与服务端通信加密,硬盘数据依旧明文
硬盘被偷走直接泄露数据
静态加密(落地)
硬盘文件全是密文,内网明文访问不受影响
只防本地离线窃取
总结:
静态数据加密是数据库离线安全防护手段,专门应对服务器硬盘被盗、数据文件被私自拷贝导致的数据泄露。
分为数据库引擎层原生加密与操作系统磁盘级加密两种,前者精细化、依赖企业版,后者通用免费适配所有版本,在 TLS 网络加密之外,筑牢数据落地最后一道安全防线。
十一、MongoDB 的备份与恢复
制定可靠的备份以及恢复的策略是数据库管理中的至关重要的一节,他可以帮助在发生数据的损坏、误操作、或者是灾难性故障的时候恢复数据,这节会介绍MongoDB的常用的备份的方法以及的恢复流程以及灾难恢复的最佳的实践。
选取不同的备份的恢复的策略是根据需求来的,如恢复时间目标 (RTO)、恢复点目标 (RPO)、数据库规模以及部署架构(独立实例、副本集、分片集群)。
备份方法
mongodump和mongorestore- 描述: MongoDB 提供的官方命令行工具,用于创建数据库的 BSON 文件备份,并可以从这些文件恢复数据。
- 优点: 简单易用,适合小型数据集或对备份窗口要求不高的场景。
- 缺点: 备份期间可能会影响数据库性能。对于大型数据集,备份和恢复过程可能非常耗时。在恢复
mongodump的备份时,它会重建索引,这会增加恢复时间。
- 文件系统快照 (Filesystem Snapshots)
- 描述: 利用文件系统(如 LVM)或云存储(如 AWS EBS)的快照功能,对 MongoDB 的数据文件进行即时备份。
- 优点: 备份速度快,对数据库性能影响小。恢复速度也很快,因为数据和索引都无需重建。
- 缺点: 必须确保在创建快照时,数据处于一致性状态。这通常需要开启 journaling 并确保在快照前执行
fsync和 lock 操作。
- MongoDB Cloud Manager / Ops Manager
- 描述: MongoDB 提供的企业级管理工具,提供持续的、时间点恢复的备份服务。
- 优点: 提供图形化界面,管理方便。支持副本集和分片集群的持续备份和时间点恢复,可以恢复到任意时刻。
- 缺点: 是商业服务,需要额外成本。
- 文件系统快照(LVM 快照 / 云盘快照) —— 互联网大厂最常用
- MongoDB 官方云管理平台(Ops Manager/Cloud Manager) —— 中大型企业、政企、付费业务线
- 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 来处理分片集群的备份,因为它们专门设计用于处理这种复杂性。
- 手动备份(不推荐,风险高):
- 禁用 Balancer。
- 对配置服务器进行
mongodump。 - 对每个分片副本集进行
mongodump。 - 重新启用 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 性能,特别是对于写密集型应用。
- 网络: 确保数据库服务器之间的网络延迟尽可能低,特别是在分片集群和地理分布的副本集中。
- 读写分离: 在读密集型应用中,通过将读请求路由到从节点来扩展读取能力。