MongoDBNotes

第 09 章:全文与地理查询

zjc 于 2026-01-09 发布

这是《MongoDB 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 MongoDB 内置文本索引和地理空间索引,适合商品搜索、门店查询、附近服务和位置筛选。它们降低了引入搜索引擎的初始成本,但复杂相关性排序和大规模搜索仍可能需要 Elasticsearch 等专用系统。

9.1 文本索引

创建索引:

db.products.createIndex(
  { title: "text", description: "text", tags: "text" },
  { name: "idx_product_text" }
)

查询:

db.products.find({
  $text: { $search: "轻薄 笔记本" }
})

返回相关性分数:

db.products.find(
  { $text: { $search: "轻薄 笔记本" } },
  { score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })

适合:

  1. 中小规模全文检索;
  2. 后台管理搜索;
  3. 多字段关键词匹配;
  4. 不需要复杂分词和排序的系统。

9.2 文本索引限制

限制 说明
分词能力 内置分词对中文等语言不一定满足业务
一个集合 通常只能有一个文本索引
复杂排序 相关性以外的复杂排序受限
高级搜索 拼写纠错、同义词、权重调优不如搜索引擎
写入成本 文本索引较大

中文搜索常见方案:

  1. 应用层分词后保存关键字数组;
  2. 使用 ngram 分词;
  3. 使用 Elasticsearch;
  4. 使用云搜索服务。

9.3 关键词数组方案

应用分词后写入:

db.products.insertOne({
  title: "轻薄笔记本电脑",
  keywords: ["轻薄", "笔记本", "电脑", "notebook"]
})

索引:

db.products.createIndex({ keywords: 1 })

查询:

db.products.find({
  keywords: { $all: ["轻薄", "笔记本"] }
})

优点是查询简单、索引可控;缺点是分词、同义词和相关性行需要应用维护。

9.4 地理数据类型

GeoJSON 点:

{
  name: "上海门店",
  location: {
    type: "Point",
    coordinates: [121.4737, 31.2304]
  }
}

经纬度顺序是 [longitude, latitude],不要写反。

传统坐标对:

{
  name: "北京门店",
  loc: [116.4074, 39.9042]
}

新项目建议使用 GeoJSON。

9.5 2dsphere 索引

创建:

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

插入:

db.stores.insertOne({
  name: "南京西路店",
  city: "上海",
  location: {
    type: "Point",
    coordinates: [121.4512, 31.2294]
  }
})

附近查询:

db.stores.find({
  location: {
    $near: {
      $geometry: {
        type: "Point",
        coordinates: [121.4737, 31.2304]
      },
      $maxDistance: 3000,
      $minDistance: 0
    }
  }
})

单位是米,适合球面距离计算。

9.6 地理范围查询

查询圆形范围:

db.stores.find({
  location: {
    $geoWithin: {
      $centerSphere: [
        [121.4737, 31.2304],
        3 / 6378.1
      ]
    }
  }
})

查询矩形范围:

db.stores.find({
  location: {
    $geoWithin: {
      $box: [
        [121.3, 31.1],
        [121.6, 31.4]
      ]
    }
  }
})

$geoWithin 不返回距离,也不排序;需要排序时使用 $near 或聚合 $geoNear

9.7 $geoNear 聚合

db.stores.aggregate([
  {
    $geoNear: {
      near: {
        type: "Point",
        coordinates: [121.4737, 31.2304]
      },
      distanceField: "distance",
      maxDistance: 5000,
      query: { city: "上海" },
      spherical: true
    }
  },
  { $limit: 10 }
])

要求:

  1. 必须有地理索引;
  2. 通常是第一个阶段;
  3. 每个管道只能有一个 $geoNear
  4. 输出距离字段便于展示;
  5. 可以附加普通条件。

9.8 地理查询性能

优化建议:

  1. 必须创建 2dsphere 或 2d 索引;
  2. 限制返回条数;
  3. 附加城市或状态条件缩小范围;
  4. 避免全球范围查询;
  5. 高并发位置查询增加缓存;
  6. 大规模地理计算考虑专用地理服务。

9.9 混合搜索设计

门店搜索示例:

keyword search
  -> candidate IDs
  -> geo filter
  -> business filter
  -> ranking

文档:

{
  store_id: "s_100",
  name: "门店名称",
  keywords: ["coffee", "coffee shop"],
  city: "上海",
  score: 98,
  location: { type: "Point", coordinates: [121.47, 31.23] }
}

索引:

db.stores.createIndex({ city: 1, keywords: 1, score: -1 })
db.stores.createIndex({ location: "2dsphere" })

复杂相关性和地理位置混合排序可以由搜索服务完成,MongoDB 保存主数据。

9.10 常见问题

问题 排查
中文搜索效果差 分词能力不足
文本索引不生效 未创建或查询字段不匹配
地理结果异常 经纬度顺序错误
$near 很慢 无地理索引或范围过大
$geoNear 报错 不在第一阶段或无索引
搜索写入变慢 索引过大

本章小结

文本和地理索引让 MongoDB 能覆盖中小型搜索和位置查询场景。文本搜索重点是分词和相关性行,地理搜索重点是数据格式、索引和距离排序。业务复杂后,可以将 MongoDB 作为主存储,把搜索能力交给专用系统。

思考题

  1. 文本索引对中文搜索的主要限制是什么?
  2. GeoJSON 坐标顺序是什么?
  3. $near$geoWithin 的区别是什么?
  4. $geoNear 为什么通常放在第一阶段?
  5. 什么时候应该引入 Elasticsearch?