Skip to content

MongoDB 面试题

一、MongoDB 基础

1. MongoDB 是什么?

MongoDB 是一个开源的、面向文档的 NoSQL 数据库。

核心特性:

  • 文档存储 - 使用 BSON(Binary JSON)格式存储数据
  • 模式灵活 - 无需预定义表结构(Schema-less)
  • 水平扩展 - 支持分片(Sharding)
  • 高可用 - 支持复制集(Replica Set)
  • 丰富查询 - 支持复杂查询和聚合

2. MongoDB vs 关系型数据库

特性MongoDBMySQL
数据模型文档(Document)行(Row)
表结构集合(Collection)表(Table)
数据库结构数据库(Database)数据库(Database)
模式无模式(Schema-less)固定模式(Schema)
事务支持(4.0+)完全支持
JOIN有限支持($lookup)完全支持
水平扩展原生支持需中间件
ACID支持(4.0+)完全支持

3. MongoDB 的优势和劣势

优势:

  • ✅ 灵活的文档模型
  • ✅ 水平扩展能力强
  • ✅ 高性能读写
  • ✅ 丰富的查询功能
  • ✅ 易于开发和迭代

劣势:

  • ❌ 内存占用较大
  • ❌ 不支持复杂 JOIN
  • ❌ 事务支持相对较晚(4.0+)
  • ❌ 数据一致性相对较弱(最终一致性)

二、核心概念

1. 文档(Document)

文档结构:

json
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "Alice",
  "age": 30,
  "email": "alice@example.com",
  "address": {
    "city": "Beijing",
    "street": "Chang'an Street"
  },
  "tags": ["developer", "mongodb"],
  "created_at": ISODate("2025-01-01T00:00:00Z")
}

字段类型:

  • String、Number、Boolean
  • Array、Object(嵌套文档)
  • Date、ObjectId、Null
  • Binary Data、Regular Expression

2. 集合(Collection)

集合特点:

  • 类似关系型数据库的"表"
  • 无需预定义结构
  • 可以存储不同结构的文档

创建集合:

javascript
// 显式创建
db.createCollection("users");

// 隐式创建(插入文档时自动创建)
db.users.insertOne({ name: "Alice" });

3. 索引(Index)

索引类型:

单字段索引:

javascript
// 创建单字段索引
db.users.createIndex({ email: 1 }); // 1 表示升序,-1 表示降序

// 唯一索引
db.users.createIndex({ email: 1 }, { unique: true });

// 稀疏索引(只索引有该字段的文档)
db.users.createIndex({ phone: 1 }, { sparse: true });

复合索引:

javascript
// 创建复合索引
db.users.createIndex({ name: 1, age: -1 });

// 查询顺序必须遵循最左前缀原则
// ✅ 可以使用索引
db.users.find({ name: "Alice" });
db.users.find({ name: "Alice", age: 30 });

// ❌ 不能使用索引
db.users.find({ age: 30 });

文本索引:

javascript
// 创建文本索引
db.articles.createIndex({ title: "text", content: "text" });

// 文本搜索
db.articles.find({ $text: { $search: "mongodb tutorial" } });

地理空间索引:

javascript
// 2dsphere 索引(地球表面)
db.places.createIndex({ location: "2dsphere" });

// 查询附近的位置
db.places.find({
  location: {
    $near: {
      $geometry: { type: "Point", coordinates: [116.3974, 39.9093] },
      $maxDistance: 1000 // 1000 米
    }
  }
});

TTL 索引(自动删除过期文档):

javascript
// 创建 TTL 索引(30 秒后自动删除)
db.logs.createIndex({ created_at: 1 }, { expireAfterSeconds: 30 });

4. 查询操作

基本查询:

javascript
// 查询所有
db.users.find();

// 条件查询
db.users.find({ age: 30 });
db.users.find({ age: { $gte: 18, $lte: 65 } });
db.users.find({ name: { $in: ["Alice", "Bob"] } });

// 逻辑查询
db.users.find({ $or: [{ age: 30 }, { city: "Beijing" }] });
db.users.find({ $and: [{ age: { $gte: 18 } }, { age: { $lte: 65 } }] });

// 模糊查询
db.users.find({ name: /^A/ }); // 以 A 开头
db.users.find({ name: /Alice$/ }); // 以 Alice 结尾

// 存在性查询
db.users.find({ email: { $exists: true } });
db.users.find({ phone: { $exists: false } });

// 空值查询
db.users.find({ email: null });
db.users.find({ email: { $in: [null], $exists: true } }); // null 且存在

投影(只返回指定字段):

javascript
// 只返回 name 和 email
db.users.find({}, { name: 1, email: 1, _id: 0 });

// 排除 email
db.users.find({}, { email: 0 });

排序、限制、跳过:

javascript
// 排序
db.users.find().sort({ age: 1 }); // 升序
db.users.find().sort({ age: -1 }); // 降序

// 限制结果数量
db.users.find().limit(10);

// 跳过
db.users.find().skip(10).limit(10); // 分页

// 组合使用
db.users.find().sort({ created_at: -1 }).skip(20).limit(10);

5. 更新操作

更新方法:

javascript
// updateOne - 更新一条
db.users.updateOne(
  { email: "alice@example.com" },
  { $set: { age: 31 } }
);

// updateMany - 更新多条
db.users.updateMany(
  { city: "Beijing" },
  { $set: { region: "North" } }
);

// replaceOne - 替换整个文档
db.users.replaceOne(
  { email: "alice@example.com" },
  { name: "Alice", age: 31, email: "alice@example.com" }
);

更新操作符:

javascript
// $set - 设置字段
db.users.updateOne({ _id: 1 }, { $set: { age: 31 } });

// $unset - 删除字段
db.users.updateOne({ _id: 1 }, { $unset: { phone: "" } });

// $inc - 增加数值
db.users.updateOne({ _id: 1 }, { $inc: { age: 1 } });

// $push - 添加数组元素
db.users.updateOne({ _id: 1 }, { $push: { tags: "new" } });

// $pull - 删除数组元素
db.users.updateOne({ _id: 1 }, { $pull: { tags: "old" } });

// $addToSet - 添加唯一元素
db.users.updateOne({ _id: 1 }, { $addToSet: { tags: "new" } });

// $rename - 重命名字段
db.users.updateOne({ _id: 1 }, { $rename: { "old_field": "new_field" } });

三、聚合(Aggregation)

1. 聚合管道(Aggregation Pipeline)

管道操作符:

javascript
// $match - 过滤
db.orders.aggregate([
  { $match: { status: "completed" } }
]);

// $group - 分组
db.orders.aggregate([
  { $group: {
    _id: "$customer_id",
    total: { $sum: "$amount" },
    count: { $sum: 1 },
    avg: { $avg: "$amount" }
  }}
]);

// $project - 投影
db.orders.aggregate([
  { $project: {
    customer_id: 1,
    amount: 1,
    date: { $dateToString: { format: "%Y-%m-%d", date: "$created_at" } }
  }}
]);

// $sort - 排序
db.orders.aggregate([
  { $sort: { amount: -1 } }
]);

// $limit - 限制
db.orders.aggregate([
  { $limit: 10 }
]);

// $skip - 跳过
db.orders.aggregate([
  { $skip: 10 }
]);

// $lookup - 关联(类似 JOIN)
db.orders.aggregate([
  {
    $lookup: {
      from: "users",
      localField: "customer_id",
      foreignField: "_id",
      as: "customer"
    }
  }
]);

复杂聚合示例:

javascript
// 统计每个客户的订单总额和平均金额
db.orders.aggregate([
  { $match: { status: "completed" } }, // 过滤已完成订单
  { $group: {
    _id: "$customer_id",
    total: { $sum: "$amount" },
    avg: { $avg: "$amount" },
    count: { $sum: 1 }
  }},
  { $sort: { total: -1 } }, // 按总额降序
  { $limit: 10 } // 取前 10
]);

2. Map-Reduce(已废弃)

注意: Map-Reduce 在 MongoDB 5.0+ 中已废弃,推荐使用聚合管道。

四、复制集(Replica Set)

1. 复制集概念

复制集组成:

  • Primary(主节点) - 处理所有写操作
  • Secondary(从节点) - 复制主节点数据
  • Arbiter(仲裁节点) - 不存储数据,只参与选举

复制集优势:

  • ✅ 高可用(自动故障转移)
  • ✅ 数据冗余(多份副本)
  • ✅ 读写分离(从节点可以读)

2. 复制集配置

javascript
// 初始化复制集
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "localhost:27017" },
    { _id: 1, host: "localhost:27018" },
    { _id: 2, host: "localhost:27019" }
  ]
});

// 查看状态
rs.status();

// 添加节点
rs.add("localhost:27020");

// 删除节点
rs.remove("localhost:27020");

3. 读写分离

从节点读取:

javascript
// 连接到从节点
var db = connect("localhost:27017/rs0");

// 设置读偏好
db.setReadPreference("secondary"); // 从从节点读

// 或在查询时指定
db.users.find({}).readPref("secondary");

读偏好选项:

  • primary - 只从主节点读(默认)
  • primaryPreferred - 优先主节点,不可用时从从节点读
  • secondary - 只从从节点读
  • secondaryPreferred - 优先从节点,不可用时从主节点读
  • nearest - 从延迟最低的节点读

五、分片(Sharding)

1. 分片概念

分片组件:

  • Shard(分片) - 存储数据
  • Config Server(配置服务器) - 存储元数据
  • Mongos(路由) - 路由查询请求

分片优势:

  • ✅ 水平扩展(突破单机限制)
  • ✅ 负载均衡(数据分散到多个分片)
  • ✅ 高可用(分片故障不影响整体)

2. 分片键(Shard Key)

选择分片键的原则:

  • ✅ 高基数(唯一值多)
  • ✅ 低频率(分布均匀)
  • ✅ 单调递增(避免热点)

分片策略:

javascript
// 启用分片
sh.enableSharding("mydb");

// 创建分片集合
sh.shardCollection("mydb.users", { user_id: 1 }); // 单字段分片键

// 复合分片键
sh.shardCollection("mydb.orders", { customer_id: 1, order_date: 1 });

哈希分片:

javascript
// 使用哈希分片键(适合范围查询少的场景)
sh.shardCollection("mydb.logs", { _id: "hashed" });

六、.NET Core 集成

1. 安装驱动

bash
dotnet add package MongoDB.Driver

2. 连接 MongoDB

csharp
using MongoDB.Driver;

var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("mydb");
var collection = database.GetCollection<BsonDocument>("users");

3. 基本操作

插入文档:

csharp
var user = new BsonDocument
{
    { "name", "Alice" },
    { "age", 30 },
    { "email", "alice@example.com" }
};
await collection.InsertOneAsync(user);

查询文档:

csharp
// 查询单条
var filter = Builders<BsonDocument>.Filter.Eq("email", "alice@example.com");
var user = await collection.Find(filter).FirstOrDefaultAsync();

// 查询多条
var users = await collection.Find(_ => true).ToListAsync();

// 条件查询
var filter = Builders<BsonDocument>.Filter.And(
    Builders<BsonDocument>.Filter.Gte("age", 18),
    Builders<BsonDocument>.Filter.Lte("age", 65)
);
var users = await collection.Find(filter).ToListAsync();

更新文档:

csharp
var filter = Builders<BsonDocument>.Filter.Eq("email", "alice@example.com");
var update = Builders<BsonDocument>.Update.Set("age", 31);
await collection.UpdateOneAsync(filter, update);

删除文档:

csharp
var filter = Builders<BsonDocument>.Filter.Eq("email", "alice@example.com");
await collection.DeleteOneAsync(filter);

4. 使用强类型模型

csharp
public class User
{
    public ObjectId Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

var collection = database.GetCollection<User>("users");

// 插入
var user = new User { Name = "Alice", Age = 30, Email = "alice@example.com" };
await collection.InsertOneAsync(user);

// 查询
var filter = Builders<User>.Filter.Eq(u => u.Email, "alice@example.com");
var user = await collection.Find(filter).FirstOrDefaultAsync();

七、常见面试题

Q1: MongoDB 为什么使用 B+ 树索引?

MongoDB 使用 B 树索引(不是 B+ 树),因为:

  • 范围查询 - B 树适合范围查询
  • 随机访问 - B 树节点可以存储数据
  • 写入性能 - B 树写入性能较好

注意: MongoDB 使用的是 B 树,不是 B+ 树。

Q2: MongoDB 如何保证数据一致性?

最终一致性:

  • 主从复制是异步的
  • 写入主节点后,从节点可能还没同步
  • 读取从节点可能读到旧数据

强一致性:

  • 使用写关注(Write Concern)
  • 使用读关注(Read Concern)
javascript
// 写关注
db.users.insertOne(
  { name: "Alice" },
  { writeConcern: { w: "majority", wtimeout: 5000 } }
);

// 读关注
db.users.find({}).readConcern("majority");

Q3: MongoDB 事务支持?

MongoDB 4.0+ 支持事务:

csharp
using var session = await client.StartSessionAsync();
session.StartTransaction();

try
{
    await collection1.InsertOneAsync(session, doc1);
    await collection2.InsertOneAsync(session, doc2);
    await session.CommitTransactionAsync();
}
catch
{
    await session.AbortTransactionAsync();
    throw;
}

限制:

  • 事务中的操作必须在同一个分片上(分片集群)
  • 事务时间不能太长(默认 60 秒)

Q4: MongoDB 如何优化查询性能?

  1. 创建索引

    javascript
    db.users.createIndex({ email: 1 });
  2. 使用投影

    javascript
    db.users.find({}, { name: 1, email: 1 });
  3. 使用限制和排序

    javascript
    db.users.find().sort({ created_at: -1 }).limit(10);
  4. 使用覆盖索引

    javascript
    // 索引包含查询所需的所有字段
    db.users.createIndex({ email: 1, name: 1 });
  5. 避免全表扫描

    • 使用索引字段查询
    • 避免正则表达式(除非前缀匹配)

Q5: MongoDB 和关系型数据库的选择?

选择 MongoDB:

  • ✅ 数据结构灵活,经常变化
  • ✅ 水平扩展需求高
  • ✅ 大量非结构化数据
  • ✅ 高并发读写

选择关系型数据库:

  • ✅ 数据结构稳定
  • ✅ 需要复杂 JOIN
  • ✅ 强一致性要求
  • ✅ 事务复杂

八、最佳实践

  1. 合理使用索引 - 提升查询性能
  2. 使用投影 - 只返回需要的字段
  3. 避免全表扫描 - 使用索引字段查询
  4. 合理设计文档结构 - 避免过度嵌套
  5. 使用复制集 - 实现高可用
  6. 监控慢查询 - 使用 profiler
  7. 定期备份 - 防止数据丢失
  8. 设置合理的写关注 - 平衡性能和数据安全
  9. 避免大文档 - 单个文档不超过 16MB
  10. 不要在生产环境使用 Map-Reduce - 使用聚合管道

基于 VitePress 构建 | Copyright © 2026-present