MongoDB权威指南 第3版 (Shannon Bradshaw / Eoin Brazil / Kristina Chodorow)

MongoDB 是当前最流行的 NoSQL 数据库之一,它采用文档模型存储数据,通过灵活的数据结构、高性能的索引机制以及完善的分布式能力,广泛应用于互联网应用、大数据处理和实时业务场景。但想真正理解 MongoDB,仅仅掌握 CRUD 操作和查询语法是不够的。MongoDB 背后涉及许多数据库核心概念:文档模型设计、BSON存储、索引原理、WiredTiger存储引擎、MVCC并发控制、副本集以及分片架构等。本文将结合《MongoDB权威指南》,从使用实践逐步深入底层原理,探索 MongoDB 的设计思想以及它如何解决现代应用中的数据存储和扩展问题。

第一部分 MongoDB入门

第1章 MongoDB简介

1.1 易于使用

MongoDB不是关系数据库,而是面向文档(document-oriented)的数据库。便于扩展是MongoDB没有使用关系模型的主要原因。

通过嵌入文档和数组,面向文档的方式可以仅用一条记录来表示复杂的层次关系,与面向对象语言的开发人员思考数据的方式非常契合。

MongoDB没有预定义模式(predefined schema):文档键值的类型和大小不是固定的。按需添加或删除字段变得更容易。

1.2 易于扩展

数据快速增长,如何扩展数据库?可以归结为两种选择:纵向扩展(提高配置)和横向扩展(将数据分布到更多机器上)。MongoDB的设计采用了横向扩展。面向文档的数据模型使跨多台服务器拆分数据更容易。MongoDB会自动平衡跨集群和数据和负载,自动重新分配文档,并将读写操作路由到正确的机器上,如图1-1所示。

图1-1 使用分片将MongoDB横向扩展至多台服务器
图1-1 使用分片将MongoDB横向扩展至多台服务器

MongoDB是一个集群还是单个节点对应用程序来说都是透明的。

1.3 功能丰富

MongoDB是通用型数据库,除了创建、读取、更新和删除数据外,还提供了数据管理系统的常见功能和许多其他独特的功能。

  • 索引
  • 聚合
  • 特殊的集合和索引类型
  • 文件存储

1.4 性能卓越

MongoDB在其WiredTiger存储引擎中使用了机会锁,以最大限度地提高并发和吞吐量。它会使用尽可能多的RAM作为缓存,并尝试为查询自动选择正确的索引。

什么是机会锁?

机会锁的思想:假设冲突很少发生,大家先自由执行,最后提交时检查有没有别人改过。也就是先做事情,提交时检查有没有冲突。有的话,就会回滚/重试。没有,就成功修改。

第2章 入门指南

2.1 文档

文档是MongoDB的核心概念:它是一组有序键值的集合。

2.2 集合

集合就是一组文档。如果将文档比作关系数据库中的行,那么集合就相当于一张表。

2.3 数据库

MongoDB使用集合对文档进行分组,使用数据库对集合进行分组。

2.5 MongoDB Shell

MongoDB Shell可在此处下载安装。要启动Shell,直接在终端运行mongosh。Shell是一个功能齐全的JavaScript解释器,能够运行任意的JavaScript程序。

启动时,Shell会连接到MongoDB服务器端的test数据库,并将此数据库连接赋值给全局变量db。要查看db当前指向哪个数据库,请键入db并按回车键:

1
2
> db
test

show dbs可查看到所有的数据库

use video中的use用于切换数据库。

show collections可查看到当前数据库的所有集合。

insertOne函数可以将一个文档添加到集合中。

findfindOne方法可用于查询集合中的文档。

updateOne可以修改文档,会接受至少两个参数:第一个用于查找要更新文档的限定条件,第二个用于描述要进行更新的文档。

1
2
3
4
db.movie.updateOne(
  { title: 'Star Wars: Episode IV - A New Hope' },
  { $set: { reviews: [] } },
);

deleteOnedeleteMany方法会从数据库中永久删除文档。都采用一个指定删除条件的过滤文档作为参数。

1
db.movies.deleteOne({ title: 'Star Wars' });

2.7 使用MongoDB Shell

Shell可以连接到机器可以访问的任何MongoDB实例,要想连接到其他机器或端口上的MongoDB服务,需要在启动Shell时指定主机名、端口和数据库:

1
mongosh localhost:27017/movies

此时,db就指向了localhost:27017上的movies数据库。

使用--nodb参数启动Shell,那么它在启动时就不会连接任何数据库:

1
mongosh --nodb

Shell内置了帮助文档,可以输入help命令进行访问。

db.help()可以查看数据库级别的帮助信息,使用db.foo.help()查看集合级别的帮助信息。

MongoDB Shell可以配合脚本一起使用。

第3章 创建、更新和删除文档

3.1 插入文档

1
2
3
4
5
db.movies.insertOne({ title: 'Stand by Me' });

db.movies.drop();

db.movies.insertMany([{ title: 'Ghostbusters' }, { title: 'E.T.' }]);

3.2 删除文档

1
2
3
4
5
6
7
db.movies.deleteOne({ _id: ObjectId('6a59ded8541a7ff3e944d53b') });

db.movies.deleteMany({ year: 1984 });

db.movies.deleteMany({}); // 删除一个集合中的所有文档

db.movies.drop(); // 如果想清空整个集合,可使用drop直接删除集合

3.3 更新文档

replaceOne会用新文档完全替换匹配的文档。

$set用来设置一个字段的值。

1
2
3
4
db.user.updateOne(
  { name: 'joe' },
  { $set: { 'favorite book': 'Green Eggs and Ham' } },
);

$unset将键完全删除。

$inc运算符可以递增键值。

$push往数组里增加元素。

$pop从数组中删除元素。

upsert是一种特殊类型的更新。如果找不到与筛选条件相匹配的文档,则会以这个条件和更新文档为基础来创建一个新文档;如果找到了匹配的文档,则进行正常的更新。

findOneAndDeletefindOneAndReplacefindOneAndUpdate可以原子地获取已修改文档的值。

第4章 查询

4.1 find简介

MongoDB使用find方法进行查询。返回集合中文档的一个子集,子集的范围从0个文档到整个文档。要返回哪些文档由find的第一个参数决定,该参数是一个用于指定查条件的文档。

1
2
3
4
5
db.users.find();

db.users.find({ age: 27 });

db.users.find({ age: 27, username: 'joe' });

可通过find(或findOne)的第二个参数指定需要的返回的键。

1
db.users.find({}, { username: 1, email: 1 });

4.2 查询条件

$ltlte$gt$gte$ne都属于比较运算符,分别对应小于、小于等于、大于、大于等于、不等于,可以将它们组合使用以查找一个范围内的值。

1
db.users.find({ age: { $gte: 18, $lte: 30 } });

$in可以用来查询一个键的多个值。$nin会返回与数组中所有条件都不匹配的文档。

1
2
3
db.raffle.find({ ticket_no: { $in: [125, 365, 778] } });

db.raffle.find({ ticket_no: { $nin: [125, 365, 778] } });

$in能对单个键进行OR查询,而$or可以在多个键中查询任意的给定值。

1
2
3
db.raffle.find({
  or: [{ ticket_no: { $in: [125, 365, 778] } }, { winner: true }],
});

OR类查询如果第一个参数能够匹配尽可能多的文档,则其效率最高。虽然总是可以用$or,但有可能就应该用$in,因为查询器可以更高效地对其进行处理。

$not是一个元条件运算符,可以用于任何其他条件之上。

取模运算符$mod会将查询的值除以第一个给定值,如果余数等于第二个给定值,则匹配成功。

1
db.users.find({ id_num: { $not: { $mod: [5, 1] } } });

4.5 游标

游标可以理解为查询结果的迭代器(Iterator),它并不是一次性把所有数据都返回给客户端,而是指向查询结果集中的一个“指针”,客户端可以通过它逐批获取数据。

MongoDB会使用游标返回find的执行结果。

第二部分 设计应用程序

第5章 索引

5.1 索引简介

索引设计的三个核心原则:

原则1:等值查询字段放前面

例如,业务查询:

1
db.orders.find({userId: 10001, status: "SUCCESS"})

设计:

1
db.orders.createIndex({userId: 1, status: 1})
原则2:范围查询字段放后面

例如,查询:

1
2
3
4
5
6
{
    userId:10001,
    createTime:{
        $gte:"2026-01-01"
    }
}

推荐:

1
2
3
4
{
  userId: 1,
  createTime: -1
}
原则3:排序字段尽量放索引里

例如,查询:

1
2
3
4
5
db.orders.find({
  userId: 100
}).sort({
  createTime: -1
}).limit(20)

推荐:

1
db.orders.createIndex({userId: 1, createTime: -1})

MongoDB索引设计经典原则:ESR(E = Equality, S = Sort, R = Range)。

第6章 特殊的索引和集合类型

地理空间索引、全文搜索索引、固定集合、TTL索引、使用GridFS存储文件

第7章 聚合框架

7.2 阶段入门:常见操作

匹配(match)、投射(project)、排序(sort)、跳过(skip)和限制(limit)这5个阶段。

7.3 $unwind

在聚合管道中处理数组字段时,通常需要包含一个或多个展开(unwind)阶段。这允许我们将指定数组字段中的每个元素都形成一个输出文档。

图7-4 $unwind会从输入文档中获取一个数组,并为该数组中的每个元素创建一个输出文档
图7-4 $unwind会从输入文档中获取一个数组,并为该数组中的每个元素创建一个输出文档

第8章 事务

什么时候使用事务,核心不是看“有几个操作”,而是看:

这些操作是否必须作为一个不可分割的业务事件,要么全部成功,要么全部失败。

MongoDB设计理念是:

优先通过合理的数据模型(嵌入文档)避免事务;只有跨文档一致性无法避免时才使用事务。

第9章 应用程序设计

第三部分 复制

第10章 创建副本集

10.1 复制简介

复制是将数据的相同副本保留在多台服务器上的一种方法,建议将其用于所有生产部署中。即使一台或多台服务器停止运行,使用复制功能也可以确保应用程序正常运行和数据安全。

在MongoDB中,创建副本集(replica set)后就可以使用复制功能了。副本集是一组服务器,其中一个是用于处理写操作的主节点(primary),还有多个用于保存主节点的数据副本的从节点(secondary)。如果主节点崩溃了,则从节点会从其中选出一个新的主节点。

10.2 建立副本集(一)

对于Windows系统:

1
2
3
.\mongod.exe --replSet rs0 --dbpath E:\MongoDBData\Data1 --port 27017 --logpath E:\MongoDBData\Log\data1.log
.\mongod.exe --replSet rs0 --dbpath E:\MongoDBData\Data2 --port 27018 --logpath E:\MongoDBData\Log\data2.log
.\mongod.exe --replSet rs0 --dbpath E:\MongoDBData\Data3 --port 27019 --logpath E:\MongoDBData\Log\data3.log

一旦启动,就应该有3个独立的mongod进程在运行。--replSet rs0表示3个MongoDB属于同一个副本集,通过--replSet指定副本集名称。

打开另一个PowerShell,连接节点1:

1
mongosh --port 27017

执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
rs.initiate(
{
   _id:"rs0",
   members:[
      {
        _id:0,
        host:"localhost:27017"
      },
      {
        _id:1,
        host:"localhost:27018"
      },
      {
        _id:2,
        host:"localhost:27019"
      }
   ]
})

出现MongoServerError[NoReplicationEnabled]: This node was not started with replication enabled.错误。意思是当前连接的MongoDB实例没有开启副本集功能,所以不能执行rs.initiate()rs.status()等副本集命令。我们可使用db.serverCmdLineOpts()命令查看是否开启了副本集:

图10-1 db.serverCmdLineOpts()输出结果
图10-1 db.serverCmdLineOpts()输出结果

没有类似replication: { replSetName: 'rs0' }内容,说明没有开启。我们可以修改配置文件D:\\Program Files\\MongoDB\\Server\\8.0\\bin\\mongod.cfg,增加:

1
2
replication:
  replSetName: rs0

完整示例如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# mongod.conf

# for documentation of all options, see:
#   http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
  dbPath: D:\Program Files\MongoDB\Server\8.0\data

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path:  D:\Program Files\MongoDB\Server\8.0\log\mongod.log

# network interfaces
net:
  port: 27017
  bindIp: 127.0.0.1

replication:
  replSetName: rs0

#processManagement:

#security:

#operationProfiling:

#sharding:

## Enterprise-Only Options:

#auditLog:

重启服务:

1
2
net stop MongoDB
net start MongoDB

之后重新启动3个mongodb进程,连接节点1,执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
rs.initiate(
{
   _id:"rs0",
   members:[
      {
        _id:0,
        host:"localhost:27017"
      },
      {
        _id:1,
        host:"localhost:27018"
      },
      {
        _id:2,
        host:"localhost:27019"
      }
   ]
})

图10-2 rs.initiate()输出结果
图10-2 rs.initiate()输出结果

使用rs.status()命令查看副本集状态:

图10-3 rs.status()输出结果
图10-3 rs.status()输出结果

此时,我们往数据库插入数据会自动复制同步。

10.3 网络注意事项

mongod默认情况绑定到localhost(127.0.0.1),为了使得副本集中每个成员都可以与其他成员通信,还必须绑定到其他成员可以访问的IP地址。

1
.\mongod.exe --replSet mdbDefGuide --dbpath E:\MongoDBData\Data1 --port 27017 --oplogSize 200 --bind_ip localhost,192.168.1.100

其中:

  • localhost本机访问
  • 192.168.1.100局域网其他机器访问

第11章 副本集的组成

第12章 从应用程序连接副本集

第13章 管理

第四部分 分片

第14章 分片简介

第15章 配置分片

第16章 选择片键

第17章 分片管理

第五部分 应用程序管理

第18章 了解应用程序的动态

第19章 MongoDB安全介绍

第20章 持久性

第六部分 服务器端管理

第21章 在生产环境中设置MongoDB

第22章 监控MongoDB

第23章 备份

第24章 部署MongoDB

附录B 深入MongoDB

推荐

MongoDB Docs


相关内容

请作者喝杯咖啡!
AndyFree96 支付宝支付宝
AndyFree96 微信微信