MongoDB面试题分类整理
文章目录
一、基础概念
1. MongoDB是什么?与传统关系型数据库有什么区别?
答案:
MongoDB是一个文档型NoSQL数据库,它用类似JSON的BSON格式存储数据。与传统关系型数据库(RDBMS)的主要区别:
- 数据模型:MongoDB是文档模型,RDBMS是表模型
- Schema:MongoDB是动态Schema(不需要预先定义表结构),RDBMS是固定Schema
- 扩展方式:MongoDB更容易水平扩展(分片),RDBMS通常垂直扩展
- 事务支持:早期MongoDB不支持多文档事务,现在已支持(4.0+版本)
- JOIN操作:MongoDB用$lookup模拟JOIN,性能不如RDBMS的JOIN
原理:想象MongoDB像一个大的文件柜,每个文档就是一个独立的文件,你可以随时往里面添加各种格式的文件。而RDBMS像Excel表格,必须预先定义好列名和数据类型。
2. 解释MongoDB中的文档、集合和数据库
答案:
- 文档(Document):相当于RDBMS中的一行记录,用BSON格式存储(类似JSON)
- 集合(Collection):相当于RDBMS中的表,是一组文档的容器
- 数据库(Database):一组集合的容器,一个MongoDB实例可以包含多个数据库
原理:可以想象MongoDB的结构像一本书:
- 数据库=书
- 集合=书的章节
- 文档=章节中的段落
二、CRUD操作
1. MongoDB如何插入文档?
答案:
// 插入单个文档
db.collection.insertOne({name: "张三", age: 25})
// 插入多个文档
db.collection.insertMany([
{name: "李四", age: 30},
{name: "王五", age: 28}
])
原理:MongoDB接收到插入命令后,会先将数据转换为BSON格式,然后写入内存和磁盘。默认情况下,写入操作会等待磁盘确认后才返回成功。
2. 如何更新文档?set和unset有什么区别?
答案:
// 更新单个文档
db.collection.updateOne(
{name: "张三"},
{$set: {age: 26}}
)
// $set用于设置字段值
db.collection.updateOne(
{name: "张三"},
{$set: {address: "北京"}}
)
// $unset用于删除字段
db.collection.updateOne(
{name: "张三"},
{$unset: {address: ""}}
)
原理:set就像给文档打补丁,只修改指定的字段,其他字段不变。unset则是从文档中移除指定字段。如果不使用$set而直接更新,整个文档会被替换。
三、索引与性能优化
1. MongoDB索引有哪些类型?
答案:
- 单字段索引:在单个字段上创建的索引
- 复合索引:在多个字段上创建的索引
- 多键索引:为数组字段中的每个元素创建索引
- 文本索引:支持字符串内容的全文搜索
- 地理空间索引:支持地理位置查询
- 哈希索引:将字段值哈希后创建索引
- TTL索引:自动删除过期的文档
- 唯一索引:确保字段值唯一
原理:索引就像书的目录,可以快速找到内容而不需要逐页查找。MongoDB使用B树数据结构存储索引,不同类型的索引针对不同的查询场景优化。
2. 如何分析查询性能?
答案:
// 使用explain()分析查询
db.collection.find({name: "张三"}).explain("executionStats")
// 主要关注以下指标:
// - executionTimeMillis:执行时间(毫秒)
// - totalDocsExamined:扫描的文档数
// - totalKeysExamined:扫描的索引键数
// - stage:查询执行阶段(COLLSCAN表示全表扫描,IXSCAN表示索引扫描)
原理:explain()就像MongoDB的X光机,可以查看查询执行的内部过程。好的查询应该尽量减少扫描的文档数,尽量使用索引扫描而非全表扫描。
四、聚合框架
1. 解释MongoDB的聚合管道
答案:
聚合管道是一系列数据处理阶段,文档依次通过这些阶段被处理。常用阶段包括:
- $match:过滤文档(类似WHERE)
- $project:选择/重命名字段(类似SELECT)
- $group:分组聚合(类似GROUP BY)
- $sort:排序(类似ORDER BY)
- limit/skip:分页
- $lookup:关联查询(类似JOIN)
示例:
db.orders.aggregate([
{$match: {status: "completed"}},
{$group: {_id: "$customer", total: {$sum: "$amount"}}},
{$sort: {total: -1}},
{$limit: 10}
])
原理:想象聚合管道像工厂的流水线,文档是产品,每个阶段是一个工作站,产品经过每个工作站都会被加工处理,最终得到想要的结果。
2. $lookup如何工作?有什么限制?
答案:
$lookup实现类似SQL的LEFT OUTER JOIN:
db.orders.aggregate([
{
$lookup: {
from: "customers", // 要关联的集合
localField: "customerId", // 本地字段
foreignField: "_id", // 外部集合字段
as: "customerInfo" // 输出数组字段
}
}
])
限制:
- 关联的两个集合必须在同一数据库中
- 性能可能不如RDBMS的JOIN高效
- 大数据集可能导致内存问题
原理:$lookup就像你去图书馆借书,先查自己的借书卡(localField),然后去书库(from)找对应的书(foreignField),最后把找到的书信息(customerInfo)带回来。
五、复制集(Replica Set)
1. 什么是MongoDB复制集?有什么作用?
答案:
复制集是一组维护相同数据集的MongoDB实例,包含:
- 1个主节点(Primary):处理所有写操作
- 1个或多个从节点(Secondary):复制主节点数据
- 可选的仲裁节点(Arbiter):不存储数据,只参与选举
作用:
- 高可用性:主节点故障时自动故障转移
- 数据冗余:多副本防止数据丢失
- 读写分离:从节点可以处理读请求
原理:复制集就像公司里的团队,主节点是经理(负责决策和写操作),从节点是员工(复制经理的决定),当经理休假时,员工们会选举新的经理。
2. 解释MongoDB的选举机制
答案:
当主节点不可用时(心跳超时),从节点会发起选举。选举规则:
- 获得大多数成员投票的节点成为新主节点
- 优先级高的节点更可能成为主节点
- 数据最新的节点优先
- 仲裁节点只参与投票,不存储数据
原理:选举就像班级选班长,同学们(节点)投票,得票最多且符合条件(成绩好/数据新)的同学当选。大多数同学(超过半数)必须参与投票才有效。
六、分片(Sharding)
1. 什么是分片?为什么要分片?
答案:
分片是将大数据集水平分割到多个MongoDB实例(分片)上的方法。
为什么要分片:
- 数据量超过单机存储容量
- 读写吞吐量超过单机处理能力
- 想实现地理分布的数据存储
分片组件:
- 分片(Shard):存储实际数据
- 配置服务器(Config Server):存储集群元数据
- 查询路由器(mongos):路由查询到正确的分片
原理:分片就像把一本大百科全书拆分成多卷,每卷放在不同的书架上。目录(配置服务器)告诉你哪部分内容在哪卷,图书管理员(mongos)帮你找到需要的卷。
2. 解释分片键的选择策略
答案:
分片键的选择至关重要,好的分片键应该:
- 基数高:有很多不同的值(如用户ID)
- 写分布均匀:避免热点(所有写操作集中到一个分片)
- 匹配查询模式:常用查询条件应包含分片键
常见策略:
- 哈希分片:均匀分布但无法范围查询
- 范围分片:支持范围查询但可能导致数据分布不均
- 复合分片:结合多个字段
原理:选择分片键就像选择如何切蛋糕。如果按水果种类切(范围分片),草莓部分可能很大而蓝莓部分很小;如果随机切(哈希分片),每块大小差不多但找不到特定水果的位置。
七、事务与一致性
1. MongoDB如何实现多文档事务?
答案:
从MongoDB 4.0开始支持多文档事务:
session.startTransaction()
try {
db.accounts.updateOne({name: "A"}, {$inc: {balance: -100}}, {session})
db.accounts.updateOne({name: "B"}, {$inc: {balance: 100}}, {session})
session.commitTransaction()
} catch (error) {
session.abortTransaction()
}
限制:
- 事务中的操作必须属于同一个分片(分片集群中)
- 事务最大运行时间有限制(默认60秒)
- 会影响性能
原理:MongoDB事务就像银行转账操作,要么两个账户都更新成功,要么都失败回滚。内部使用快照隔离机制,保证事务看到一致的数据视图。
2. MongoDB的写关注(Write Concern)和读偏好(Read Preference)是什么?
答案:
写关注:控制写操作的确认级别
// 写入主节点后确认
db.collection.insert({...}, {writeConcern: {w: 1}})
// 写入大多数节点后确认
db.collection.insert({...}, {writeConcern: {w: "majority"}})
读偏好:控制从哪个节点读取
// 从主节点读取(默认)
db.collection.find().readPref("primary")
// 从从节点读取
db.collection.find().readPref("secondary")
原理:写关注就像寄信,你可以选择"投递到邮箱"(w:1)就确认,或者"对方签收"(w:“majority”)才确认。读偏好就像读书,你可以选择读原版(primary)或复印件(secondary)。
八、安全与运维
1. MongoDB如何实现身份验证和授权?
答案:
身份验证:
- 启用认证:启动时加
--auth
参数 - 创建用户:
db.createUser({
user: "admin",
pwd: "password",
roles: ["userAdminAnyDatabase"]
})
授权:
基于角色的访问控制(RBAC),常用内置角色:
- read:只读
- readWrite:读写
- dbAdmin:数据库管理
- userAdmin:用户管理
- clusterAdmin:集群管理
原理:MongoDB的安全机制像公司的门禁系统,用户需要用户名密码(身份验证)才能进入,然后根据工牌(角色)决定能进入哪些区域(数据库)和做什么操作(权限)。
2. 如何备份和恢复MongoDB数据?
答案:
备份:
- mongodump工具:
mongodump --uri="mongodb://user:password@host:port/db" --out=/backup/path
- 文件系统快照(需要存储引擎支持)
mongorestore --uri="mongodb://user:password@host:port/db" /backup/path
原理:mongodump像复印机,把数据库内容复制到文件;mongorestore像扫描仪,把文件内容重新导入数据库。对于大数据集,文件系统快照效率更高(像给数据库拍照片)。
九、高级特性
1. 什么是Change Stream?如何使用?
答案:
Change Stream允许应用程序实时监听数据库变更:
const changeStream = db.collection.watch()
changeStream.on("change", (change) => {
console.log("Change detected:", change)
})
使用场景:
- 实时数据同步
- 事件驱动架构
- 数据变更审计
原理:Change Stream像数据库的"消息通知",当数据发生变化时,会推送变更事件给监听者。底层使用oplog(操作日志)实现。
2. MongoDB的存储引擎有哪些?有什么区别?
答案:
主要存储引擎:
- WiredTiger(默认):
- 支持文档级并发控制
- 支持压缩
- 使用B+树存储数据
- In-Memory(企业版):
- 所有数据存储在内存中
- 极高性能但不持久化
- MMAPv1(已废弃):
- MongoDB早期引擎
- 集合级锁,性能较差
原理:存储引擎就像汽车的发动机,WiredTiger是现代的涡轮增压发动机(高效节能),In-Memory是赛车发动机(极快但不耐用),MMAPv1是老式发动机(已淘汰)。