MongoDB为字符串内容的文本查询提供了文本索引,文本索引支持那些字段的值为字符串或者是数组中的字符串元素。
在MongoDB 3.2版本后,MongoDB引入了版本3的文本索引,新版本的索引具有支持大小写不敏感,音调不敏感,对其他的界定符进行标记等优点,这些优点将在之后的文章中一一介绍。
创建文本索引
一个集合中只可以拥有一个文本索引。
在创建索引时,指定索引类型为text,即可创建一个文本索引,比如有这样的集合文档结构:
{
"_id" : ObjectId("5837080221f5ea1660d59910"),
"patient" : "patient_4396",
"symptom" : "fever old headache",
"comment" : "Inpatient observation Liquid input"
}
创建一个单键文本索引:
db.patient_text_index.createIndex({"symtom" : "text"})
创建一个复合文本索引:
db.patient_text_index.createIndex({"symtom" : "text", "comment" : "text"})
(这里只是举例说明,故创建索引演示均在同一个集合中)。
在一个文本索引中,索引字段的权重表示了关系到其他索引字段的文本搜索分数方面的重要性。
对集合中每一个索引字段来说,MongoDB将权重和匹配的数量相乘,然后将每个结果集相加,根据这个和的值,MongoDB可以计算这个文档的分数。$meta运算符可以根据文档分数对文档进行排序(降序)。
索引字段的默认权重是1,权重的范围可以是1~99999中间的整数,可以在创建索引时对索引字段的权重进行调整,比如:
db.patient_text_index.createIndex({"symtom" : "text", "comment" : "text"}, {"weight" : {"symtom" : 100, "comment" : 50}})
支持通配符的文本索引
在对多个字段建立文本索引时,你可以使用通配符$**,如果你使用了通配符,MongoDB会对集合中所有包含字符串数据的文档进行索引。
通过
db.patient_text_index.createIndex({"$**" : "text"})
即可创建一个支持通配符的索引,这样的话,patient、symtom以及comment都在索引范围之内。这样的索引对那些高度非结构化的数据是非常有用的,通配符文本索引是一个多字段的文本索引,所以它支持在创建索引是对键赋予权重值,这可以对结果集中的数据进行排序。它也可以是一个符合索引的一部分,比如,我们的文档结构是这样的
{
"_id" : ObjectId("5837080221f5ea1660d59910"),
"patient" : "patient_4396",
"symptom" : "fever old headache",
"department_id" : 1
"comment" : "Inpatient observation Liquid input"
}
我们可以创建一个这样的索引:
db.patient_text_index.createIndex({"department_id" : 1, "$**" : "text"})
###大小写不敏感
这是MongoDB 3.2版本中的新特性。
在版本3的文本索引中,不仅仅支持常用的字母,而且还支持土耳其语,特殊的unicode码等。而且还支持音调不敏感,根据官方文档(因为在我们的场景,一般不会使用,我也没有做测试), é, É, e和E,现在是不会对其作区分的。
在之前的版本中,文本索引仅支持没有音调的[A-z],对不同音调的相同的字母,会进行去重策略。
索引条目
文本索引是对索引条目中的索引字段进行标记和词干分析的,它为集合中的每一个文档的每一个索引字段的每一个词干存储了一个索引条目,索引将会使用简单的语言标识定义词干后缀。
支持语言停用词
MongoDB为几种语言提供了文本搜索,文本索引去掉了语言指定的停用词(比如英语中的the,an,and等)。
如果你将语言指定为none,那么文本索引将会使用简单的方式进行标记,没有停用词和词干。
稀疏属性
文本索引默认是稀疏属性,如果一个文档中没有文本索引中的字段,MongoDB不会为这个文档添加一个索引条目。
对于一个包括文本索引的复合索引来说,只有文本索引能确认索引是否指向一个文档,其他索引字段是无法确定索引是否指向一个文档。
约束
- 一个集合只能有一个文本索引。
- 在使用文本搜索的时候,是不能使用hint()方法的(hint()方法用于强制MongoDB使用某个索引)。
- sort操作无法获得一个文本索引的排序,即使是一个复合文本索引,比如,在文本索引中是无法使用排序的。
- 在一个包含文本索引的复合索引中,文本索引中不能包含其他类型的索引,比如不能包含多键索引或者是地理索引;如果符合索引包括文本索引之前的键,那么在使用$text进行搜索时,查询谓词必须包括先前键的等值匹配条件。
存储需求和性能消耗
- 文本索引可以变得很大,每当向集合中插入文档时,文本索引都会相应的在索引字段上创建一个指向这个文档的索引条目,而文本索引又包含了这些索引条目。
- 创建一个文本索引和创建一个规模较大的多键索引是非常类似的,这个过程可能会比创建一个简单的排序索引要消耗更多的时间。
- 当在一个已经存在的文档上创建索引时,确保对打开的文件的描述符具有足够高的限制。
- 文本索引会影响插入的效率,因为它会为每个插入的文档创建一个索引条目。
- 此外,MongoDB不会存储关于文档中字词的短语或者信息,总的来说,当数据都存在于内存中时,短语查询的效率将会高很多。
为文本索引指定一种语言
默认语言与索引数据需要确认断句的规则或者忽略停用词等有关。如果创建文本索引时没有指定语言,则默认语言是英文。
在创建索引时,可以使用default_language参数指定默认语言:
db.patient_text_index.createIndex({"comment" : "text"}, {"default_language" : "hans"})
看起来是不是有点熟悉的感觉,确实,这是我们中国的汉字,在3.2版本中,MongoDB商业版支持了简体中文和繁体中文,语言名称分别为simplified chinese或hans 和traditional chinese或hant。
为文本索引指定多种语言
根据上一小节所讲,在创建索引时添加的参数”default_language”无法动态识别集合中的文档的语言类型,但是MongoDB通过类似继承的手段,实现了文档语言解析的动态加载。
看一下规则:
- 在文档中指定的语言将会重写文本索引中指定的默认语言。
- 在内嵌文档中指定的语言将会仅仅会重写在内嵌文档中的语言。
我会举个例子形象化展示这一个过程,目前有一个如下的文档:
{
"_id" : ObjectId("5837080221f5ea1660d59910"),
"patient" : "patient_4396",
"language" : "english",
"original_symptom" : "fever old headache",
"comment" : [
{
"language" : "hans",
"syptom" : "发烧,感冒,偏头痛"
},
{
"language" : "",
"syptom" : "La fièvre le rhume la migraine"
}
]
}
然后我们创建一个文本索引:
db.patient_text_index.createIndex({"original_syptom" : "text", "comment.syptom" : "text"})
然后在查询时,我们就会根据文档或内嵌文档中的语言类型,来解析词干或者是其他语言特征。
也可以使用其他字段代替language字段,在创建索引时,使用language_override参数即可。
为文本索引指定一个名字
我们知道,在创建一个索引时,如果我们没有指定一个索引名称,则MongoDB会默认给我们分配一个索引名称,比如在创建索引时:
db.patient_text_index.createIndex({"original_syptom" : "text", "comment.syptom" : "text"})
索引名称将会是original_syptom_text_comment.syptom_text,这仅仅是拥有两个字段的复合索引。
同时MongoDB对索引长度也有要求,截止到目前为止,MongoDB中单个索引长度不得超过128个字节(包括分隔符)。
我们可以在创建索引时,使用name参数指定一个自定义的索引名称,比如在创建刚刚的索引时:
db.patient_text_index.createIndex({"original_syptom" : "text", "comment.syptom" : "text"},
{"name" : "PatientInfo"})
而且删除索引时,也方便了很多,这就像常用的的驼峰命名法一样,一目了然。
db.patient_text_index.dropIndex("PatientInfo")
通过权重掌控查询结果集
文本索引为每一个文档中索引的字段分配了一个分数,这个分数确认了查询语句与文档之间的相关性。在之前的内容已经介绍过关于权重的信息。
这里需要注意的是,为了避免索引的重排序,请谨慎选择权重。
在之前的例子中db.patient_text_index.createIndex({“symtom” : “text”, “comment” : “text”}, {“weight” : {“syptom” : 100, “comment” : 50}})
syptom的权重是comment的两倍,所以在查询过程中,syptom将会以两倍词匹配影响于comment。
对索引条目数量进行限制
原理其实很简单,当我们需要对一个字符串进行查询时,可以缩小所需遍历文档的数量,比如:
{
"_id" : ObjectId("5837080221f5ea1660d59910"),
"patient" : "patient_4396",
"symptom" : "fever old headache",
"comment" : "Inpatient observation Liquid input"
}
如果我们经常一个类似如下的查询:
db.patient_text_index.find({"patient" : "patient_443", $text : {"$search" : "fever"}})
我们可以通过创建一个这样的复合索引:
db.patient_text_index.createIndex({"patient" : 1, "comment" : "text"})
这样,我们在执行查询时,将只会遍历patient等于patient_443的文档,大大提高了遍历速度。