mangoDB面试题及详细答案 117道(051-070)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

一、本文面试题目录

51. 如何实现MongoDB中的分页查询?

MongoDB中通过skip()limit()方法组合实现分页查询。skip(n)用于跳过前n条文档,limit(m)用于限制返回结果数量为m条。

示例:查询users集合中第2页的数据(每页10条)

// 第1页:skip(0) + limit(10)
db.users.find().skip(0).limit(10)
// 第2页:skip(10) + limit(10)
db.users.find().skip(10).limit(10)

原理skip()会先扫描并跳过指定数量的文档,limit()则截取后续指定数量的文档。但需注意,当skip()的参数值过大时(如超过10000),会导致性能下降,此时建议使用基于_id的范围查询优化分页(如db.users.find({_id: {$gt: lastId}}).limit(10))。

52. 如何实现文档的部分更新?

使用updateOne()updateMany()方法,结合更新操作符(如$set$inc等)实现部分字段更新,避免覆盖整个文档。

示例:更新users集合中name为"Alice"的文档,将age增加1,同时修改email字段

db.users.updateOne(
  { name: "Alice" }, // 查询条件
  { 
    $set: { email: "alice_new@example.com" }, // 设置字段值
    $inc: { age: 1 } // 自增字段值
  }
)

原理$set用于指定字段的新值,$inc用于对数字字段进行增减,仅修改指定字段,其他字段保持不变,减少数据传输和存储开销。

53. 如何实现文档的原子操作?

MongoDB的更新操作默认是原子性的,可通过$set$push$pull等操作符实现单文档的原子修改,确保多线程并发操作时的数据一致性。

示例:向users集合中某用户的hobbies数组原子添加一个元素

db.users.updateOne(
  { _id: ObjectId("60d21b4667d0d8992e610c85") },
  { $push: { hobbies: "hiking" } } // 原子性添加数组元素
)

原理:单文档操作在MongoDB中是原子的,即使多个请求同时修改同一文档,也会按顺序执行,避免中间状态暴露。对于多文档原子操作,需使用事务(4.0+版本支持)。

54. 如何查询数组中的元素?

使用$elemMatch操作符查询数组中满足多个条件的元素,或直接通过数组字段匹配单个条件。

示例1:查询users集合中hobbies数组包含"reading"的文档

db.users.find({ hobbies: "reading" })

示例2:查询scores数组中存在数学成绩大于80且英语成绩大于70的文档(scores为包含subjectscore的对象数组)

db.students.find({
  scores: {
    $elemMatch: { 
      subject: "math", 
      score: { $gt: 80 } 
    },
    $elemMatch: { 
      subject: "english", 
      score: { $gt: 70 } 
    }
  }
})

原理:直接匹配数组元素适用于单条件查询,$elemMatch适用于数组中对象的多条件联合查询,确保多个条件同时作用于同一个数组元素。

55. 如何删除数组中的元素?

使用$pull操作符从数组中移除满足条件的元素,$pop操作符移除数组首尾元素。

示例1:从users集合中某用户的hobbies数组中移除"gaming"

db.users.updateOne(
  { name: "Bob" },
  { $pull: { hobbies: "gaming" } } // 移除数组中值为"gaming"的元素
)

示例2:移除comments数组的最后一个元素

db.posts.updateOne(
  { _id: ObjectId("60d21b4667d0d8992e610c86") },
  { $pop: { comments: 1 } } // 1表示移除最后一个元素,-1移除第一个
)

原理$pull根据条件匹配并删除数组元素,$pop按位置删除,操作具有原子性,确保数组状态一致性。

56. 如何实现多条件查询?

使用逻辑操作符($and$or$not等)组合多个查询条件。

示例:查询users集合中age大于25且city为"Beijing",或email包含"@company.com"的文档

db.users.find({
  $or: [
    { $and: [{ age: { $gt: 25 } }, { city: "Beijing" }] },
    { email: /@company\.com/ } // 正则匹配邮箱
  ]
})

原理$and要求所有条件同时满足,$or只要任一条件满足即可,逻辑操作符可嵌套使用,灵活组合复杂查询条件。

57. 如何使用正则表达式进行模糊查询?

在查询条件中直接使用JavaScript正则表达式,或$regex操作符实现模糊匹配。

示例1:查询users集合中name以"J"开头的文档

db.users.find({ name: /^J/ }) // 正则表达式直接作为条件

示例2:查询description字段包含"mongodb"(不区分大小写)的文档

db.articles.find({
  description: { $regex: "mongodb", $options: "i" } // $options: "i"表示不区分大小写
})

原理:正则表达式匹配基于字符串内容,^匹配开头,$匹配结尾,$options可指定匹配模式(如大小写不敏感、多行匹配等)。注意:对大集合使用非锚定正则(如/mongodb/)可能无法使用索引,影响性能。

58. 如何查询指定日期范围内的文档?

使用$gte(大于等于)和$lte(小于等于)操作符,结合Date对象查询日期字段在指定范围内的文档。

示例:查询orders集合中2023年1月1日至2023年1月31日创建的订单

db.orders.find({
  createTime: {
    $gte: new Date("2023-01-01"),
    $lte: new Date("2023-01-31T23:59:59")
  }
})

原理:MongoDB中的日期以UTC时间存储,Date对象会被转换为相应的时间戳进行比较,确保日期范围查询的准确性。

59. 如何实现聚合查询中的分组统计?

使用聚合框架的$group操作符,按指定字段分组,并结合$sum$avg等累加器计算统计数据。

示例:统计orders集合中每个用户的订单总数和总金额

db.orders.aggregate([
  {
    $group: {
      _id: "$userId", // 按userId分组
      orderCount: { $sum: 1 }, // 统计每组文档数量(订单总数)
      totalAmount: { $sum: "$amount" } // 累加每组的amount字段(总金额)
    }
  }
])

原理$group将文档按_id指定的字段分组,累加器($sum$avg$max等)对每组数据进行计算,生成统计结果。$sum: 1表示每组包含的文档数量,$sum: "$amount"表示累加每组中amount字段的值。

60. 如何在聚合查询中过滤数据?

使用$match操作符在聚合管道的开头过滤文档,减少后续处理的数据量,提高效率。

示例:先过滤出status为"completed"的订单,再按userId分组统计总金额

db.orders.aggregate([
  { $match: { status: "completed" } }, // 过滤条件:只处理已完成的订单
  {
    $group: {
      _id: "$userId",
      totalAmount: { $sum: "$amount" }
    }
  }
])

原理$match类似于find()的查询条件,应尽可能放在聚合管道的早期,利用索引过滤数据,减少后续阶段需要处理的文档数量,提升聚合性能。

61. 如何实现两个集合的关联查询(类似SQL的JOIN)?

使用聚合框架的$lookup操作符实现集合间的左外连接(Left Outer Join),关联两个集合的数据。

示例:关联orders集合和users集合,通过userId获取订单对应的用户信息

db.orders.aggregate([
  {
    $lookup: {
      from: "users", // 要关联的集合名
      localField: "userId", // 当前集合(orders)中的关联字段
      foreignField: "_id", // 目标集合(users)中的关联字段
      as: "userInfo" // 关联结果存储的数组字段名
    }
  },
  { $unwind: "$userInfo" } // 将数组拆分为单个文档(若确定一对一关系)
])

原理$lookup会对orders中的每个文档,从users集合中匹配_id等于orders.userId的文档,将匹配结果放入userInfo数组。$unwind用于将数组转换为单个对象(适用于一对一关联)。

62. 如何使用全文索引进行文本搜索?

先创建全文索引,再使用$text$search操作符进行文本检索。

示例1:为articles集合的titlecontent字段创建全文索引

db.articles.createIndex({ title: "text", content: "text" })

示例2:查询包含"mongodb"或"database"的文档,并按相关性排序

db.articles.find(
  { $text: { $search: "mongodb database" } }, // 搜索关键词(空格分隔表示"或")
  { score: { $meta: "textScore" } } // 包含相关性得分
).sort({ score: { $meta: "textScore" } }) // 按相关性降序排序

原理:全文索引会对指定字段的文本内容进行分词,支持多语言分词规则,$search中的关键词默认按"或"逻辑匹配,可通过""表示精确短语,-表示排除某个词。

63. 如何实现数据的导入和导出?

使用mongoimportmongoexport命令行工具,导入/导出JSON或CSV格式的数据。

示例1:导出users集合的数据到JSON文件

mongoexport --uri "mongodb://localhost:27017/mydb" --collection users --out users.json

示例2:从CSV文件导入数据到products集合(CSV首行为字段名)

mongoimport --uri "mongodb://localhost:27017/mydb" --collection products --type csv --headerline --file products.csv

原理mongoexport将集合数据序列化为JSON/CSV,mongoimport读取文件并解析为BSON文档插入集合,支持指定字段映射、分隔符等参数,适合数据迁移或备份。

64. 如何使用事务处理多文档操作?

在MongoDB 4.0+版本中,通过startSession()创建事务会话,使用withTransaction()方法执行多文档事务操作。

示例:转账场景,从A用户扣钱并向B用户加钱,确保操作原子性

const session = db.getMongo().startSession();
session.startTransaction();

try {
  // 扣减A用户余额
  db.users.updateOne(
    { _id: ObjectId("60d21b4667d0d8992e610c85") },
    { $inc: { balance: -100 } },
    { session }
  );

  // 增加B用户余额
  db.users.updateOne(
    { _id: ObjectId("60d21b4667d0d8992e610c86") },
    { $inc: { balance: 100 } },
    { session }
  );

  // 提交事务
  await session.commitTransaction();
} catch (error) {
  // 发生错误时回滚事务
  session.abortTransaction();
  throw error;
} finally {
  session.endSession();
}

原理:事务确保多个文档操作要么全部成功(commitTransaction),要么全部失败(abortTransaction),避免部分操作成功导致的数据不一致。事务需在副本集环境中使用,4.2+版本支持跨分片事务。

65. 如何监控集合的大小和索引大小?

使用db.collection.stats()方法获取集合的详细统计信息,包括数据大小、索引大小、文档数量等。

示例:查看users集合的统计信息

db.users.stats()

返回结果中的关键字段:

  • size:集合数据总大小(字节),不包括索引。
  • totalIndexSize:所有索引的总大小(字节)。
  • count:文档总数。
  • avgObjSize:平均文档大小。

原理stats()方法通过扫描集合元数据计算统计信息,帮助评估存储占用、索引效率,为容量规划提供依据。

66. 如何删除集合中的重复文档?

通过聚合查询找出重复文档,再结合deleteMany()删除重复项,保留一条记录。

示例:删除users集合中email重复的文档,只保留_id最小的一条

// 步骤1:找出重复的email及其对应的文档ID
const duplicates = db.users.aggregate([
  { $group: { 
    _id: "$email", // 按email分组
    ids: { $push: "$_id" }, // 收集每组的所有_id
    count: { $sum: 1 } // 统计重复次数
  }},
  { $match: { count: { $gt: 1 } } } // 筛选出重复次数>1的组
]);

// 步骤2:删除每组中除第一个ID外的其他文档
duplicates.forEach(doc => {
  doc.ids.shift(); // 移除第一个ID(保留的记录)
  db.users.deleteMany({ _id: { $in: doc.ids } }); // 删除剩余重复ID的文档
});

原理:通过$group聚合找出重复字段的文档ID列表,排除保留的ID后,批量删除其余重复文档,确保数据唯一性。

67. 如何设置集合的TTL索引自动删除过期数据?

创建TTL(Time-To-Live)索引,指定字段为日期类型,MongoDB会自动删除字段值超过指定时间的文档。

示例:为logs集合的createTime字段创建TTL索引,设置24小时后自动删除文档

db.logs.createIndex(
  { createTime: 1 }, // 日期字段
  { expireAfterSeconds: 86400 } // 过期时间(秒),86400秒=24小时
)

原理:TTL索引会定期检查文档的日期字段,当当前时间减去字段值大于expireAfterSeconds时,自动删除文档。适用于日志、临时会话等需要自动清理的数据。注意:字段必须是Date类型,且一个集合只能有一个TTL索引。

68. 如何使用地理空间索引查询附近的位置?

创建2dsphere索引,结合$near操作符查询指定坐标附近的地理空间数据。

示例1:为stores集合的location字段(存储经纬度)创建2dsphere索引

db.stores.createIndex({ location: "2dsphere" })

示例2:查询距离坐标(116.4, 39.9)10公里范围内的商店

db.stores.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [116.4, 39.9] // 经度, 纬度
      },
      $maxDistance: 10000 // 最大距离(米),10000米=10公里
    }
  }
})

原理2dsphere索引支持球面几何计算,$near按距离排序返回结果,$maxDistance限制最大距离,适用于LBS(基于位置的服务)场景,如附近商家查询。

69. 如何限制集合的最大文档数量?

创建固定大小集合(Capped Collection),指定最大文档数或最大存储空间

69. 如何限制集合的最大文档数量?(续)

(接上段)通过创建固定大小集合(Capped Collection),指定最大文档数或最大存储空间。

示例:创建一个名为log的固定大小集合,限制最多存储20000个文档,且最大存储空间为5MB(5242880字节)

db.createCollection( "log", { capped: true, size: 5242880, max: 20000 } )

原理:固定大小集合按插入顺序存储文档,类似循环缓冲区。达到最大文档数或存储空间后,新文档会覆盖最旧的文档,无需额外脚本删除。size参数指定集合的最大字节数,max指定最大文档数。若size先达到,即使max未满足,也会开始覆盖旧文档。注意:固定大小集合对写入操作序列化,并发写入性能低于非固定大小集合。

70. 如何在MongoDB中实现数据的版本控制?

在文档中添加版本号字段(如version),每次更新时递增版本号,利用乐观锁机制保证并发更新的正确性。

示例:更新products集合中_id60d21b4667d0d8992e610c87的产品价格,同时递增版本号

// 先查询当前文档及版本号
const product = db.products.findOne({ _id: ObjectId("60d21b4667d0d8992e610c87") });
const currentVersion = product.version;

// 更新操作,条件为版本号未变
const updateResult = db.products.updateOne(
  { _id: ObjectId("60d21b4667d0d8992e610c87"), version: currentVersion },
  { 
    $set: { price: 99.99 },
    $inc: { version: 1 }
  }
);

if (updateResult.matchedCount === 0) {
  // 版本冲突,重新读取数据并更新
  // 这里省略重试逻辑,实际应用中可循环重试
}

原理:读取文档时记录版本号,更新时对比版本号,若相同则更新并递增版本号;若不同,说明数据已被其他操作修改,更新失败,应用层需处理冲突(如重新读取数据、重试更新)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还是大剑师兰特

打赏一杯可口可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值