MongoDB文本索引

TrumanWong
7/17/2024
TrumanWong

MongoDB 中的 text 索引支持全文搜索。这种类型的 text 索引不应该与 MongoDB Atlas 全文搜索索引full-text search index相混淆,不同于 MongoDB 文本索引,后者利用 Apache Lucene 来提供额外的文本搜索功能。如果应用程序允许用户提交关键字查询,而这些查询与集合中的标题、描述和其他字段的文本相匹配,那么应该使用 text 索引。

文本索引支持对包含字符串内容的字段进行文本搜索查询。文本索引可提高搜索字符串内容中特定单词或短语时的性能。

一个集合只能有一个文本索引,但该索引可以包含多个字段。

注意:在本文中所有创建索引操作之前,必须先删除集合上已有的文本索引。

创建文本索引

文本索引支持对包含字符串内容的字段进行文本搜索查询。文本索引可提高搜索字符串内容中特定单词或短语时的性能。

创建文本索引需使用db.collection.createIndex()方法。语法如下:

db.<collection>.createIndex(
   {
      <field1>: "text",
      <field2>: "text",
      ...
   }
)

对于某些集合,你可能并不知道文档包含哪个字段。可以使用 $** 在文档的所有字符串字段上创建全文本索引。这样做不仅会对顶层的字符串字段建立索引,也会搜索内嵌文档和数组中的字符串字段。语法如下:

db.<collection>.createIndex( { "$**": "text" } )

传播关键如下集合:

> db.blog.insertMany( [
   {
     _id: 1,
     content: "This morning I had a cup of coffee.",
     about: "beverage",
     keywords: [ "coffee" ]
   },
   {
     _id: 2,
     content: "Who likes chocolate ice cream for dessert?",
     about: "food",
     keywords: [ "poll" ]
   },
   {
     _id: 3,
     content: "My favorite flavors are strawberry and coffee",
     about: "ice cream",
     keywords: [ "food", "dessert" ]
   }
] )
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }

创建单字段文本索引

content字段上创建文本索引:

> db.blog.createIndex( { "content": "text" } )
content_text

该索引支持在 content 字段上进行文本搜索查询。例如,以下查询返回其中 content 字段包含 coffee 字符串的文档:

> db.blog.find({ $text: { $search: "coffee" } })
[
  {
    _id: 1,
    content: 'This morning I had a cup of coffee.',
    about: 'beverage',
    keywords: [ 'coffee' ]
  },
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ]
  }
]

非索引字段上的匹配

{ "content": "text" }索引仅包含content字段,不返回非索引字段的匹配项。例如,以下查询在blog集合中搜索字符串food

> db.blog.find(
   {
      $text: { $search: "food" }
   }
)

查询未返回任何文档。尽管字符串 food 出现在文档 _id: 2_id: 3 中,但它分别出现在字段 aboutkeywords 中。aboutkeywords 字段不包含在文本索引中,因此不会影响文本搜索查询结果。

创建复合文本索引

blog 集合的 aboutkeywords 字段上创建复合文本索引:

> db.blog.createIndex({ "about": "text", "keywords": "text" })
about_text_keywords_text

该索引支持对 aboutkeywords 字段进行文本搜索查询。例如,以下查询返回字符串 food 出现在 aboutkeywords 字段中的文档:

> db.blog.find({ $text: { $search: "food" } })
[
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ]
  },
  {
    _id: 2,
    content: 'Who likes chocolate ice cream for dessert?',
    about: 'food',
    keywords: [ 'poll' ]
  }
]

创建通配符文本索引

blog集合上创建通配符文本索引:

> db.blog.createIndex( { "$**": "text" } )
$**_text

搜索单个单词

查询blog集合中的字符串coffee

> db.blog.find( { $text: { $search: "coffee" } } )
[
  {
    _id: 1,
    content: 'This morning I had a cup of coffee.',
    about: 'beverage',
    keywords: [ 'coffee' ]
  },
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ]
  }
]

搜索多个单词

$text 会使用空格和大多数标点符号作为分隔符来对搜索字符串进行标记,并在搜索字符串时对所有这些标记执行 OR 逻辑。

查询blog集合中包含字符串pollcoffee的文档:

> db.blog.find( { $text: { $search: "poll coffee" } } )
[
  {
    _id: 1,
    content: 'This morning I had a cup of coffee.',
    about: 'beverage',
    keywords: [ 'coffee' ]
  },
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ]
  },
  {
    _id: 2,
    content: 'Who likes chocolate ice cream for dessert?',
    about: 'food',
    keywords: [ 'poll' ]
  }
]

搜索确切的短语

查询blog集合中包含短语chocolate ice cream的文档:

> db.blog.find( { $text: { $search: "\"chocolate ice cream\"" } } )
[
  {
    _id: 2,
    content: 'Who likes chocolate ice cream for dessert?',
    about: 'food',
    keywords: [ 'poll' ]
  }
]

为文本索引指定默认语言

默认情况下,文本索引的 default_languageenglish。为提高非英语文本搜索查询的性能,可以指定与文本索引相关的不同默认语言。语法如下:

db.<collection>.createIndex(
   { <field>: "text" },
   { default_language: <language> }
)

如果指定default_language值为none ,则文本索引会解析字段中的每个单词,包括停用词,并忽略后缀词干。

创建如下集合:

> db.quotes.insertMany( [
   {
      _id: 1,
      quote : "La suerte protege a los audaces."
   },
   {
      _id: 2,
      quote: "Nada hay más surrealista que la realidad."
   },
   {
      _id: 3,
      quote: "Es este un puñal que veo delante de mí?"
   },
   {
      _id: 4,
      quote: "Nunca dejes que la realidad te estropee una buena historia."
   }
] )
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4 } }

以下操作在 quote 字段上创建文本索引,并将 default_language 设置为 spanish

> db.quotes.createIndex({ quote: "text" }, { default_language: "spanish" } )
quote_text

生成的索引支持使用西班牙语后缀词干提取规则对 quote 字段进行文本搜索查询。例如,以下查询在 quote 字段中搜索关键字 punal

> db.quotes.find(
   {
      $text: { $search: "punal" }
   }
)
[ { _id: 3, quote: 'Es este un puñal que veo delante de mí?' } ]

虽然 $search 值设置为 punal,但查询将返回包含单词 puñal 的文档,因为文本索引对变音符不敏感。

该索引还忽略了特定语言的停止词。例如,虽然带有 _id: 2 的文档包含词语 hay,但以下查询不会返回任何文档。hay 被归类为西班牙语停止词,即不包含在文本索引中。

为文本索引分配权重

为每个索引字段创建具有不同权重的 text 索引:

> db.blog.createIndex(
   {
     content: "text",
     keywords: "text",
     about: "text"
   },
   {
     weights: {
       content: 10,
       keywords: 5
     },
     name: "BlogTextIndex"
   }
 )
BlogTextIndex

text 索引有以下字段和权重:

这些权重表示索引字段之间的相对重要性。

以下查询在 blog 集合中的文档中搜索字符串 ice cream

db.blog.find(
   {
      $text: { $search: "ice cream" }
   },
   {
      score: { $meta: "textScore" }
   }
).sort( { score: { $meta: "textScore" } } )
[
  {
    _id: 2,
    content: 'Who likes chocolate ice cream for dessert?',
    about: 'food',
    keywords: [ 'poll' ],
    score: 12
  },
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ],
    score: 1.5
  }
]

搜索字符串 ice cream 匹配:

content 字段中术语匹配的影响力(10:1 权重)是 keywords 字段中术语匹配的 10 倍。

以下查询在 blog 集合中的文档中搜索字符串 food

> db.blog.find(
   {
      $text: { $search: "food" }
   },
   {
      score: { $meta: "textScore" }
   }
).sort( { score: { $meta: "textScore" } } )
[
  {
    _id: 3,
    content: 'My favorite flavors are strawberry and coffee',
    about: 'ice cream',
    keywords: [ 'food', 'dessert' ],
    score: 5.5
  },
  {
    _id: 2,
    content: 'Who likes chocolate ice cream for dessert?',
    about: 'food',
    keywords: [ 'poll' ],
    score: 1.1
  }
]

搜索字符串 food 匹配:

keywords 字段中术语匹配的影响力(5:1 权重)是 about 字段中术语匹配的 5 倍。

为了在搜索字符串匹配多个字段时计算 score,MongoDB 会将匹配的字段数量乘以相应字段的权重,然后对结果求和。

优化全文本搜索

如果对大型数据集执行文本搜索查询,单字段文本索引可能会扫描大量条目以返回结果,这可能会导致查询速度变慢。

如要提高查询性能,您可以创建复合文本索引 ,并在文本搜索查询中包含等值匹配。如果复合索引包含等值匹配中使用的字段,索引将扫描更少条目,并更快返回结果。

创建如下集合:

> db.inventory.insertMany( [
   { _id: 1, department: "tech", description: "lime green computer" },
   { _id: 2, department: "tech", description: "wireless red mouse" },
   { _id: 3, department: "kitchen", description: "green placemat" },
   { _id: 4, department: "kitchen", description: "red peeler" },
   { _id: 5, department: "food", description: "green apple" },
   { _id: 6, department: "food", description: "red potato" }
] )
{
  acknowledged: true,
  insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
}

在包含以下字段的 inventory 集合上创建复合索引:

> db.inventory.createIndex(
   {
     department: 1,
     description: "text"
   }
)
department_1_description_text

创建复合索引后,文本搜索查询仅扫描与 department 字段上指定的相等条件匹配的文档。

例如,以下查询会扫描 department 等于 kitchen 的文档,其中 description 字段包含字符串 green

> db.inventory.find( { department: "kitchen", $text: { $search: "green" } } )
[ { _id: 3, department: 'kitchen', description: 'green placemat' } ]