MongoDB 入门

本文主要记录近期学习 MongoDB 的一些内容,主要参考了官方文档 https://docs.mongodb.com/v4.4/,MongoDB 的官方文档写的还是挺不错的,很详细,值得一看。

本文包含以下几部分内容:

  • 认识 MongoD
  • 文档的 CRUD 操作
  • 聚合操作
  • 索引
  • 事务
  • 副本集

下边 MongoDB 的相关操作都是在 Mongo Shell 中执行的,这种方式不需要考虑语言环境的问题,具有一定的通用性,也足以应对一些不是特别复杂的操作,作为 MongoDB 入门也足够了。

一、认识 MongoD

MongoDB 属于 NoSQL 类型数据库的一种,它具有高性能、高可用、易扩展等特点。

MongoDB 是一个文档数据库,它以 Binary JSON(BSON) 的数据结构来存储文档格式的数据,简单的说一条文档数据就类似于 JSON 对象,即键值对的形式,借用官方的以一张图来认识下文档数据:

其中 field 是字符串类型,但是 value 支持的数据类型相比传统的 JSON 更加丰富,后边我们细聊。

为了直观的认识一下 MongoDB,我们来对比常见的关系型数据库 MySQL,两者还是有很多相似的地方:

MySQL MongoDB
Database(数据库) Database(数据库)
Table(表) Collection(集合)
Row(行) Document(文档)
Column(列) Field(字段)
Primary Key(主键) Primary Key(主键)
Index(索引) Index(索引)
Transactions(事务) Transactions(事务)

1.1、一些特点

每一种数据库都有自身相对适用的场景,如果我们的需求大致符合以下的场景,就可以考虑使用 MongoDB,这样可用更低的成本来解决问题:

  • 需要支持事务,从 MongoDB4.0 版本开始已经逐步完善了对事务的支持,开始支持多文档事务,通过分布式事务,事务可以跨多个操作、集合、数据库、文档和分片使用。
  • 不需要复杂的多表关联查询(join),在 MongoDB 中使用 Aggregation Pipeline 中的 $lookup 操作符可以简单的实现 left outer join。
  • 对文本以及地理位置数据的存储、查询有较强烈的需求。当然特别专业的文本检索还是需要用搜素引擎的,比如 Elasticsearch
  • 数据模型可能有比较频繁的变更
  • 有高并发的数据读写需求
  • 需要存储大规模的数据
  • 对数据库的扩展性以及可用性有较高的需求

1.2、常用数据类型

也就是 BSON 中支持的数据类型:https://docs.mongodb.com/manual/reference/bson-types/

类型 说明
ObjectId 是一个 12 字节的 16 进制数,每个字节占两位,总长度为 24 位。具体是由 4 字节的时间戳(以 Unix 纪元以来的秒数为单位)、5 字节的根据机器和进程生成的唯一随机值、3 字节的递增计数器值(使用前边的随机值作为起始值)组成。每条文档数据都有一个默认字段_id作为主键,如果没有指定值,MongoDB 会指定一个 ObjectId 类型的默认值
String 必须是 UTF-8 编码格式的字符串
Double 浮点数,MongoDB 中没有 Float 类型
Decimal128 高精度浮点数,可用于存储价格等数据
Integer 分为 32 位(NumberInt)和 64(NumberLong)位两种整数
Boolean true 或者 false
Object 对象,可用于嵌入另外一个文档
Null 存储空值
Array 用于存储数组或者列表
Date 日期,是一个 64 位整数,从 1970.1.1(Unix 纪元)到现在的毫秒数, 也称为 UTC 日期时间,推荐使用 Date 类型处理日期
Timestamp 时间戳,64位,前 32 位是从 1970.1.1(Unix 纪元)到现在的秒数,后 32 位是给定秒内操作的递增序数,无法与 Date 类型关联,主要供内部 MongoDB 使用,在单个 mongod 实例中,时间戳值始终是唯一的
Regular Expression 存储正则表达式
Code 存储 JavaScript 代码
Binary Data 存储二进制数据

1.3、数据库、集合、文档

https://docs.mongodb.com/v4.4/core/databases-and-collections/

MongoDB 将数据记录以文档(Document)的形式保存集合(Collection)中、集合又需要存储到对应的数据库(Database)中,所以掌握它们的一些常用操作。

MongoDB 中对数据库、集合、文档 field 等的命名规范以及长度都有一定的限制,可以参考官方文档查看 。

MongoDB 的安装可以参考文章最后一部分,起动安装的 MongoDB 节点,然后连接到主节点:

mongo -u  -p  --host  --port 

除了mongo之外,其它参数都是可选的,hostport 默认是本机的27017端口。

1.3.1、创建、切换数据库

use mydb

通过use 命令可以切换到指定的已存在的数据库,如果数据库不存在,则会在第一次给数据库创建集合时创建对应的数据库。

1.3.2、查看所有的数据库

show dbs 
show databases

上边两个命令都可以查看权限内的所有数据库。

1.3.3、查看当前所在的数据库

db

1.3.4、删除数据库

db.dropDatabase()

切换到指定数据库后,才可以删除数据库。

1.3.5、查看数据库中的所有集合

show collections

先切换到指定数据,才可以查看其中的集合。

1.3.6、创建集合

db.createCollection("book")

这种属于指定集合名称显式的创建方式,需要先切换到指定数据库。

除了显式的创建集合,还在隐式创建,如果集合不存在,当第一往集合中保存数据时就会自动创建对应的集合,比如:

db.book.inserOne({})

1.3.7、删除集合

db.book.drop()

切换到指定数据库后,就可以根据名称来删除集合。

二、文档的 CRUD 操作

https://docs.mongodb.com/v4.4/crud/

2.1、插入

2.1.1、insertOne()

insertOne()是给指定集合中插入单条文档,如果文档没有指定_id字段,则 MongoDB 会添加一个值为 ObjectId _id 字段到文档中,注意 _id 字段为默认主键。

db.book.insertOne({
    "author": "刘慈欣",
    "commentCount": 1038006,
    "img": "//img14.360buyimg.com/n1/s200x200_jfs/t1705/189/702227414/177982/cc8c12f0/55dab54dN5271c377.jpg",
    "name": "中国科幻基石丛书:三体(套装1-3册)",
    "price": 83.7,
    "publisher": "重庆出版社",
    "shop": "科幻世界京东自营店",
    "skuId": "11757834"
})

插入数据时如果不指定字段的类型,则 MongoDB 会使用自推断出数据类型来存储数据,但这可能不是你需要的理想类型。

2.1.2、insertMany()

如果要一次插入多条文档,可以使用insertMany(),并以数组形式传入文档,_id字段的处理规则和上边一致。

db.book.insertMany([
    {
        "author": "紫金陈",
        "commentCount": 36164,
        "img": "//img14.360buyimg.com/n1/s200x200_jfs/t1/130390/12/10177/391461/5f63788cE9b818814/fa5465bd1280778e.jpg",
        "name": "长夜难明(紫金陈社会派悬疑推理小说必读 迷雾剧场廖凡白宇出演的网剧《沉默的真相》原著小说)",
        "price": 42,
        "publisher": "云南人民出版社",
        "shop": "浦睿文化京东自营店",
        "skuId": "12081064"
    },
    {
        "author": "鲁迅",
        "commentCount": 25567,
        "img": "//img11.360buyimg.com/n1/s200x200_g15/M0A/0F/18/rBEhWFJeUd4IAAAAAAbFh_6HtZsAAENlQKKQEoABsWf482.jpg",
        "name": "呐喊",
        "price": 10.6,
        "publisher": "译林出版社",
        "shop": "凤凰壹力京东自营店",
        "skuId": "11338556"
    }
])

2.1.3、insert()

insert()既可以用来给集合中插入单条文档,也可以通过数组参数插入多条文档。

db.book.insert({})
db.book.insert([{}, {}])

2.2、查询

查询主要是通过find(query, projection)方法来实现的,find()会返回符合条件的全部文档,但需要注意在 mongo shell 中并不是一次性返回集合中的全部文档。

比如下边的查询(没有查询条件{}可以省略):

db.book.find({})

该查询实际会返回一个游标(cursor),但我们没有通过定义var变量接收游标,所以游标会自动迭代最多 20 次,所以该查询最多返回 20 条文档。

如果我们通过变量来接收返回的游标,并手动调用游标变量,同样游标会自动迭代 20 次,并返回对应的文档:

> var c = db.book.find({})
> c
> c

当然也可以手动迭代,逐条返回数据:

> var c = db.book.find({})
> while (c.hasNext()) {
    printjson(c.next());
  }

这个默认的 20 次可以通过如下方式修改:

DBQuery.shellBatchSize = 30

MongoDB 中提供了许多查询相关的操作符,来满足不同的场景,

2.2.1、比较运算符

比较运算符有$eq$gt$gte$lt$lte$ne$in$nin,我们来看几个例子

比如查询author的是紫金陈的文档:

db.book.find({author: {$eq: "紫金陈"}})

也可以使用下边的简化写法:

db.book.find({author: "紫金陈"})

但需要注意,如果使用$eq时查询条件的值是正则表达式,那么只会匹配字段值也是正则表达式的文档,比如下边的模糊查询是没有结果的:

db.book.find({author: {$eq: /金/}})

需要使用如下的查询方式才能查询到author中包含的文档:

db.book.find({author: /金/})
db.book.find({author: {$regex: /金/}})

查询price小于100的书:

db.book.find({price: {$lt: 100}})

查询author紫金陈或者鲁迅的文档:

db.book.find({author: {$in: ["紫金陈", "鲁迅"]}})

这个功能也可以使用逻辑运算符$or实现,但这种场景还是建议使用$in

详细内容可参考文档:https://docs.mongodb.com/v4.4/reference/operator/query-comparison/

2.2.2、逻辑运算符

逻辑运算符有$and$or$not$nor

比如查询name呐喊并且price小于等于50的文档,可以使用$and

db.book.find({$and: [{name: "呐喊"}, {price: {$lte: 50}}]})

还有一种等价简单的写法,提供了隐式的$and操作:

db.book.find({name: "呐喊", price: {$lte: 50}})

查询price小于 100 或者commentCount大于100000的文档,可以使用$or

db.book.find($or: [{price: {$lt: 100}}, {commentCount: {$gt: 100000}}])

$not表示逻辑非操作,即对查询条件取反,同时也会查询不包含对应字段的文档,如下查询语句:

db.book.find({price: {$not: {$lt: 100}}})

会查询price大于 100 或者不包含price的文档。

$nor会返回不匹配所有指定字段的查询条件的文档,比如:

db.book.find($nor: [{author: "鲁迅"}, {commentCount: {$gt: 100000}}])

会查询author不是鲁迅commentCount不大于 100000、以及不包含对应字段的文档。

2.2.3、文档字段与查询

2.2.3.1

find()方法默认会返回文档的全部字段,我们可以通过它的第二个参数来指定只返回哪些字段和不返回那些字段。

db.book.find({}, {name: 1})

理论上我们希望返回的文档只有name字段,但其实也包含_id字段,因为_id字段比较特殊我们可以手动指定不返回:

db.book.find({}, {name: 1, _id: 0}

所以1表示只会返回对应的字段,其它字段不返回(_id需要单独指定);0表示不会返回对应的字段,其它字段不受影响。

2.2.3.2

有时我们需要查询存在某个字段的文档,可以使用$exists

db.book.find({img: {$exists: true}})

上边会查询有img字段的文档。

2.2.3.3

还可以按照字段类型还查询文档数据,$type可以用来查询字段值为指定 BSON 类型的文档:

db.book.find({commentCount: {$type: "long"}})

也可以用数组表示多个类型:

db.book.find({commentCount: {$type: ["long", "string"]}})

2.2.3.4

要获取一个字段的类型,可以使用typeof

typeof db.book.name

要判断一个字段是否为指定类型,可以使用instanceof

 db.book.commentCount instanceof NumberInt

2.2.3.5

mongo 中关于null值的查询比较特殊,如下查询会返回img字段值为null以及不存在该字段的文档:

db.book.find(img: null)

2.2.4、排序

find()方法查询到的结果默认按_id升序排列,我们也可以使用sort()方法按照指定字段来排序。

db.book.find().sort({price: 1})

price: 1表示查询结果按照price升序排列,1表示升序、-1表示降序。

也可以同时用多个字段排序:

db.book.find().sort({price: 1, commentCount: -1})

2.2.5、分页

分页查询一般通过limit()skip()两个方法结合实现:

  • limit: 控制每次查询返回的文档数量
  • skip: 从查询结果中跳过指定数量的文档再开始返回
db.book.find().sort({price: 1}).limit(10).skip(20)

从查询结果中跳过20条数据后取10条数据返回,即第三次分页查询。

这种方式用起来简单,但是当数据量比较大的时候,比如几十万到一百万左右,越往后分页查询就越慢,有比较明显的性能问题。所以不太适合大量数据的全量分页查询,只查询少量数据还是可以的。

当然在实际中也是有大量数据的全量分页查询需求的,此时我们可以指定一个排序字段,保证字段值按一定的规律增长即可。这样每次查询时需要携带一个排序字段的基准值,查询前先按照排序字段排序,再查询排序字段值大于或小于该基准值的指定数据量数据。

2.3、更新

更新文档的方法可以指定三个参数:

  • filter,文档的过滤条件也就是要更新哪些文档
  • update,如何更新文档
  • options, 可选参数,指定一些额外的属性

具体的可以参考文档 https://docs.mongodb.com/v4.4/reference/update-methods/

比如要更新匹配过滤条件的第一条文档,可以使用updateOne()

db.book.updateOne({skuId: "11757834"}, {$set: {price: 66.6}, $currentDate: {lastModified: true}}, {upsert: true})

{skuId: "11757834"}是过滤条件,$set操作符用来更新指定字段的值。$currentDate操作符用来将lastModified字段的值更新为当前时间(默认 Date)(如果该字段不存在则会自动创建)。upsert是可选属性,要更新的文档不存在时决定是否插入新文档。

updateMany()updateOne()用法类似,但是会更新匹配条件的全部文档,比如将指定作者的书调价:

db.book.updateMany({author: "鲁迅"}, {$inc: {price: 2}})

还有一个update()方法,可以通过指定第三个参数的multi属性来决定是更新单个文档还是多个文档。

除了上边用的$set$currentDate$inc这几个更新操作符之外,其它的可以参考文档 https://docs.mongodb.com/manual/reference/operator/update/

最后还有一个replaceOne()方法,可以用新文档替换掉符合过滤条件的第一个文档。

2.4、删除

删除操作相对简单一些,根据指定的过滤条件,可以一次最多删除一条,也可以删除全部。

deleteOne()每次最多删除一条文档:

db.book.deleteOne({author: "鲁迅"})

deleteMany()则可以删除全部符合条件的。

还有一个remove()方法,可以通过justOne属性指定一次删除最多一条还是全部:

db.book.remove({author: "鲁迅"}, {justOne: false})

三、聚合操作

除了一般的查询操作,MongoDB 提供的聚合查询也是非常有用的,主要可以实现对文档数据的统计、分析功能。这里我们主要了解一下聚合管道,至于Map-Reduce就不讨论了,它基本可以由聚合管道代替,同时可以获得更高的性能以及可以用性。最后还有一些简单的聚合操作方法可以了解下,方便处理一下简单的需求。

详细的内容可以直接参考官方文档 Aggregation。

3.1、聚合管道

聚合管道,类似于流水线处理,对输入的文档进行一个或者多个阶段的连续处理,上一个处理阶段的输出作为下一个阶段的输入,直到所有阶段全部完成后输出最终的处理结果。

先看一个例子来感受一下这个过程,比如先过滤出commentCount大于 100000 的文档、再按author字段分组并统计出组内总的文档数bookCount、然后按照bookCount降序排列:

db.book.aggregate([
    {$match: {commentCount: {$gt: 100000}}},
    {$group: {_id: "$author", bookCount: {$sum: 1}}},
    {$sort: {bookCount: -1}}
])

经过上边三个阶段的处理,最终的结果如下:


在聚合管道中,每一个阶段(Stage)都需要指定一个操作符来做具体的事情,比如前边的$match$group$sort。目前官方提供了大约 30 个阶段操作符,具体的可以参考文档 Aggregation Pipeline Stages,每个操作符都有详细的用法。

这里简单介绍一下,知道每个操作符大概是做什么的,可以在那些场景使用:

  • $match,从中的文档数据中过滤出符合查询条件的
  • $group,对文档按指定字段进行分组,字段的值相同的会被分为一组,同时可以对组内数据进行各种计算操作,最后为每组输出一个文档,分组也可以实现数据去重的效果
  • $bucket,功能和$group类似也会对文档分组,但是它可以指定分组字段的值的区间,对应区间的文档会被分为一组
  • $sort,对文档按指定字段排序,可以指定多个字段,-1降序、1升序
  • $count,返回管道中当前阶段的文档数
  • $sortByCount,对输入的文档按指定字段的值进行分组,然后计算每组的文档数,相当于$group+$sort
  • $skip,跳过指定个数的文档,将剩余的传递到下一阶段
  • $limit,指定传递到下一阶段的文档数
  • $project,给文档添加新字段或者删除已有字段,即重构文档结构
  • $addFields,给文档中添加新字段,它还有一个别名$set,和$project的添加功能类似
  • $unset,从文档中删除已有的字段,和$project的删除功能类似
  • $unwind,对文档中的数组类型字段进行结构操作,即将数组值拆分成多个元素,每个元素值将会替换原数组的值并且和当前文档的其它字段组成一个新的文档
  • $lookup,主要的功能是两个集合之间的关联查询,类似于 MySQL 中的 left join
  • $unionWith,可以将两个集合的管道处理结果集组合成一个新的结果集(不会去重),然后输入到管道中的下一阶段
  • $out,必须出现在聚合管道的最后一个阶段,将前边管道返回的文档写入指定的集合中

3.2、简单的聚合操作

简单的聚合操作主要是针对单个集合中文档的操作,功能都很简单。

前几个都是统计集合中文档数的。

3.2.1、count()

db.book.count({author: "鲁迅"})

使用count()方法时尽量不要省略过滤条件,否则会使用元数据来计数,在分片集群上、非常正关机可能导致结果不准确。

上边的计数统计也等价于:

db.book.find({author: "鲁迅"}).count()

除了设置过滤条件,还支持可选的参数,比如设置最多统计的文档数,避免统计过多的数据做无用功:

db.book.count({author: "鲁迅", commentCount: {$gt: 100000}}, {limit: 10000})

3.2.2、estimatedDocumentCount()

db.book.estimatedDocumentCount()

这个不方法支持过滤条件,它直接使用元数据来计数,也存在上边count()方法的问题

3.2.3、countDocuments()

db.book.countDocuments({author: "鲁迅"})

这个方法支持过滤条件以及一些可以选的参数(比如limit),它不会使用元数据统计文档数量,而是通过文档的聚合返回准确的统计结果。而且没有上边两个方法存在的问题。

3.2.4、distinct()

在单个集合中根据指定的过滤条件查找指定字段的不同值,类似于去重,并且以数组形式返回字段的值,比如

db.book.distinct("author", {commentCount: {$gt: 100000}})

四、索引

4.1、认识索引

MongoDB 中的索引和 MySQL 中的类似,MongoDB 中使用索引可以高效的进行数据查询,如果不使用索引,查询过程中就必须扫描集合中的每个文档,进而找到匹配查询条件的,有了索引了,则会从索引中找到匹配查询条件的文档。

MongoDB 中的索引是集合级别的,可以给文档中任何字段(包括嵌套的子字段)创建索引,索引使用 B-tree 的数据结构,它会存储作为索引的字段的值,并且按照字段值升序或降序排列,来提高查询的效率。

借用官方的一张图来看一下使用索引时的查询过程:


4.2、创建索引

MongoDB 在创建集合时,默认会在_id字段上创建唯一索引,索引名为_id_并且该索引不能被删除,这样也可以保证_id字段值相同的文档不会被重复插入到集合中。

除了默认的_id字段上的索引,我们也可以使用createIndex()方法给其它字段创建索引。

首先可以给单个字段创建索引,比如:

db.book.createIndex({skuId: -1}, {name: "skuIdIndex"})
  • 上边给author字段创建了索引,-1表示创建降序索引,即索引中的值按降序排列,1则表示升序索引,对于单字段索引,使用降序或升序都可以,因为 MongoDB 可以从任意一个方向来遍历索引。

  • name则用来指定索引的名称,也可以直接使用默认值,索引的默认名称由索引键(指定的字段名)和索引键方向(-1或1)使用下划线拼接成,上边例子的默认索引名就是skuId_-1

最后可以查看创建好的索引:


此时也可以看到 MongoDB 在_id字段上创建的默认索引。

除了上边的单字段索引,还有复合索引,使用复合索引可以同时在多个字段上创建索引,此时字段之间的顺序很重要,这也决定了索引中数据排序的先后顺序。如下创建一个复合索引:

db.book.createIndex({auther: 1, name: 1})

这里没有指定索引名称,而是使用默认的,查看创建好的索引:


五、事务

在 MongoDB4.0 版本之前, 仅对单个文档的操作是具有原子性的,即单文档事务,这里因为 MongoDB 中文档是可以嵌套子文档的,这样就解决了一般关系型数据库需要多张表才能描述不同数据之间关系的问题,所以这样也能在一定程度上保证数据的一致性。

但是对于同一集合或者不同集合甚至不同数据库中的多个文档的读写就不能保证原子性了,但这个需求显然也是存在的,所以从 MongoDB4.0 开始支持多文档事务了:

  • 在 MongoDB4.0 版本中,支持副本集上的多文档事务
  • 在 MongoDB4.2 版本中,新增了分片集群上多文档事务的支持,同时也合并了副本集上多文档事务的支持。从 MongoDB4.2 版本开始多文档事务也称为分布式事务

在大多数情况下,多文档事务比单文档事务有着更大的性能开销,所以尽可能的去优化文档的结构,使用单文档事务,而不是过分的依赖多文档事务。

MongoDB 在单节点模式下是不支持事务的,所以要使用 MongoDB 的事务特性就需要搭建副本集(Replica Set)或者分片集群(Sharded Cluster

关于事务这里简单的介绍一下,更多内容还请参考官方文档 https://docs.mongodb.com/v4.4/core/transactions/

六、副本集

副本集是一组维护相同数据集的 Mongod 实例,具有自动的故障恢复、读写分离等特点,其中包含一个主节点(Primary)、一个或多个副本节点(Secondary)、一个可选的仲裁节点(Arbiter)。

  • 主节点负责处理所有的写操作,也可以处理查询操作
  • 副本节点会作从主节点同步数据,不处理写操作,但可以通过修改客户端连接支持读操作来减少主节点的压力,主节点挂掉时会参与主节点的选举、投票
  • 冲裁节点不存储数据,不参与主节点的选举,但是会参与投票,一般会在硬件成本有限的情况下使用仲裁节点来代替副本节点,同时也保证了副本集的正常工作。

关于副本集详细的介绍可以参考 https://docs.mongodb.com/manual/replication/

搭建副本集可以采用 一个主节点+偶数个副本节点 或者 一个主节点+奇数个副本节点+一个仲裁节点 的方式,这里简单介绍在 Winddows 环境下采用1主1副本1仲裁的组合模拟搭建副本集。

这里下载 MongoDB4.4.10 版本 zip 包,解压3份,如下:

分别在3个根目录下创建confdata\dblog目录,然后在conflog分别创建mongod.confmongod.log文件。

下边是mongodb-1mongod.conf的内容:

storage:
  dbPath: D:\mongodb-4.4.10\mongodb-1\data\db # mongod实例的数据存储目录
  journal:
    enabled: true # 启用持久性日志以确保数据文件保持有效和可恢复
systemLog:
  destination: file
  path: D:\mongodb-4.4.10\mongodb-1\log\mongod.log # 指定日志文件
  logAppend: true # 重启时将日志追加到现有之日文件末尾
net:
  bindIp: localhost # 绑定ip,默认就是localhost
  port: 27017 # 绑定的端口号,默认就是27017
replication:
  replSetName: myrs #副本集的名称

其它两个分别修改dbPathpathport即可。mongodb-2mongodb-3的端口分别指定为2701827019

配置完后,分别在bin目下启动3个节点:

mongod -f ../conf/mongod.conf

连接到mongodb-1(也可以连接到其它节点,可以在bin目录下启动也可以配置环境变量后在任意目录启动),进行副本集初始化:

mongo --host localhost --port 27017
rs.initiate()

初始化成功后,mongodb-1默认会成为主节点,向副本集中添加副本节点mongodb-2

rs.add("localhost:27018")

还需要添加仲裁节点mongodb-3

rs.addArb("localhost:27019")

连接到上边添加的副本节点,使其支持读操作:

mongo --host localhost --port 27018
rs.secondaryOk()

到这里基本的配置就完成了,向主节点添加数据后,副本节点会自动同步主节点的数据,如果主节点挂掉则会从副本节点中重新选举新的主节点,后期重新启动副本集的话,也会选举新的主节点。

本文内容到这里就结束了,下一篇我们将学习如在 Springboot 中使用 MongoDB。

你可能感兴趣的:(MongoDB 入门)