28个MongoDB经典面试题详解
MongoDB作为非关系型数据库的代表,在招聘面试中出现的频率非常高。本文整理了28个高频面试题,覆盖基础概念、操作、索引、复制集、分片、性能优化等核心知识点,帮助大家系统梳理MongoDB相关知识。
一、基础概念类面试题
1. 什么是MongoDB?它和关系型数据库有什么区别?
MongoDB是一个基于分布式文件存储的开源数据库系统,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案。它属于NoSQL数据库,和传统关系型数据库的核心区别如下:
| 对比维度 | 关系型数据库(如MySQL) | MongoDB |
|---|---|---|
| 数据模型 | 二维表结构,行和列固定 | 文档型结构,数据以BSON(类JSON)格式存储,结构灵活 |
| Schema约束 | 建表时需定义字段类型、长度等约束,修改结构成本高 | 无固定Schema,同一个集合中的文档可以有不同的字段 |
| 事务支持 | 早期仅支持单文档事务,现在支持分布式事务 | 4.0版本后支持多文档ACID事务 |
| 扩展方式 | 垂直扩展为主,水平扩展复杂度高 | 原生支持水平扩展,通过分片机制实现 |
2. MongoDB中的核心概念有哪些?分别是什么作用?
MongoDB的核心概念和高频对应关系如下:
数据库(Database):MongoDB中最高级别的存储单元,一个MongoDB实例可以包含多个数据库,不同数据库的数据物理隔离。
集合(Collection):对应关系型数据库中的表,是文档的容器,集合中的文档不需要有统一的字段结构。
文档(Document):MongoDB中最小的数据存储单元,对应关系型数据库中的行,以BSON格式存储,支持嵌套和数组类型。
字段(Field):对应文档中的键值对,类似关系型数据库中的列。
_id:每个文档默认自带的主键字段,类型为ObjectId,保证文档的唯一性,也可以自定义该字段的值。
3. BSON和JSON有什么区别?
BSON是Binary JSON的缩写,是MongoDB存储数据的底层格式,和JSON的主要区别如下:
JSON是文本格式,BSON是二进制格式,解析速度更快,存储空间更小。
BSON支持更多数据类型,比如Date、Binary Data、ObjectId、Regular Expression等,JSON仅支持字符串、数字、布尔、数组、对象、null等基础类型。
BSON支持在编码时记录字符串的长度,遍历时不需要像JSON一样逐个字符解析,效率更高。
4. MongoDB适合哪些应用场景?不适合哪些场景?
适合的场景:
数据结构灵活多变,Schema经常变化的业务,比如内容管理系统、用户行为日志存储。
需要快速水平扩展的业务,比如高并发的社交应用、物联网设备数据存储。
对读写性能要求高,且不需要复杂多表关联查询的场景。
不适合的场景:
需要复杂事务支持、多表强关联查询的金融类核心业务,比如银行转账系统。
数据一致性要求极高的场景,比如涉及资金结算的核心交易系统。
二、基础操作类面试题
5. 如何创建、查看、删除数据库和集合?
以下是MongoDB shell中的常用操作命令:
# 切换到指定数据库,如果数据库不存在,会在插入第一条数据时自动创建
use test_db
# 查看当前连接的数据库
db
# 查看所有数据库(只显示有数据的数据库)
show dbs
# 创建集合,不指定选项时直接使用db.createCollection("集合名")
db.createCollection("user")
# 查看当前数据库下的所有集合
show collections
# 删除当前数据库下的指定集合
db.user.drop()
# 删除当前数据库
db.dropDatabase()6. 如何向集合中插入、查询、更新、删除文档?
基础CRUD操作示例如下:
# 插入单条文档,insertOne方法返回插入的文档_id
db.user.insertOne({name: "张三", age: 25, hobby: ["篮球", "阅读"]})
# 插入多条文档,insertMany方法接收文档数组
db.user.insertMany([
{name: "李四", age: 28, hobby: ["足球"]},
{name: "王五", age: 22, hobby: ["游戏", "跑步"]}
])
# 查询所有文档
db.user.find()
# 条件查询,查询age大于25的文档
db.user.find({age: {$gt: 25}})
# 查询指定字段,1表示返回,0表示不返回,_id默认返回,需要显式设置0才不返回
db.user.find({age: {$gt: 25}}, {name: 1, age: 1, _id: 0})
# 更新单条文档,将name为张三的文档age更新为26
db.user.updateOne({name: "张三"}, {$set: {age: 26}})
# 更新多条文档,将所有age小于25的文档增加标签"年轻用户"
db.user.updateMany({age: {$lt: 25}}, {$push: {tags: "年轻用户"}})
# 删除单条文档,删除name为王五的文档
db.user.deleteOne({name: "王五"})
# 删除多条文档,删除age大于30的所有文档
db.user.deleteMany({age: {$gt: 30}})7. MongoDB中的比较操作符有哪些?分别是什么作用?
常用的比较操作符如下:
$eq:等于,比如{age: {$eq: 25}}等价于{age: 25}$ne:不等于$gt:大于$gte:大于等于$lt:小于$lte:小于等于$in:匹配数组中任意一个值,比如{age: {$in: [22, 25, 28]}}$nin:不匹配数组中任意一个值
8. 如何对查询结果进行排序、分页?
排序使用sort()方法,参数中1表示升序,-1表示降序;分页使用skip()跳过指定数量的文档,limit()限制返回的文档数量:
# 按age升序排序,如果age相同按name降序排序
db.user.find().sort({age: 1, name: -1})
# 分页查询,每页10条,查询第2页数据(跳过前10条,返回接下来10条)
db.user.find().skip(10).limit(10)三、索引相关面试题
9. 什么是MongoDB索引?为什么需要索引?
索引是MongoDB中特殊的数据结构,保存了集合中某个字段或字段集的值,以及这些值对应的文档在磁盘上的存储位置。作用和关系型数据库的索引类似:
没有索引时,查询需要全集合扫描,数据量大时效率极低。
创建索引后,查询可以直接通过索引快速定位到目标文档,大幅提升查询效率。
但是索引会占用额外的存储空间,且会降低插入、更新、删除操作的性能,因为每次写操作都需要同步更新索引。
10. MongoDB支持哪些类型的索引?
常用的索引类型如下:
单字段索引:对单个字段创建的索引,是最基础的索引类型。
复合索引:对多个字段组合创建的索引,查询时如果遵循最左前缀原则,索引才会生效。
多键索引:对数组类型的字段创建的索引,比如文档中的hobby是数组,对hobby创建索引就是多键索引。
文本索引:支持对字符串内容做全文检索,一个集合只能创建一个文本索引,可以包含多个字段。
地理空间索引:支持对地理坐标类型的字段做位置查询,比如查找附近的商家。
哈希索引:对字段值做哈希计算后存储的索引,只支持等值查询,不支持范围查询。
11. 如何创建、查看、删除索引?
操作示例如下:
# 对user集合的age字段创建升序单字段索引
db.user.createIndex({age: 1})
# 创建复合索引,先按age升序,再按name降序
db.user.createIndex({age: 1, name: -1})
# 创建文本索引,对name和hobby字段做全文检索
db.user.createIndex({name: "text", hobby: "text"})
# 查看集合的所有索引
db.user.getIndexes()
# 删除指定索引,通过索引名删除,比如删除age_1索引
db.user.dropIndex("age_1")
# 删除集合的所有索引(除了_id字段的默认索引)
db.user.dropIndexes()12. 什么是索引的最左前缀原则?
最左前缀原则针对复合索引生效:如果创建的复合索引是{a: 1, b: 1, c: 1},那么以下查询可以使用该索引:
查询条件包含a字段
查询条件包含a和b字段
查询条件包含a、b、c三个字段
以下查询无法使用该复合索引:
仅查询b字段
仅查询c字段
查询b和c字段
如果将查询条件的字段顺序调整为a、b、c(和索引定义的顺序无关,MongoDB会自动优化),只要包含最左边的字段就可以命中索引。
13. 如何分析查询是否使用了索引?
可以使用explain()方法分析查询的执行计划,查看索引使用情况:
# 分析查询的执行计划,executionStats模式会返回详细的执行统计信息
db.user.find({age: 25}).explain("executionStats")执行结果中需要重点关注的字段:
winningPlan.inputStage.stage:如果是IXSCAN表示使用了索引扫描,如果是COLLSCAN表示全集合扫描。executionStats.totalDocsExamined:扫描的文档数量,如果该值远小于集合总文档数,说明索引生效。executionStats.totalKeysExamined:扫描的索引键数量,正常情况下应该和返回的文档数量接近。
四、复制集相关面试题
14. 什么是MongoDB复制集?有什么作用?
复制集是一组维护相同数据集的MongoDB实例,包含一个主节点(Primary)和多个从节点(Secondary),作用如下:
数据冗余:从节点会异步复制主节点的数据,即使主节点故障,数据也不会丢失。
高可用:主节点故障时,复制集会自动触发选举,从从节点中选出一个新的主节点,保证服务不中断。
读写分离:可以将读请求分发到从节点,减轻主节点的压力,提升整体吞吐量。
15. 复制集的主从节点有什么区别?读写请求如何分配?
主节点是唯一可以接收写请求的节点,所有写操作都会记录到主节点的oplog(操作日志)中。
从节点会异步拉取主节点的oplog,并重放这些操作,从而保持和主节点的数据同步。
默认情况下,读请求也可以发送到主节点,从节点默认不可读,需要手动设置
slaveOk才可以从从节点读取数据。注意:从节点的数据同步存在延迟,读从节点可能会读到旧数据,需要根据业务场景选择是否开启读写分离。
16. 复制集的选举机制是什么?
当主节点不可用(比如宕机、网络断开)时,复制集会触发选举流程:
所有从节点会检测主节点是否存活,如果主节点超过配置的选举超时时间没有响应,从节点就会发起选举。
每个从节点会给自己投票,同时请求其他节点给自己投票,获得多数票(超过复制集总节点数的一半)的节点会当选新的主节点。
为了防止脑裂(出现多个主节点),复制集的节点数建议设置为奇数,比如3节点、5节点。
17. 如何搭建一个3节点的MongoDB复制集?
步骤如下:
准备3台服务器(或同一台服务器的3个不同端口),分别创建数据存储目录和配置文件。
每个实例的配置文件需要指定
replication.replSetName为相同的复制集名称,比如rs0。启动3个MongoDB实例,然后连接到任意一个实例,执行初始化复制集的命令:
# 初始化复制集,指定三个节点的地址和端口
rs.initiate({
_id: "rs0",
members: [
{_id: 0, host: "127.0.0.1:27017"},
{_id: 1, host: "127.0.0.1:27018"},
{_id: 2, host: "127.0.0.1:27019"}
]
})
# 查看复制集状态
rs.status()五、分片相关面试题
18. 什么是MongoDB分片?什么时候需要用到分片?
分片是将数据分散存储到多个机器上的机制,每个机器上的数据集合称为一个分片(Shard),所有分片的数据组合起来是整个数据集。需要用到分片的场景:
单台机器的存储容量无法容纳全部数据,需要水平扩展存储。
单台机器的读写吞吐量达到瓶颈,需要分散请求压力。
数据量增长快,需要提前做好扩展规划,避免后续迁移成本过高。
19. 分片集群包含哪些组件?分别是什么作用?
MongoDB分片集群由三个核心组件组成:
分片(Shard):实际存储数据的节点,每个分片可以是单个MongoDB实例,也可以是一个复制集,生产环境建议分片使用复制集保证高可用。
配置服务器(Config Server):存储分片集群的元数据,包括分片和数据块的映射关系、集群的配置信息等,配置服务器也需要是复制集,防止单点故障。
查询路由(Mongos):客户端连接的中间件,负责将客户端的请求路由到对应的分片,对客户端来说,分片集群就像是一个普通的MongoDB实例,不需要关心数据具体存在哪个分片。
20. 什么是片键(Shard Key)?选择片键有什么原则?
片键是分片集合中用于划分数据的字段,MongoDB会根据片键的值将数据分散到不同的分片。选择片键的原则:
基数要高:片键的不同值要足够多,避免数据集中到少数几个分片,比如不要选择性别这种只有两个值的字段作为片键。
写分布均匀:片键的值要随机分布,避免大量写操作集中到同一个分片,比如不要选择自增的ID作为片键,否则所有新写入的数据都会进入同一个分片。
查询友好:片键要和业务查询的常用条件匹配,这样查询可以直接路由到对应的分片,不需要广播到所有分片。
避免单调递增或递减:否则会导致数据热点,影响分片的负载均衡效果。
21. 分片和复制集的区别是什么?
两者的核心区别如下:
| 对比维度 | 复制集 | 分片 |
|---|---|---|
| 核心目标 | 数据冗余、高可用 | 水平扩展存储和吞吐量 |
| 数据存储 | 所有节点存储全量数据 | 每个分片只存储部分数据,所有分片数据组合为全量数据 |
| 写请求 | 只有主节点可以处理写请求 | 写请求可以根据片键分散到不同分片处理 |
| 扩展性 | 扩展能力有限,主要提升可用性 | 可以无限水平扩展,适合大数据量场景 |
六、性能优化类面试题
22. 如何定位MongoDB的性能问题?
常用的定位方法:
开启慢查询日志,记录执行时间超过阈值的操作,分析慢查询的原因:
# 设置慢查询阈值为100毫秒,超过100ms的操作会被记录到日志中
db.setProfilingLevel(1, 100)
# 查看慢查询记录
db.system.profile.find().sort({ts: -1}).limit(10)使用
explain()分析查询的执行计划,查看是否使用了合适的索引,是否存在全集合扫描。监控服务器的资源使用情况,包括CPU、内存、磁盘IO、网络带宽,判断是否是硬件资源瓶颈。
查看复制集或分片的负载情况,判断是否是某个节点负载过高导致整体性能下降。
23. 有哪些常见的MongoDB性能优化手段?
可以从以下几个方面优化:
索引优化:根据查询条件创建合适的索引,删除无用索引,避免索引过多影响写性能;遵循最左前缀原则设计复合索引。
查询优化:只返回需要的字段,避免返回大量无用数据;合理使用分页,避免一次性查询过多数据;尽量让查询命中索引,减少全集合扫描。
写入优化:批量写入比单条写入效率高,尽量使用
insertMany、updateMany等批量操作;对于大量写入场景,可以暂时关闭不需要的索引,写入完成后再重建。硬件优化:使用SSD存储提升磁盘IO性能;保证足够的内存,MongoDB会尽可能将热数据缓存到内存中,内存不足会导致频繁的磁盘读写。
架构优化:读多写少的场景可以开启读写分离,将读请求分发到从节点;数据量大的场景可以引入分片,分散存储和请求压力。
24. MongoDB的内存管理机制是什么?
MongoDB使用操作系统的虚拟内存机制,同时内置了类似操作系统的页缓存机制:
MongoDB会尽可能将常用的数据和索引加载到内存中,称为工作集(Working Set)。
如果工作集大小小于可用内存,MongoDB的读写性能会非常高,因为大部分操作可以直接在内存中完成。
如果工作集大小超过可用内存,MongoDB会触发页换出,将不常用的数据写到磁盘,此时性能会大幅下降。
建议生产环境给MongoDB分配足够的内存,保证工作集可以完全放在内存中。
七、事务与一致性类面试题
25. MongoDB的事务支持有什么特点?
MongoDB 4.0版本开始支持多文档ACID事务,特点如下:
事务可以保证多个文档的读写操作要么全部成功,要么全部失败,满足ACID特性。
事务需要在复制集或分片集群环境下使用,单节点实例不支持事务。
事务有执行时间限制,默认是60秒,超过时间会自动回滚,避免长时间占用资源。
事务中的操作会加锁,并发的事务如果操作相同的数据可能会产生冲突,需要根据业务场景合理设计事务范围,避免大事务。
26. MongoDB的读写关注(Write Concern和Read Concern)是什么?
Write Concern(写关注):指定写操作的确认级别,比如:
{w: 1}:写操作只要主节点确认就返回成功,是默认级别。{w: "majority"}:写操作需要大多数节点(超过半数)确认才返回成功,保证数据不会因主节点故障而丢失。{j: true}:写操作需要写入日志才返回成功,进一步保证数据持久性。Read Concern(读关注):指定读操作的数据一致性级别,比如:
"local":读取最新的数据,可能会读到未提交的事务数据,是默认级别。"majority":只读取已经被大多数节点确认的数据,保证读到的数据是持久化的,不会回滚。"snapshot":读取事务开始时的快照数据,保证可重复读。
八、其他高频面试题
27. 如何备份和恢复MongoDB的数据?
MongoDB提供了mongodump和mongorestore工具用于备份和恢复:
# 备份整个数据库,输出到指定目录 mongodump --host 127.0.0.1 --port 27017 --out /data/backup/mongo_backup_20240501 # 备份指定的数据库 mongodump --host 127.0.0.1 --port 27017 --db test_db --out /data/backup/test_db_backup # 恢复整个备份数据到MongoDB mongorestore --host 127.0.0.1 --port 27017 /data/backup/mongo_backup_20240501 # 恢复指定数据库的数据,--drop表示恢复前先删除目标数据库中的原有数据 mongorestore --host 127.0.0.1 --port 27017 --db test_db --drop /data/backup/test_db_backup/test_db
28. 生产环境部署MongoDB有哪些注意事项?
复制集节点数建议为奇数,至少3节点,保证选举可以正常进行,避免脑裂。
分片集群的分片建议使用复制集,配置服务器也必须使用复制集,保证元数据安全。
关闭MongoDB的公开访问端口,只允许内网IP访问,避免数据泄露。
定期备份数据,验证备份数据的可恢复性,防止数据丢失。
开启慢查询日志,定期分析慢查询,优化索引和查询语句。
监控MongoDB的各项指标,包括CPU、内存、磁盘、连接数、操作延迟等,设置合理的告警阈值。
版本选择上,优先选择官方长期支持的稳定版本,避免使用最新但未经过大规模验证的版本。