title | category | tag | ||
---|---|---|---|---|
MongoDB常见面试题总结(上) |
数据库 |
|
少部分内容参考了 MongoDB 官方文档的描述,在此说明一下。
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常流行的 文档类型数据库 。
在高负载的情况下,MongoDB 天然支持水平扩展和高可用,可以很方便地添加更多的节点/实例,以保证服务性能和可用性。在许多场景下,MongoDB 可以用于代替传统的关系型数据库或键/值存储方式,皆在为 Web 应用提供可扩展的高可用高性能数据存储解决方案。
MongoDB 的存储结构区别于传统的关系型数据库,主要由如下三个单元组成:
也就是说,MongoDB 将数据记录存储为文档 (更具体来说是BSON 文档),这些文档在集合中聚集在一起,数据库中存储一个或多个文档集合。
SQL 与 MongoDB 常见术语对比:
SQL | MongoDB |
---|---|
表(Table) | 集合(Collection) |
行(Row) | 文档(Document) |
列(Col) | 字段(Field) |
主键(Primary Key) | 对象 ID(Objectid) |
索引(Index) | 索引(Index) |
嵌套表(Embedded Table) | 嵌入式文档(Embedded Document) |
数组(Array) | 数组(Array) |
MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。字段的值可能包括其他文档、数组和文档数组。
文档的键是字符串。除了少数例外情况,键可以使用任意 UTF-8 字符。
\0
(空字符)。这个字符用来表示键的结尾。.
和 $
有特别的意义,只有在特定环境下才能使用。_
开头的键是保留的(不是严格要求的)。BSON [bee·sahn] 是 Binary JSON的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。有关 BSON 规范的内容,可以参考 bsonspec.org,另见BSON 类型。
根据维基百科对 BJSON 的介绍,BJSON 的遍历速度优于 JSON,这也是 MongoDB 选择 BSON 的主要原因,但 BJSON 需要更多的存储空间。
与 JSON 相比,BSON 着眼于提高存储和扫描效率。BSON 文档中的大型元素以长度字段为前缀以便于扫描。在某些情况下,由于长度前缀和显式数组索引的存在,BSON 使用的空间会多于 JSON。
MongoDB 集合存在于数据库中,没有固定的结构,也就是 无模式 的,这意味着可以往集合插入不同格式和类型的数据。不过,通常情况下,插入集合中的数据都会有一定的关联性。
集合不需要事先创建,当第一个文档插入或者第一个索引创建时,如果该集合不存在,则会创建一个新的集合。
集合名可以是满足下列条件的任意 UTF-8 字符串:
""
。\0
(空字符),这个字符表示集合名的结尾。system.users
这个集合保存着数据库的用户信息,system.namespaces
集合保存着所有数据库集合的信息。$
。数据库用于存储所有集合,而集合又用于存储所有文档。一个 MongoDB 中可以创建多个数据库,每一个数据库都有自己的集合和权限。
MongoDB 预留了几个特殊的数据库。
数据库名可以是满足以下条件的任意 UTF-8 字符串:
""
。' '
(空格)、.
、$
、/
、\
和 \0
(空字符)。数据库名最终会变成文件系统里的文件,这也就是有如此多限制的原因。
MongoDB 的优势在于其数据模型和存储引擎的灵活性、架构的可扩展性以及对强大的索引支持。
选用 MongoDB 应该充分考虑 MongoDB 的优势,结合实际项目的需求来决定:
存储引擎(Storage Engine)是数据库的核心组件,负责管理数据在内存和磁盘中的存储方式。
与 MySQL 一样,MongoDB 采用的也是 插件式的存储引擎架构 ,支持不同类型的存储引擎,不同的存储引擎解决不同场景的问题。在创建数据库或集合时,可以指定存储引擎。
插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如 MySQL 既可以支持 B-Tree 结构的 InnoDB 存储引擎,还可以支持 LSM 结构的 RocksDB 存储引擎。
在存储引擎刚出来的时候,默认是使用 MMAPV1 存储引擎,MongoDB4.x 版本不再支持 MMAPv1 存储引擎。
现在主要有下面这两种存储引擎:
此外,MongoDB 3.0 提供了 可插拔的存储引擎 API ,允许第三方为 MongoDB 开发存储引擎,这点和 MySQL 也比较类似。
目前绝大部分流行的数据库存储引擎都是基于 B/B+ Tree 或者 LSM(Log Structured Merge) Tree 来实现的。对于 NoSQL 数据库来说,绝大部分(比如 HBase、Cassandra、RocksDB)都是基于 LSM 树,MongoDB 不太一样。
上面也说了,自 MongoDB 3.2 以后,默认的存储引擎为 WiredTiger 存储引擎。在 WiredTiger 引擎官网上,我们发现 WiredTiger 使用的是 B+ 树作为其存储结构:
WiredTiger maintains a table's data in memory using a data structure called a B-Tree ( B+ Tree to be specific), referring to the nodes of a B-Tree as pages. Internal pages carry only keys. The leaf pages store both keys and values.
此外,WiredTiger 还支持 LSM(Log Structured Merge) 树作为存储结构,MongoDB 在使用 WiredTiger 作为存储引擎时,默认使用的是 B+ 树。
如果想要了解 MongoDB 使用 B+ 树的原因,可以看看这篇文章:【驳斥八股文系列】别瞎分析了,MongoDB 使用的是 B+ 树,不是你们以为的 B 树。
使用 B+ 树时,WiredTiger 以 page 为基本单位往磁盘读写数据。B+ 树的每个节点为一个 page,共有三种类型的 page:
其整体结构如下图所示:
如果想要深入研究学习 WiredTiger 存储引擎,推荐阅读 MongoDB 中文社区的 WiredTiger 存储引擎系列。
实际项目中,我们经常需要将多个文档甚至是多个集合汇总到一起计算分析(比如求和、取最大值)并返回计算后的结果,这个过程被称为 聚合操作 。
根据官方文档介绍,我们可以使用聚合操作来:
MongoDB 提供了两种执行聚合的方法:
count()
、distinct()
、estimatedDocumentCount()
。绝大部分文章中还提到了 map-reduce 这种聚合方法。不过,从 MongoDB 5.0 开始,map-reduce 已经不被官方推荐使用了,替代方案是 聚合管道。聚合管道提供比 map-reduce 更好的性能和可用性。
MongoDB 聚合管道由多个阶段组成,每个阶段在文档通过管道时转换文档。每个阶段接收前一个阶段的输出,进一步处理数据,并将其作为输入数据发送到下一个阶段。
每个管道的工作流程是:
常用阶段操作符:
操作符 | 简述 |
---|---|
$match | 匹配操作符,用于对文档集合进行筛选 |
$project | 投射操作符,用于重构每一个文档的字段,可以提取字段,重命名字段,甚至可以对原有字段进行操作后新增字段 |
$sort | 排序操作符,用于根据一个或多个字段对文档进行排序 |
$limit | 限制操作符,用于限制返回文档的数量 |
$skip | 跳过操作符,用于跳过指定数量的文档 |
$count | 统计操作符,用于统计文档的数量 |
$group | 分组操作符,用于对文档集合进行分组 |
$unwind | 拆分操作符,用于将数组中的每一个值拆分为单独的文档 |
$lookup | 连接操作符,用于连接同一个数据库中另一个集合,并获取指定的文档,类似于 populate |
更多操作符介绍详见官方文档:https://docs.mongodb.com/manual/reference/operator/aggregation/
阶段操作符用于 db.collection.aggregate
方法里面,数组参数中的第一层。
db.collection.aggregate( [ { 阶段操作符:表述 }, { 阶段操作符:表述 }, ... ] )
下面是 MongoDB 官方文档中的一个例子:
db.orders.aggregate([
# 第一阶段:$match阶段按status字段过滤文档,并将status等于"A"的文档传递到下一阶段。
{ $match: { status: "A" } },
# 第二阶段:$group阶段按cust_id字段将文档分组,以计算每个cust_id唯一值的金额总和。
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
MongoDB 事务想要搞懂原理还是比较花费时间的,我自己也没有搞太明白。因此,我这里只是简单介绍一下 MongoDB 事务,想要了解原理的小伙伴,可以自行搜索查阅相关资料。
这里推荐几篇文章,供大家参考:
我们在介绍 NoSQL 数据的时候也说过,NoSQL 数据库通常不支持事务,为了可扩展和高性能进行了权衡。不过,也有例外,MongoDB 就支持事务。
与关系型数据库一样,MongoDB 事务同样具有 ACID 特性:
Atomicity
):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;Consistency
):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;Isolation
):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。WiredTiger 存储引擎支持读未提交( read-uncommitted )、读已提交( read-committed )和快照( snapshot )隔离,MongoDB 启动时默认选快照隔离。在不同隔离级别下,一个事务的生命周期内,可能出现脏读、不可重复读、幻读等现象。Durability
):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。关于事务的详细介绍这篇文章就不多说了,感兴趣的可以看看我写的MySQL 常见面试题总结这篇文章,里面有详细介绍到。
MongoDB 单文档原生支持原子性,也具备事务的特性。当谈论 MongoDB 事务的时候,通常指的是 多文档 。MongoDB 4.0 加入了对多文档 ACID 事务的支持,但只支持复制集部署模式下的 ACID 事务,也就是说事务的作用域限制为一个副本集内。MongoDB 4.2 引入了 分布式事务 ,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
根据官方文档介绍:
从 MongoDB 4.2 开始,分布式事务和多文档事务在 MongoDB 中是一个意思。分布式事务是指分片集群和副本集上的多文档事务。从 MongoDB 4.2 开始,多文档事务(无论是在分片集群还是副本集上)也称为分布式事务。
在大多数情况下,多文档事务比单文档写入会产生更大的性能成本。对于大部分场景来说, 非规范化数据模型(嵌入式文档和数组) 依然是最佳选择。也就是说,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
注意:
借助 WiredTiger 存储引擎( MongoDB 3.2 后的默认存储引擎),MongoDB 支持对所有集合和索引进行压缩。压缩以额外的 CPU 为代价最大限度地减少存储使用。
默认情况下,WiredTiger 使用 Snappy 压缩算法(谷歌开源,旨在实现非常高的速度和合理的压缩,压缩比 3 ~ 5 倍)对所有集合使用块压缩,对所有索引使用前缀压缩。
除了 Snappy 之外,对于集合还有下面这些压缩算法:
WiredTiger 日志也会被压缩,默认使用的也是 Snappy 压缩算法。如果日志记录小于或等于 128 字节,WiredTiger 不会压缩该记录。
Amazon DocumentDB(与 MongoDB 兼容) 是一种快速、可靠、完全托管的数据库服务。Amazon DocumentDB 可在云中轻松设置、操作和扩展与 MongoDB 兼容的数据库。
$vectorSearch
运算符Amazon DocumentDB 不支持$vectorSearch
作为独立运营商。相反,我们在$search
运营商vectorSearch
内部支持。有关更多信息,请参阅 向量搜索 Amazon DocumentDB。
OpCountersCommand
Amazon DocumentDB 的OpCountersCommand
行为偏离于 MongoDB 的opcounters.command
如下:
opcounters.command
计入除插入、更新和删除之外的所有命令,而 Amazon DocumentDB 的 OpCountersCommand
也排除 find
命令。getCloudWatchMetricsV2
)对 OpCountersCommand
计入。Amazon DocumentDB 不支持管理或本地数据库,MongoDB system.*
或 startup_log
集合也不支持。
cursormaxTimeMS
在 Amazon DocumentDB 中,cursor.maxTimeMS
重置每个请求的计数器。getMore
因此,如果指定了 3000MS maxTimeMS
,则该查询耗时 2800MS,而每个后续getMore
请求耗时 300MS,则游标不会超时。游标仅在单个操作(无论是查询还是单个getMore
请求)耗时超过指定值时才将超时maxTimeMS
。此外,检查游标执行时间的扫描器以五 (5) 分钟间隔尺寸运行。
Amazon DocumentDB 在利用分布式、容错、自修复的存储系统的专用数据库引擎上模拟 MongoDB 4.0 API。因此,查询计划和explain()
的输出在 Amazon DocumentDB 和 MongoDB 之间可能有所不同。希望控制其查询计划的客户可以使用 $hint
运算符强制选择首选索引。
Amazon DocumentDB 不支持点“。” 例如,文档字段名称中 db.foo.insert({‘x.1’:1})
。
Amazon DocumentDB 也不支持字段名称中的 $ 前缀。
例如,在 Amazon DocumentDB 或 MongoDB 中尝试以下命令:
rs0:PRIMARY< db.foo.insert({"a":{"$a":1}})
MongoDB 将返回以下内容:
WriteResult({ "nInserted" : 1 })
Amazon DocumentDB 将返回一个错误:
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 2,
"errmsg" : "Document can't have $ prefix field names: $a"
}
})
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。