这是《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" } })
适合:
- 中小规模全文检索;
- 后台管理搜索;
- 多字段关键词匹配;
- 不需要复杂分词和排序的系统。
9.2 文本索引限制
| 限制 | 说明 |
|---|---|
| 分词能力 | 内置分词对中文等语言不一定满足业务 |
| 一个集合 | 通常只能有一个文本索引 |
| 复杂排序 | 相关性以外的复杂排序受限 |
| 高级搜索 | 拼写纠错、同义词、权重调优不如搜索引擎 |
| 写入成本 | 文本索引较大 |
中文搜索常见方案:
- 应用层分词后保存关键字数组;
- 使用 ngram 分词;
- 使用 Elasticsearch;
- 使用云搜索服务。
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 }
])
要求:
- 必须有地理索引;
- 通常是第一个阶段;
- 每个管道只能有一个
$geoNear; - 输出距离字段便于展示;
- 可以附加普通条件。
9.8 地理查询性能
优化建议:
- 必须创建 2dsphere 或 2d 索引;
- 限制返回条数;
- 附加城市或状态条件缩小范围;
- 避免全球范围查询;
- 高并发位置查询增加缓存;
- 大规模地理计算考虑专用地理服务。
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 作为主存储,把搜索能力交给专用系统。
思考题
- 文本索引对中文搜索的主要限制是什么?
- GeoJSON 坐标顺序是什么?
$near和$geoWithin的区别是什么?$geoNear为什么通常放在第一阶段?- 什么时候应该引入 Elasticsearch?