MongoDB是领先的NoSQL数据库之一,以其快速的性能,灵活的模式,可伸缩性和强大的索引功能而闻名。 这种快速性能的核心是MongoDB索引,它通过避免全集合扫描并因此限制了MongoDB搜索的文档数量来支持查询的有效执行。
从2.4版开始,MongoDB首先提供了一项实验性功能,该功能支持使用文本索引进行全文 本搜索 。 现在,此功能已成为产品不可或缺的一部分(不再是实验功能)。 在本文中,我们将直接从基础上探索MongoDB的全文本搜索功能。
如果您不熟悉MongoDB,建议您阅读Envato Tuts +上的以下文章,这些文章将帮助您了解MongoDB的基本概念:
基础
在开始任何细节之前,让我们先看一下背景。 全文搜索是指根据用户指定的搜索条件搜索全文数据库的技术。 这类似于我们通过输入某些字符串关键字/短语并返回按其排名排序的相关结果来搜索Google(或实际上任何其他搜索应用程序)上的任何内容的方法。
在更多情况下,我们将看到全文搜索:
- 考虑在Wiki上搜索您喜欢的主题。 当您在Wiki上输入搜索文本时,搜索引擎将显示与您搜索的关键字/短语相关的所有文章的结果(即使这些关键字在文章的内部使用了)。 这些搜索结果根据它们的匹配分数按相关性排序。
- 作为另一个示例,考虑一个社交网站,用户可以在其中进行搜索以查找包含关键字
cats
所有帖子。 在他们之中; 或者更复杂的是,所有带有注释的帖子都包含cats
一词。
在继续之前,您应该了解一些与全文搜索有关的通用术语。 这些术语适用于任何全文本搜索实现(不适用于MongoDB)。
停用词
停用词是不相关的词,应从文本中过滤掉。 例如:a,an,the,is,at,which等。
抽干
词干是将单词减少到词干的过程。 例如:站立,站立,站立等词语具有共同的基本立场。
计分
相对排名来衡量哪个搜索结果最相关。
MongoDB中全文本搜索的替代方法
在MongoDB提出文本索引的概念之前,我们要么对数据建模以支持关键字搜索,要么使用正则表达式来实现这种搜索功能。 但是,使用以下任何一种方法都有其自身的局限性:
- 首先,这些方法都不支持词干,停用词,排名等功能。
- 使用关键字搜索将需要创建多关键字索引,这比全文索引还不够。
- 从性能的角度来看,使用正则表达式效率不高,因为这些表达式无法有效利用索引。
- 除此之外,这些技术都不能用于执行任何词组搜索(例如搜索“ 2015年发行的电影”)或加权搜索。
除了这些方法之外,对于更高级和更复杂的以搜索为中心的应用程序,还有其他解决方案,例如Elastic Search或SOLR 。 但是使用这些解决方案中的任何一种都会增加应用程序的体系结构复杂性,因为MongoDB现在必须与其他外部数据库进行对话。
请注意,MongoDB的全文搜索不建议完全替代搜索引擎数据库,例如Elastic,SOLR等。但是,它可以有效地用于当今使用MongoDB构建的大多数应用程序。
介绍MongoDB文本搜索
使用MongoDB全文搜索,您可以在文档中任何值为字符串或字符串数组的字段上定义文本索引。 当我们在字段上创建文本索引时,MongoDB将标记并阻止索引字段的文本内容,并相应地设置索引。
为了进一步了解事物,让我们现在深入探讨一些实际的事物。 我希望您通过尝试mongo shell中的示例来跟随本教程。 我们将首先创建一些示例数据,我们将在整篇文章中使用这些数据,然后继续讨论关键概念。
就本文而言,请考虑一个收集messages
,该messages
存储以下结构的文档:
{
"subject":"Joe owns a dog",
"content":"Dogs are man's best friend",
"likes": 60,
"year":2015,
"language":"english"
}
让我们使用insert
命令插入一些样本文档来创建测试数据:
db.messages.insert({"subject":"Joe owns a dog", "content":"Dogs are man's best friend", "likes": 60, "year":2015, "language":"english"})
db.messages.insert({"subject":"Dogs eat cats and dog eats pigeons too", "content":"Cats are not evil", "likes": 30, "year":2015, "language":"english"})
db.messages.insert({"subject":"Cats eat rats", "content":"Rats do not cook food", "likes": 55, "year":2014, "language":"english"})
db.messages.insert({"subject":"Rats eat Joe", "content":"Joe ate a rat", "likes": 75, "year":2014, "language":"english"})
创建文本索引
文本索引的创建与我们创建常规索引的方式非常相似,不同之处在于它指定了text
关键字而不是指定了升序/降序。
索引一个字段
使用以下查询在文档的subject
字段上创建文本索引:
db.messages.createIndex({"subject":"text"})
为了在subject
字段上测试这个新创建的文本索引,我们将使用$text
运算符搜索文档。 我们将寻找所有在subject
字段中具有关键字dogs
的文档。
由于我们正在运行文本搜索,因此我们也有兴趣获取一些有关结果文档相关性的统计信息。 为此,我们将使用{ $meta: "textScore" }
表达式,该表达式提供有关$text
运算符处理的信息。 我们还将使用sort
命令按文档的textScore
进行sort
。 较高的textScore
表示匹配程度更高。
db.messages.find({$text: {$search: "dogs"}}, {score: {$meta: "toextScore"}}).sort({score:{$meta:"textScore"}})
上面的查询返回以下文档,这些文档的subject
字段中包含关键字dogs
。
{ "_id" : ObjectId("55f4a5d9b592880356441e94"), "subject" : "Dogs eat cats and dog eats pigeons too", "content" : "Cats are not evil", "likes" : 30, "year" : 2015, "language" : "english", "score" : 1 }
{ "_id" : ObjectId("55f4a5d9b592880356441e93"), "subject" : "Joe owns a dog", "content" : "Dogs are man's best friend", "likes" : 60, "year" : 2015, "language" : "english", "score" : 0.6666666666666666 }
如您所见,第一个文档的得分为1(因为关键字dog
在其主题中出现两次),而第二个文档的得分为0.66。 该查询还按得分的降序对返回的文档进行了排序。
您可能会想到的一个问题是,如果我们要搜索关键字dogs
,为什么搜索引擎会考虑关键字dog
(不带“ s”)? 还记得我们关于词干的讨论,在该词中,任何搜索关键字都会减少到其基数? 这就是为什么将关键字dogs
简化为dog
。
索引多个字段(复合索引)
通常,您将在文档的多个字段上使用文本搜索。 在我们的示例中,我们将在subject
和content
字段上启用复合文本索引。 继续在mongo shell中执行以下命令:
db.messages.createIndex({"subject":"text","content":"text"})
这个工作了吗? 没有!! 创建第二个文本索引将给您一条错误消息,指出全文搜索索引已存在。 为什么会这样呢? 答案是,文本索引每个集合只能有一个文本索引。 因此,如果您要创建另一个文本索引,则必须删除现有的文本索引并重新创建一个新的文本索引。
db.messages.dropIndex("subject_text")
db.messages.createIndex({"subject":"text","content":"text"})
执行完上述索引创建查询后,尝试搜索所有包含关键字cat
文档。
db.messages.find({$text: {$search: "cat"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
上面的查询将输出以下文档:
{ "_id" : ObjectId("55f4af22b592880356441ea4"), "subject" : "Dogs eat cats and dog eats pigeons too", "content" : "Cats are not evil", "likes" : 30, "year" : 2015, "language" : "english", "score" : 1.3333333333333335 }
{ "_id" : ObjectId("55f4af22b592880356441ea5"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }
您可以看到,在subject
和content
字段中都包含关键字cat
的第一个文档的分数更高。
索引整个文档(通配符索引)
在最后一个示例中,我们在subject
和content
字段上放置了组合索引。 但是在某些情况下,您希望文档中的任何文本内容都可搜索。
例如,考虑将电子邮件存储在MongoDB文档中。 对于电子邮件,所有字段(包括发件人,收件人,主题和正文)都必须可搜索。 在这种情况下,您可以使用$**
通配符说明符来索引文档的所有字符串字段。
查询将如下所示(确保在创建新索引之前删除现有索引):
db.messages.createIndex({"$**":"text"})
该查询将自动在文档中的任何字符串字段上设置文本索引。 要进行测试,请在其中插入具有新字段location
的新文档:
db.messages.insert({"subject":"Birds can cook", "content":"Birds do not eat rats", "likes": 12, "year":2013, location: "Chicago", "language":"english"})
现在,如果您尝试使用关键字chicago
(以下查询)进行文本搜索,它将返回我们刚刚插入的文档。
db.messages.find({$text: {$search: "chicago"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
我想在这里重点介绍以下几点:
- 请注意,在插入新文档之后,我们没有在
location
字段上明确定义索引。 这是因为我们已经使用$**
运算符在整个文档上定义了文本索引。 - 有时通配符索引可能会变慢,尤其是在您的数据非常大的情况下。 因此,请明智地计划文档索引(也称为通配符索引),因为它可能会导致性能下降。
进阶搜寻
词组搜寻
您可以搜索“喜欢烹饪的聪明鸟”之类的短语 使用文本索引。 默认情况下,词组搜索对所有指定的关键字进行“ 或”搜索,即,它将查找包含关键字smart
, bird
, love
或cook
文档。
db.messages.find({$text: {$search: "smart birds who cook"}}, {score: {$meta: "text Score"}}).sort({score:{$meta:"text Score"}})
该查询将输出以下文档:
{ "_id" : ObjectId("55f5289cb592880356441ead"), "subject" : "Birds can cook", "content" : "Birds do not eat rats", "likes" : 12, "year" : 2013, "location" : "Chicago", "language" : "english", "score" : 2 }
{ "_id" : ObjectId("55f5289bb592880356441eab"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }
如果您想执行精确的词组搜索(逻辑AND ),可以通过在搜索文本中指定双引号来实现。
db.messages.find({$text: {$search: "\"cook food\""}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
该查询将产生以下文档,其中包含短语“ cook food”:
{ "_id" : ObjectId("55f5289bb592880356441eab"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }
否定搜索
在搜索关键字前面加上–
(减号)会排除所有包含否定词的文档。 例如,尝试使用以下查询搜索包含关键字rat
但不包含birds
任何文档:
db.messages.find({$text: {$search: "rat -birds"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
在幕后看
我没有透露至今的一个重要功能是怎么看的幕后,看看你的搜索关键词被梗,停止措辞应用,否定等$explain
救援。 您可以通过传递true
作为其参数来运行说明查询,这将为您提供有关查询执行的详细统计信息。
db.messages.find({$text: {$search: "dogs who cats dont eat ate rats \"dogs eat\" -friends"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}}).explain(true)
如果查看说明命令返回的queryPlanner
对象,您将能够看到MongoDB如何解析给定的搜索字符串。 观察到它忽略了“ who
这样的停用词,并阻止了dogs
的dog
。
您还可以看到我们在搜索中忽略的术语以及在parsedTextQuery
部分中使用的短语。
"parsedTextQuery" : {
"terms" : [
"dog",
"cat",
"dont",
"eat",
"ate",
"rat",
"dog",
"eat"
],
"negatedTerms" : [
"friend"
],
"phrases" : [
"dogs eat"
],
"negatedPhrases" : [ ]
}
解释查询将非常有用,因为我们执行更复杂的搜索查询并希望对其进行分析。
加权文本搜索
当我们在文档中的多个字段上拥有索引时,大多数情况下,一个字段比另一个字段更重要(即,权重更高)。 例如,当您在博客中搜索时,博客标题应具有最高的权重,其次是博客内容。
每个索引字段的默认权重为1。要为索引字段分配相对权重,可以在使用createIndex
命令时包括weights
选项。
让我们通过一个例子来理解这一点。 如果您尝试使用当前索引搜索cook
关键字,则会产生两个文档,两个文档的得分相同。
db.messages.find({$text: {$search: "cook"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
{ "_id" : ObjectId("55f5289cb592880356441ead"), "subject" : "Birds can cook", "content" : "Birds do not eat rats", "likes" : 12, "year" : 2013, "location" : "Chicago", "language" : "english", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("55f5289bb592880356441eab"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }
现在让我们修改索引以包含权重; subject
字段的权重为3, content
字段的权重为1。
db.messages.createIndex( {"$**": "text"}, {"weights": { subject: 3, content:1 }} )
现在尝试搜索关键字cook
,您会发现在subject
字段中包含此关键字的文档的得分(为2)比其他文档(得分为0.66)更高。
{ "_id" : ObjectId("55f5289cb592880356441ead"), "subject" : "Birds can cook", "content" : "Birds do not eat rats", "likes" : 12, "year" : 2013, "location" : "Chicago", "language" : "english", "score" : 2 }
{ "_id" : ObjectId("55f5289bb592880356441eab"), "subject" : "Cats eat rats", "content" : "Rats do not cook food", "likes" : 55, "year" : 2014, "language" : "english", "score" : 0.6666666666666666 }
分区文本索引
随着应用程序中存储的数据的增长,文本索引的大小也在不断增长。 随着文本索引大小的增加,每当进行文本搜索时,MongoDB必须针对所有索引条目进行搜索。
作为一种通过不断增长的索引来保持文本搜索效率的技术,您可以通过对常规$text
搜索使用相等条件来限制扫描的索引条目的数量。 一个非常常见的示例是搜索在特定年份/月份内发布的所有帖子,或搜索具有特定类别/标签的所有帖子。
如果您观察我们正在处理的文档,则其中有一个尚未使用的year
字段。 一种常见的方案是按年份搜索邮件,以及我们一直在学习的全文搜索。
为此,我们可以创建一个复合索引,该索引指定year
上的升/降索引键,然后在subject
字段上指定文本索引。 通过这样做,我们正在做两件重要的事情:
- 我们在逻辑上将整个收集数据划分为按年份分隔的集合。
- 这将限制文本搜索以仅扫描属于特定年份(或称为特定年份)的那些文档。
删除已经拥有的索引,并在( year
, subject
)上创建一个新的复合索引:
db.messages.createIndex( { "year":1, "subject": "text"} )
现在执行以下查询,以搜索所有在2015年创建并包含cats
关键字的消息:
db.messages.find({year: 2015, $text: {$search: "cats"}}, {score: {$meta: "textScore"}}).sort({score:{$meta:"textScore"}})
该查询将只返回一个匹配的文档,如预期的那样。 如果您对该查询进行explain
并查看executionStats
,则会发现此查询的totalDocsExamined
为1,这表明我们的新索引已得到正确利用,并且MongoDB仅扫描单个文档,而安全地忽略了所有其他totalDocsExamined
文档在2015年。
文字索引:好处
文本索引还能做什么?
在学习文本索引方面,我们已经走了很长一段路。 您可以尝试许多其他概念来使用文本索引。 但是由于本文的范围,今天我们将无法对其进行详细讨论。 尽管如此,让我们简要看一下这些功能是什么:
- 文本索引提供了多语言支持,使您可以使用
$language
运算符以不同的语言进行搜索。 MongoDB当前支持大约15种语言,包括法语,德语,俄语等。 - 文本索引可用于聚合管道查询。 聚合搜索中的match阶段可以指定全文搜索查询的使用。
- 在使用文本索引时,可以将常规运算符用于投影,过滤器,限制,排序等。
MongoDB文本索引与外部搜索数据库
请记住,MongoDB全文搜索不能完全替代与MongoDB一起使用的传统搜索引擎数据库,因此,出于以下原因,建议使用本机MongoDB功能:
- 根据最近在MongoDB上的一次演讲,当前的文本搜索范围对于今天使用MongoDB构建的大多数应用程序(大约80%)都非常合适。
- 在相同的应用程序数据库中构建应用程序的搜索功能可降低应用程序的体系结构复杂性。
- MongoDB文本搜索实时工作,没有任何滞后或批量更新。 插入或更新文档后,文本索引条目即被更新。
- 文本搜索已集成到MongoDB的db内核功能中,它完全一致,即使在分片和复制中也能很好地工作。
- 它与您现有的Mongo功能(例如过滤器,聚合,更新等)完美集成。
文字索引:缺点
全文搜索是MongoDB中的一个相对较新的功能,目前缺少某些功能。 我将它们分为三类。 我们来看一下。
文本搜索缺少的功能
- 文本索引当前不支持可插入接口,例如可插入词干,停用词等。
- 它们目前不支持基于同义词,相似词等进行搜索的功能。
- 它们不存储术语位置,即,两个关键字被分隔的单词数。
- 您不能从文本索引中指定排序表达式的排序顺序。
现有功能限制
- 复合文本索引不能包含任何其他类型的索引,例如多键索引或地理空间索引。 此外,如果复合文本索引在文本索引键之前包含任何索引键,则所有查询都必须为前面的键指定相等运算符。
- 有一些特定于查询的限制。 例如,查询只能指定一个
$text
表达式,不能将$text
与$nor
,不能将hint()
命令与$text
使用,将$text
与$or
一起使用,$or
需要其中的所有子句您的$or
要索引的表达式,等等。
绩效劣势
- 文本索引在插入新文档时会产生开销。 这反过来会影响插入吞吐量。
- 某些查询(例如词组搜索)可能相对较慢。
包起来
全文搜索一直是MongoDB最需要的功能之一。 在本文中,我们先介绍什么是全文搜索,然后再继续介绍创建文本索引的基础。
然后,我们探索了复合索引,通配符索引,短语搜索和否定搜索。 此外,我们探索了一些重要的概念,例如分析文本索引,加权搜索以及对索引进行逻辑分区。 我们预计在即将发布的MongoDB版本中将对此功能进行一些重大更新。
我建议您尝试一下文本搜索并分享您的想法。 如果您已经在应用程序中实现了它,请在这里分享您的经验。 最后,请随时在评论部分中对本文发表您的问题,想法和建议。
翻译自: https://code.tutsplus.com/tutorials/full-text-search-in-mongodb--cms-24835