嵌入引用与聚合与索引与 TTL 地理

共 19 题
📑 题目列表 19 题
#
★★★

1. $lookup 的语义与跨集合 JOIN 模拟?

请解释 MongoDB 聚合管道中 $lookup 的语义,以及它是如何模拟关系型数据库中的跨集合 JOIN 的?

  • $lookup 只能在聚合管道中使用,且默认是"左外连接"(left outer join)语义
  • foreignField 与 localField 的匹配规则及索引利用
  • 与关系型数据库 JOIN 在性能、内存与灵活性上的差异

$lookup 是聚合管道中用于执行跨集合关联的阶段,它把"from"集合(一般称为"外部集合"或"被关联集合")中与指定字段匹配的文档以数组形式合并到当前文档中。默认行为是等价于关系型数据库的左外连接:当前集合(local collection)的每个文档都会保留,即使没有匹配也会得到空数组;而被关联集合中多余的文档不会被输出。执行时,MongoDB 会对 localField 的每个值在 from 集合上进行匹配,若 from 集合的 foreignField 存在索引(通常应建在 foreignField 和 localField 上),则可以在不扫描整个集合的情况下完成关联。相比 SQL 的 JOIN,$lookup 的结果是"数组"而非"扁平行",且默认只能做等值连接,不能做不等值连接,除非使用 pipeline 语法。

$lookup 的"左外连接"语义意味着它不会丢失主表文档,这一点对理解结果集很重要。性能上,$lookup 常常成为管道瓶颈,因为每个本地文档都要在外部集合做一次匹配;因此必须为 foreignField 建索引,否则会退化为全表扫描。了解其"数组化输出"与"仅等值匹配"这两点,是判断何时该用 $lookup、何时该反规范化的关键。

// 订单集合关联用户集合,返回每个订单及其用户信息
db.orders.aggregate([
  {
    $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
    }
  }
]);
#
★★★

2. MongoDB 聚合管道(Aggregation Pipeline)的阶段,$match、$group、$project、$lookup?

请解释 MongoDB 聚合管道的核心阶段 $match、$group、$project、$lookup 各自的作用与执行特性?

  • 各阶段的功能定位:过滤、分组、投影、关联
  • 阶段的执行顺序与可流式/阻塞特性
  • 与索引、内存的交互

聚合管道是 MongoDB 中一组有序的文档处理阶段,每个阶段接收上一阶段的全部或部分文档,处理后再交给下一阶段。$match 用于按条件过滤文档,等价于 WHERE,应尽量放在管道最前面以利用索引并减少后续阶段的数据量;$group 按某个字段分组并进行累加计算,等价于 SQL 的 GROUP BY,是典型的阻塞型阶段(必须先收集完所有分组数据才能输出);$project 用于选择、重命名、添加或计算字段,等价于 SELECT,可提前裁剪字段减少传输量;$lookup 用于跨集合关联。优化器会自动重排部分阶段(如把 $match 提前),但开发者主动把过滤条件放在最前仍是更优做法。

聚合管道优异之处在于"阶段化"的声明式表达,把复杂的 SQL 变换拆成多个可读、可测的小步骤。$match 前置是唯一公认的黄金法则,因为它能利用索引且大幅减少数据量;$group 和 $sort 这类阻塞阶段会占用内存,超过 100MB 需启用 allowDiskUse 落盘。理解每个阶段是否阻塞、是否可用索引,是评估管道性能的基础。

db.orders.aggregate([
  { $match: { status: "paid" } },          // 过滤,用索引
  { $lookup: { from: "users", localField: "uid", foreignField: "_id", as: "user" } },
  { $project: { total: 1, userName: { $first: "$user.name" } } },
  { $group: { _id: "$userName", sum: { $sum: "$total" } } }
]);
#
★★★

3. MongoDB 索引类型,单字段、复合、多键、地理、文本、TTL?

请介绍 MongoDB 的各类索引类型:单字段、复合、多键、地理、文本、TTL 索引各自的特点与适用场景?

  • 每组索引的底层结构与适用查询
  • 数组字段与多键索引的展开
  • 特殊索引(地理、文本、TTL)的约束与使用方式

单字段索引是最基础的索引,对单个字段建立升序或降序 B-tree,加速等值、范围与排序查询;复合索引对多个字段建索引,支持多字段组合查询与排序,需遵循最左前缀原则与 ESR 规则;多键索引用于数组字段,MongoDB 会为数组中的每个元素建立索引项(展开存储),索引自动变为多键;地理索引支持位置查询,2dsphere 用于球面坐标(GeoJSON),2d 用于平面坐标;文本索引用于全文搜索,支持 $text 查询;TTL 索引在指定字段达到过期时间后由后台线程自动删除文档。这些索引在底层多用 B-tree 变体,但存储与回收策略不同,需按查询模式选择。

索引类型的核心是"为查询服务"——单字段与复合索引解决精确与范围查询,多键处理数组,地理与文本解决非结构化查询,TTL 解决自动过期。实际设计中要避免无索引集合因全表扫描而拖垮性能,也要避免为每个字段建索引导致的写入与存储膨胀。理解各类型索引的适用场景,才能为不同查询建立正确的索引。

#
★★★

4. TTL(Time To Live)索引的过期清理机制?

请解释 TTL 索引的过期清理机制:它如何工作、何时执行、有哪些限制?

  • TTL 索引是单字段索引,字段必须为日期或日期数组
  • 后台"monitor"线程每 60 秒扫描一次并删除过期文档
  • 删除不及时、无法精确到秒、对单字段/非复合的限制

TTL 索引是 MongoDB 提供的一种特殊单字段索引,用于自动过期删除文档。它只允许建立在日期类型(或日期数组)字段上,且不能是复合索引、不能与多键索引组合。MongoDB 启动一个后台 monitor 线程,大约每 60 秒扫描一次 TTL 索引,把当前时间超过"字段值 + expireAfterSeconds"的文档删除。删除操作采用批量删除,且删除是异步的,不保证精确到秒;如果文档被删除时正在写入,删除可能延迟。TTL 删除会产生写入操作,在副本集上会同步到从节点,在分片集群上也有一定限制。

TTL 的设计是以"周期性扫描换取低开销",因此删除有延迟、不精确,适合会话过期、日志清理这类对延迟不敏感的场景,不适合对时效要求极高的业务。由于 monitor 线程每 60 秒运行一次,即使设置了很小 expireAfterSeconds,实际删除也会延迟最多约 60 秒。若要精确删除或用 TTL 清理复合场景,需改用 scheduler 或定时任务。理解"后台扫描"这一点,是判断 TTL 是否够用的关键。

// 创建 TTL 索引:expireAt 字段到达后 0 秒自动删除
db.sessions.createIndex({ "expireAt": 1 }, { expireAfterSeconds: 0 });
#
★★★

5. 嵌入 vs 引用的量化决策,以访问模式(读多写少)、文档增长与一致性要求为依据的建模案例

请以访问模式(读多写少)、文档增长与一致性要求为依据,说明嵌入与引用的量化决策过程与建模案例?

  • 嵌入的适用条件:读多写少、数据大小可控、一起读取
  • 引用的适用条件:文档无限增长、需独立更新、一致性强
  • 16MB 上限与数组反模式的约束

嵌入与引用的选择本质上是"数据访问模式"的映射。嵌入(embedding)适合:数据经常与父文档一起读取、数据量小且不会无限增长、父文档几乎不被单独修改(读多写少)、数据物主关系强(如订单中的商品明细)。引用(referencing)适合:数据会被多个文档共享、数据会无限增长(如评论、消息)、需要独立并发更新以保证一致性、或数据量超过 16MB 限制。量化判断时,应计算"读多写少"比例:若父文档每次读取都伴随子数据,嵌入更优;若子数据被独立频繁更新且嵌入会引入并发写冲突,则引用更优;若子数据可能无限增长,应避免嵌入并拆分为独立集合。

MongoDB 建模的核心是"面向查询建模",而非"面向关系建模"。嵌入换来的是读取的高效(一次读取出全部相关数据、无 JOIN),代价是写入的放大与文档增长;引用换来的是灵活与一致,代价是读取时的多次查询或 $lookup。判断准则可用"读多写少、共读多、无界长"等口诀归纳:子数据是否与父文档强绑定一起读、是否频繁独立更新、是否无界增长。这三个维度的答案直接决定嵌入还是引用。

#
★★★

6. $geoWithin、$geoNear、$nearSphere 分别如何使用 2dsphere 索引?与普通索引在查询计划上的差异是什么?

请解释 $geoWithin、$geoNear、$nearSphere 三种地理查询如何配合 2dsphere 索引,并与普通索引在查询计划上的差异?

  • 三种查询的适用场景:多边形包含、最近邻、球面距离
  • 2dsphere 索引的 Geohash/网格结构
  • 普通 B-tree 索引无法高效处理"最近邻"查询

2dsphere 索引是专为地理数据设计的索引,它把 GeoJSON 点、线、面转换为地理网格(基于 Geohash 类似的空间填充曲线)以便高效空间查询。$geoWithin 用于查询"位于某个几何形状内"的文档,配合 2dsphere 做包含性判断;$geoNear 用于按距离排序返回最近的点,必须配合 2dsphere 索引且只能在聚合管道中使用;$nearSphere 是 $geoNear 的查询运算符版本,也返回按距离排序的结果。与普通 B-tree 索引不同,普通索引只能做等值/范围比较,无法表达"距离排序"或"形状包含"这类二维空间语义,因此地理查询必须走专门的地理索引;空间查询在查询计划上会优先选择 2dsphere 索引做候选集收缩,再在内存中做精确距离计算。

地理查询的关键在于"空间索引的必要性"。普通 B-tree 对数轴排序友好,但空间数据是二维的,无法用单维排序高效实现"最近邻"或"多边形包含",因此 MongoDB 用空间网格(Geohash)把二维坐标映射到一维再进行索引。$geoNear 与 $nearSphere 返回按距离排序的结果,本质是最近邻搜索;$geoWithin 是包含判断。三者都要求 2dsphere 索引,否则报错或退化为全量扫描。理解"空间网格"与"距离排序"的特性,是回答地理查询的关键。

#
★★★

7. 为什么评论数组这类无界数组是文档建模反模式(16MB 上限、索引膨胀、原子更新受限)?应如何拆分为独立集合?

请解释为什么评论这类无界数组是文档建模反模式,涉及 16MB 上限、索引膨胀与原子更新受限,并说明如何拆分为独立集合?

  • 无界数组导致文档无限增长,最终触及 16MB 上限
  • 数组多键索引随元素增多而膨胀
  • 增加/删除元素需重写整个文档,原子更新受限

把评论、消息这类会无限增长的数组直接嵌入文档是典型反模式。第一,文档有 16MB 硬上限,无界数组最终会撑爆文档;第二,数组字段的多键索引会为每个元素建立索引项,数组无限增长导致索引膨胀,写入与存储成本飙升;第三,MongoDB 对数组更新采用"重写整个文档"的方式,当一个数组元素需要独立且原子地更新(如删除某条评论、修改某条评论)时,无法只更新该元素而不重写整个文档,导致并发写冲突与性能下降。正确做法是把评论拆分为独立集合(如 comments 集合),通过引用(如 postId 字段)关联,并为其建立索引;这样评论可独立增删、独立更新、并发写入,也不受父文档 16MB 限制。

反模式的核心信号是"无界增长"。判断嵌入数组是否合适,关键看数组是否会被无限追加、是否需独立更新元素。评论、日志、消息属于"无限增长 + 需要独立更新"的场景,应拆为独立集合;而订单明细、标签这类"有界、随父文档一起读"的数组适合嵌入。拆分后引入的 join 成本可用 $lookup 或反规范化冗余字段来平衡。理解"16MB 上限 + 索引膨胀 + 原子更新受限"三个维度的连锁影响,是识别反模式的关键。

#
★★

8. 地理空间索引,2dsphere、2d、HASHED?

请对比 MongoDB 的 2dsphere、2d、HASHED 三种索引类型?

  • 2dsphere 用于球面 GeoJSON 坐标,支持 $geoWithin/$geoNear
  • 2d 用于平面坐标,兼容旧版
  • HASHED 用于分片键的散列分布,不支持地理查询

2dsphere、2d、HASHED 是三种容易被混淆的索引。2dsphere 是推荐的地理索引,支持 GeoJSON 的点、线、面,用于球面地球模型,支持 $geoWithin、$geoNear、$nearSphere 等地理查询;2d 是旧版平面坐标索引,使用经纬度数值,适用于平面距离计算,功能较 2dsphere 少,已不推荐使用;HASHED 索引与地理无关,它把字段值做哈希后建索引,主要用于分片键,使数据在分片间均匀分布,但无法做范围查询与地理查询,且对普通等值查询也增加一次哈希计算。三者的用途完全不同:前两个服务于地理空间查询,后者服务于分片均匀分布。

三者名称相似但用途截然不同。2dsphere 满足现代地理业务(GPS、地图、GeoJSON),2d 是历史遗留的平面版本,HASHED 是分片工具而非地理工具。判断时看"是否要地理查询"——要则用 2dsphere;看"是否要均匀分布分片数据"——要则用 HASHED。混淆三者会选错索引导致功能不可用或性能下降。

#
★★

9. 索引选择性(Selectivity)与 ESR 规则?

请解释索引选择性(Selectivity)与 ESR 规则在复合索引设计中的作用?

  • 选择性:索引能区分大多/少数文档,区分度越高选择性越好
  • ESR:E(Equality) 等值字段在前、S(Sort) 排序字段、R(Range) 范围字段最后
  • 复合索引字段顺序设计原则

索引选择性衡量索引区分文档的能力:高选择性意味着索引能快速缩小到很少的文档,低选择性意味着索引项对应大量文档(如布尔字段),查询仍要扫描很多数据。理想情况下,应把选择性高的字段放在复合索引前面。ESR 规则是设计复合索引字段顺序的黄金法则:E(Equality,等值条件)字段放在最前,因为它能精确匹配;S(Sort,排序字段)居中,以便利用索引天然有序避免额外排序;R(Range,范围条件)字段放在最后,因为范围会打断索引的有序性。ESR 规则帮助在单条复合索引中同时满足等值过滤、排序和范围过滤,避免"排序无法用索引"而走内存排序。

选择性与 ESR 是复合索引设计的两个核心。选择性回答"该不该给这个字段建索引",ESR 回答"多个字段怎么排顺序"。ESR 的本质是让索引既做过滤又做排序,减少内存排序与回表。先 E 后 S 再 R,能使查询在索引内部完成过滤与排序。实际设计中,若等值条件多,选择性差的字段可以适当靠后权衡;但 ESR 是通用起点。

#
★★

10. MongoDB 的嵌入(Embedding)vs 引用(Referencing),何时嵌入、何时引用?

请说明 MongoDB 中嵌入与引用各自的适用时机?

  • 嵌入适合在一起读取、数据有界、关系紧密
  • 引用适合共享数据、无限增长、独立更新
  • 读性能与一致性权衡

嵌入是把相关子数据直接放进父文档中,读取时一次取出全部相关数据,无需 join,适合"数据总是与父文档一起读取、数据量有界、更新不频繁、物主关系强"的场景,如订单的商品明细、个人资料中的地址。引用则是把子数据存入独立集合,用 ID 关联,适合"数据被多个文档共享、需要独立并发更新、数据无界增长、一致性强"的场景,如用户、商品、评论。嵌入读快但写放大、文档受限;引用灵活一致但读需多次查询或 $lookup。选择时以访问模式为准:先读还是先写、是否总是一起读、是否无界增长。

嵌入与引用是 MongoDB 建模的核心取舍。核心判断是"访问模式":一起读、有界、不常改 → 嵌入;独立改、共享、无界长 → 引用。没有绝对优劣,只有匹配度。嵌入的代价是文档更新需重写、16MB 上限、无界数组反模式;引用的代价是 N+1 查询或 $lookup 开销。工程上常以"反规范化冗余字段"作为折中。

#
★★

11. 文档大小限制(16MB)与 GridFS 的应用?

请解释 MongoDB 16MB 文档大小限制,以及 GridFS 的应用场景与原理?

  • 16MB 是单文档硬上限
  • GridFS 把大文件拆成多个 chunk 存储
  • GridFS 的 files 与 chunks 集合结构

MongoDB 对单个文档的 BSON 大小有 16MB 硬上限(BSON 最大 16MB),这是为了限制单文档过大对内存、重建与网络传输的影响。GridFS 是 MongoDB 用于存储超过 16MB 大文件的规范,它把文件拆成多个 255KB 的 chunk,分别存入 chunks 集合,并在 files 集合中记录文件元数据(文件名、大小、chunk 大小、mime 等)。读取时通过 file_id 和 chunk 序号按需读取,支持流式读取与部分读取。GridFS 适合大文件、大对象存储,但不应替代专用的对象存储(如 S3),因为它的读写性能与并发能力有限。

16MB 上限是 BSON 格式的硬性约束,理解它才能避免设计出超限文档。GridFS 通过"分块 + 元数据"解决超大文件存储,本质是把大文件拆成多个小文档。GridFS 的 chunks 集合按 chunk 序号建立索引,files 集合按 file_id 建立索引。应用场景如音频、视频、图片、大型 JSON 文档。但要注意 GridFS 不是高性能对象存储,超大文件应优先考虑 S3 等专用存储。

#
★★

12. MongoDB 建模与一致性?

请说明 MongoDB 中数据建模与一致性之间的关系?

  • 嵌入/引用对一致性的影响
  • 单文档原子性 vs 跨文档事务
  • 反规范化与冗余字段的一致性维护

MongoDB 的建模方式直接影响一致性保证。单文档内的更新是原子的,因此嵌入相关数据可以在一次原子操作中完成更新,保证强一致;但跨文档(引用)的更新需要多文档事务或多步操作,若中途失败会产生中间状态。此外,建模常采用反规范化(冗余字段)来优化读取,但冗余字段的一致性需要应用层维护,可能产生数据不一致。因此,建模时要在"读取性能"与"一致性维护成本"之间权衡:需要强一致且原子更新时,倾向把相关数据嵌入同一文档;容忍最终一致或需独立更新时,用引用并辅以事务或补偿机制。

MongoDB 的原子性边界是"单文档"。建模决策事实上决定了你的原子性边界:嵌入越大,原子更新的范围越大,但写放大与并发冲突增加;引用越多,跨文档更新越频繁,越需要事务支持。理解"单文档原子性"是 MongoDB 一致性的基石,据此设计文档边界,才能既保证一致性又控制性能。

#
★★

13. MongoDB 的索引策略,复合索引、TTL 与地理索引的配合?

请说明如何设计复合索引、TTL 索引与地理索引的配合策略?

  • 复合索引遵循 ESR 规则支撑业务查询
  • TTL 索引做自动过期清理
  • 地理索引服务位置查询

MongoDB 索引策略的核心是"按查询模式建索引,避免为每个字段盲目建索引"。复合索引用于支撑高频业务查询,遵循 ESR 规则设计字段顺序,使等值过滤、排序、范围查询都能利用索引;TTL 索引用于会话、日志等自动过期数据,设置在日期字段上,由后台周期清理;地理索引(2dsphere)用于位置相关查询,建在 GeoJSON 字段上。三类索引配合时要注意:每个索引都增加写入开销与存储占用,因此索引总数量应受控;TTL 索引不能是复合索引;地理索引与普通复合索引可共存但分别服务不同查询。策略上应抓住"最热查询"建立最匹配的索引,并定期用 explain 校验。

索引策略的本质是"平衡查询加速与写入成本"。复合索引解决主体业务查询,TTL 解决数据生命周期,地理索引解决空间查询,三者各司其职。设计要点是:索引为查询而生,先分析查询再建索引;用 ESR 优化复合索引;TTL 与地理索引是特殊用途,不滥用。过度建索引会拖慢写入与膨胀存储,因此要定期清理低效索引。

#
★★

14. 多键索引(数组字段)的索引项展开与性能边界

请解释多键索引对数组字段的索引项展开机制及其性能边界?

  • 多键索引为数组每个元素建索引项
  • 索引膨胀与写入放大
  • 数组元素数量与查询性能的边界

当对数组字段建立索引时,MongoDB 自动创建多键索引:每个数组元素都会展开成独立的索引项,即数组中有 N 个元素,索引就多出 N 个条目。这使得对数组字段的等值查询(匹配任意元素)可以用索引。但代价是:数组元素越多,索引越膨胀,写入时重建索引项越多(写入放大),存储占用越大;而且一个文档对应多个索引项会降低索引选择性。性能边界在于:数组元素数量过大时,索引膨胀与写入成本急剧上升,且最大数组元素数受 BSON 大小限制。因此,多键索引适合元素数量可控的数组(如标签、分类),不适合无界大数组(如评论)。

多键索引的"展开"特性是理解数组查询性能的钥匙。查询"匹配数组中任意一个元素"能命中索引,正是因为每个元素都被单独建索引项。但展开带来索引膨胀与写放大,数组中元素越多,每个写入的索引维护成本越高。设计上应控制数组规模,避免把无界数组嵌在大文档中并建多键索引。理解"展开 + 膨胀"是把握多键索引性能边界的关键。

#
★★

15. 物联网时间序列场景的桶化(bucket)建模如何设计桶键与边界?相比每事件一个文档在索引与写入放大上有何收益?

请说明物联网时间序列场景的桶化建模如何设计桶键与边界,以及相比每事件一个文档在索引与写入放大上的收益?

  • 桶化:按时间窗口把多个事件合并进一个文档
  • 桶键与边界设计(时间窗口、桶大小容量)
  • 减少文档数、索引项与写入放大

桶化(bucket)建模是时间序列场景的经典优化,把多个测量事件按时间窗口聚合进一个文档(桶)。桶键通常用桶起始时间(如按小时/分钟取整);桶边界用时间区间 + 固定容量(如每桶最多 N 条或最多 M 字节)双重约束,桶键因此常为"时间戳 + 计数"的组合。相比每事件一个文档,桶化的收益显著:一是文档数量大幅减少(如 1 小时 3600 条变为 1 个文档),索引项随之减少,索引体积与磁盘占用下降;二是写入放大降低,原来每个事件都插入一条文档并更新索引,现在只需对桶做一次追加更新(或少量更新),写入次数与索引维护成本大幅下降;三是读取时按桶批量读取,适合趋势分析。缺陷是更新单条事件需重写整个桶,需权衡粒度。

桶化的本质是"用文档粒度换取写入与索引效率"。时间序列数据量大、写入密集、常按时间范围读取,桶化把高频小写合并为低频大写,显著减少索引项与文档数。设计桶键关键是"时间窗口 + 容量"双边界,避免桶无界增长。桶化是"时间序列集合性能优化"的经典手段,理解了写入放大与索引膨胀,才能理解桶化的收益。

#
★★

16. TTL 索引的删除为什么不精确(后台周期扫描)?对复合索引、数组字段与分片集群有哪些限制?

请解释 TTL 索引删除为什么不精确,以及它对复合索引、数组字段与分片集群的限制?

  • 后台 monitor 线程约 60 秒扫描一次,删除有延迟
  • 不能建在复合索引上
  • 对数组字段与分片集群的特殊限制

TTL 索引的删除由后台 monitor 线程执行,该线程约每 60 秒启动一次扫描所有 TTL 索引,删除过期文档;因此删除不精确,最多延迟约 60 秒,且删除是批量异步的,不保证即时。TTL 索引的限制包括:只能建在单字段上——不能是复合索引,不能与多键索引组合;字段必须是日期类型或日期数组;不能建在 _id 字段上。在分片集群中,TTL 删除由各分片分别执行,如果分片上的文档由分片键决定,删除只发生在对应分片,但若 TTL 字段不是分片键,删除需在各分片独立扫描,仍不保证全局精确。若业务需要精确删除,应改用定时任务或 scheduler。

TTL 的"周期扫描"设计决定了其不精确性,这是用它做清理必须接受的代价。理解限制能避免误用:把 TTL 建在复合索引或数组字段上会报错或失效;在分片集群中其删除范围受分片键影响。判断 TTL 是否适用,看是否容忍删除延迟与是否满足单字段日期限制。若需精确或灵活过期,采用应用层定时任务。

#

17. MongoDB 聚合 vs MapReduce,性能与复杂度对比?

请对比 MongoDB 聚合管道与 MapReduce 在性能与复杂度上的差异?

  • 聚合管道用 C++ 实现,性能高、方式声明式
  • MapReduce 用 JavaScript 实现,灵活但慢
  • 聚合管道是 MapReduce 的推荐替代

MongoDB 聚合管道 与 MapReduce 都能做复杂数据处理,但性能和复杂度差异显著。聚合管道由 C++ 原生实现,编译为执行计划,能利用索引、内存流式处理,性能高、延迟低,且是声明式($match/$group/$project 等),易读易维护。MapReduce 用 JavaScript(map/reduce 函数)实现,灵活性强,可写任意逻辑,但执行在 JS 引擎中,性能差、内存开销大、无法利用索引,且已基本被聚合管道取代。MongoDB 官方推荐用聚合管道替代 MapReduce,因为聚合管道在绝大多数场景下性能更好、更易优化。仅当有无法用聚合表达的复杂逻辑时才考虑 MapReduce,且新版已不倾向使用。

聚合管道 vs MapReduce 的对比本质是"原生 vs 解释执行"的性能差异。聚合管道享受 C++ 执行与索引优化,MapReduce 受限于 JS 引擎。复杂度上,聚合管道声明式更清晰,MapReduce 可编程但难维护。工程上应优先聚合管道,仅在极特殊逻辑时用 MapReduce。理解两者的性能根因,是回答的关键。

#

18. MongoDB 的 $text 全文索引与中文分词?

请说明 MongoDB 的 $text 全文索引及其中文分词的问题?

  • $text 索引用于全文搜索,支持 $text 查询
  • 默认分词基于空格,对中文支持差
  • 中文分词需特定方案或外部搜索引擎

MongoDB 的 $text 索引用于全文搜索,支持对字符串字段做词项匹配,配合 $text 运算符进行查询,可做词干、停用词、词频排序等。但 $text 索引的分词(tokenization)默认基于空格和标点,对英文友好,对中文几乎没有天然分词能力——中文没有空格分隔词,MongoDB 默认分词会把整句切分得不好,导致搜索精度差。因此中文场景通常需要:要么使用第三方分词方案(结合外部搜索引擎如 Elasticsearch),要么在写入时自行分词并存储分词字段再建索引。$text 索引还有限制:一个集合只能有一个文本索引,不能与多键等组合(数组字段除外),且不支持中文语义分析。

$text 索引适合英文等空格分词语言的全文搜索,中文因无空格分词天然支持差。理解这一点才能在中文场景选择正确方案(外部搜索引擎或自建分词字段)。$text 索引的"单集合单文本索引"限制也需注意。中文分词是全文搜索的关键难点,掌握其局限是工程决策的基础。

#

19. MongoDB 的通配符索引与部分索引(partial index)的适用场景

请说明 MongoDB 通配符索引与部分索引(partial index)的适用场景?

  • 通配符索引:对未知字段名或动态字段建索引
  • 部分索引:只对满足条件的文档建索引,减少体积
  • partialFilterExpression 与部分索引的查询约束

通配符索引(Wildcard Index)用于对字段名不确定或动态变化的文档建索引,如文档中有一组动态键,可用通配符语法统一索引,避免为每个动态字段单独建索引;它适合字段模式不固定的文档。部分索引(Partial Index)通过 partialFilterExpression 只对满足条件的文档建立索引,如只对 status: "active" 的文档建索引,可显著减少索引体积与存储、写入开销;但查询必须包含与该过滤条件匹配的谓词,否则优化器可能无法使用该索引。通配符索引解决"字段名未知",部分索引解决"只需索引部分文档",两者适用场景不同。

通配符索引与部分索引是应对"不固定"与"部分"两类场景的索引。通配符自适应动态字段,部分索引缩小索引体积。使用部分索引时注意查询谓词需覆盖过滤条件,否则索引不生效。理解两者适用边界,才能在真实建模中选对索引类型。