《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 51. 如何实现MongoDB中的分页查询?
- 52. 如何实现文档的部分更新?
- 53. 如何实现文档的原子操作?
- 54. 如何查询数组中的元素?
- 55. 如何删除数组中的元素?
- 56. 如何实现多条件查询?
- 57. 如何使用正则表达式进行模糊查询?
- 58. 如何查询指定日期范围内的文档?
- 59. 如何实现聚合查询中的分组统计?
- 60. 如何在聚合查询中过滤数据?
- 61. 如何实现两个集合的关联查询(类似SQL的JOIN)?
- 62. 如何使用全文索引进行文本搜索?
- 63. 如何实现数据的导入和导出?
- 64. 如何使用事务处理多文档操作?
- 65. 如何监控集合的大小和索引大小?
- 66. 如何删除集合中的重复文档?
- 67. 如何设置集合的TTL索引自动删除过期数据?
- 68. 如何使用地理空间索引查询附近的位置?
- 69. 如何限制集合的最大文档数量?
- 69. 如何限制集合的最大文档数量?(续)
- 70. 如何在MongoDB中实现数据的版本控制?
一、本文面试题目录
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
为包含subject
和score
的对象数组)
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
集合的title
和content
字段创建全文索引
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. 如何实现数据的导入和导出?
使用mongoimport
和mongoexport
命令行工具,导入/导出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
集合中_id
为60d21b4667d0d8992e610c87
的产品价格,同时递增版本号
// 先查询当前文档及版本号
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) {
// 版本冲突,重新读取数据并更新
// 这里省略重试逻辑,实际应用中可循环重试
}
原理:读取文档时记录版本号,更新时对比版本号,若相同则更新并递增版本号;若不同,说明数据已被其他操作修改,更新失败,应用层需处理冲突(如重新读取数据、重试更新)。