本文关于MongoDB的全文搜索的内容主要包括:
- 建立全文索引
- 通过$text关键字进行搜索
全文搜索概述
MongoDB通过建立全文索引,可以实现按照文本进行全文搜索。通常全文搜索的索引代价较高,所以在实现时,除非确有需要,不要默认对每个表格进行全文索引。
MongoDB对一部分语言可以根据语句进行分词,自动建立全文索引,支持的语言清单参见:
https://docs.mongodb.com/manual/reference/text-search-languages/#std-label-text-search-languages
遗憾的是,支持的语言中没有中文。因此,使用MongoDB进行中文索引时,需要我们手工进行分词。
建立全文索引
MongoDB可以对任意字段建立全文索引(text index),但需要注意:
1个collection中只能包含至多1个全文索引
在1个索引中可以包含多个需要索引的字段。例如,我们需要对users表的name和description字段进行索引,则可以通过以下方法建立索引。
db.users.createIndex({
name: "text",
description: "text"
})
索引将包含name和description中的全部内容。
当然,可能我们并不希望name和description是平等的,因为包含在name中的文字重要性要高于description,我们可以在创建索引时,指定索引的权重。
全文索引权重
在创建索引时,我们可以建立好各个字段的权重以使得搜索结果优先返回相关性更高的数据。
db.users.createIndex(
{
name: "text",
mobile: "text",
description: "text"
},
{
weights: {
name: 10,
mobile: 5
}
}
)
例如,通过上述命令,我们建立了users的全文索引。其中,name字段的权重为10,mobile字段权重为5,description字段没有设置权重,其权重为默认值:1。
在进行全文索引时,如果一个词在name中命中,那么其得分将会是在description中命中的10倍;在mobile中命中,将会是在description中命中的5倍。
权重的作用会体现在搜索时的textScore上,权重越高的字段命中了搜索词,该条文档的textScore得分也会越高。当然,如果一个字段中,搜索词出现的频率越高,其textScore得分也会越高。关于textScore的计算算法,可以参考:https://ananya281294.medium.com/mongo-maths-676469e55f78 文中详细介绍了textScore的计算过程。
也可以参考MongoDB计算分值的源代码;https://github.com/mongodb/mongo/blob/v4.2/src/mongo/db/fts/fts_spec.cpp#L185
复合全文索引
全文索引可以和其他索引一起组成复合全文索引,例如:
```
db.users.createIndex({
type: 1,
name: "text",
description: "text"
})
```
一个简单的正序或者倒序索引和全文索引一起,构成了复合全文索引。MongoDB官方文档给出了复合索引需要注意的几个问题:
- 复合全文索引只能包含简单的升序、降序索引,不能包含多键值索引和地理位置索引等;
- 如果在全文索引字段前面,包含前序的索引键值,那么在搜索时,必须在搜索条件中包含全部的索引键值搜索;
- 创建全文索引时,所有被全文索引的字段必须相邻,中间不能插入其他非全文索引键值。
其中的第二条,对于上面的索引,在搜索时,必须包含type条件,否则搜索时将报错;例如:
db.users.find({
type: 10,
$text: {$search: '米米'}
})
type这里作为了全文索引的一级索引,只有搜索时包含type作为条件,才能够使全文索引有效,否则全文索引就无法形成有效的搜索条件。反之,如果在建立索引时,type不是前序索引,则搜索时无需包含type值。
db.users.createIndex({
name: "text",
description: "text",
type: 1,
})
通配符全文索引
建立一个通配符全文索引的方法是:
db.collection.createIndex( { a: 1, "$**": "text" } )
通配符全文索引和内嵌文档的通配符索引是有区别的。通配符全文索引,对文档的所有字段建立全文索引。文档比较不结构化,那么可以采用这种方式建立全文索引。全文索引,也可以设置键值的权重。
通过$text关键字搜索
基本方法
MongoDB客户端可以使用$text关键字,进行全文搜索。
db.users.find({$text: {$search: '米米'}})
在搜索的时候不需要指定key值,MongoDB会在全部全文索引中进行检索,并返回结果。注意,默认返回的是自然顺序,而不是相关度最高的结果。
如果要搜索多个关键词,可以用空格分开:
db.users.find({$text: {$search: '米米 科技'}})
这时返回的结果是:包含“米米”或者包含“科技”的文档。
其他搜索条件
条件“与”
如果我们希望返回既有“米米”又有“科技”的文档,可以使用双引号,包含每个搜索词:
db.users.find({$text: {$search: '"米米" "科技"'}})
去除某个搜索词
如果我们希望返回有“米米”同时又没有“科技”的文档,可以使用负号(-)来去掉“科技”:
db.users.find({$text: {$search: '米米 -科技'}})
获得textScore并排序
搜索的时候默认返回的顺序是自然顺序;如果在搜索的时候,不仅想得到搜索的文档,还能够获得文档的textScore,并按照textScore排序,需要使用$meta操作符。
需要注意的是:MongoDB 4.2及以下版本,如果要按照textScore排序,则返回字段中也必须包含textScore。
db.users.find({$text: {$search: '米米 -科技'}}, {score: {$meta: 'textScore'}})
.sort({score: {$meta: 'textScore'}})
关于中文
MongoDB的全文搜索并没有对中文的支持,这使得默认情况下,MongoDB只能对语句进行搜索。例如:假设某个字段包含“阿基米米科技北京有限公司”。在搜索“米米”或者“科技”时,都无法得到这条结果。 如果将字段拆分成“阿基 米米 科技 北京 有限公司”,则可以搜索到这条数据。因此,开发人员需要手工进行分词,并对分词后的结果进行索引。
搜索限制
全文查询时有以下限制:
- 一个查询最多可以包含一个$text操作;符
- $text查询不能出现在$nor操作符中;
- $text查询不能出现在$elemMatch操作符中;
- 如果在$or操作符中使用$text查询,则每条查询条件都必须是建立了索引的;
- 进行$text查询时,不能用hint指定索引;
- 进行$text查询时不能指定$natural排序
- $text查询不能喝其他特殊查询一起进行,比如地理位置等;
- 视图不能使用$text查询
在aggregation中使用$text查询时,有以下限制:
- 如果在管道中使用包含$text的$match,则应该将它放在第一个stage中;
- $text操作符只能出现一次;
- $text操作符在$or或者$not中都不能出现;
- 默认情况下,不会按照textScore排序,如果要排序,可以在$sort stage中利用$meta表达式获取字段排序。