9. 检索(1)

431 阅读18分钟

1. 检索选型指南

1.1 es检索分类

image.png

1.2 精准匹配检索和全文检索的本质区别

对于es检索类型,我们继续缩小学习范围,聚焦于最常用的精准匹配检索、全文检索、组合检索这3种类型。

精准匹配检索是es中一种根据确切词条值查找文档的方法。在此检索方式下,用户查询的关键词须与文档中的词条完全吻合,方可视为匹配。此类检索主要应用于结构化数据,如ID、状态和标签等。在es中,精准匹配检索通常采用term、terms和terms_set等检索类型。

全文检索是es中一种对文档内容进行深入分析和处理的方法,以便找到与查询关键词相关的文档。全文检索考虑词汇的语义关联,包括词干、同义词等,并通常对文档进行评分,以衡量其与查询关键词的相关程度。此类检索主要应用于非结构化文本数据,如文章和评论等。在es中,全文检索主要使用match、match_phrase、query_string等查询类

型。

组合检索是es中一种将多个查询条件结合在一起的检索方法。此类检索允许用户根据多个因素和逻辑操作符(如与、或、非)来定位相关文档,从而实现更为复杂且精确的查询。组合检索可以针对不同字段进行精准匹配和全文检索,并将它们组合在一个查询中。在es中,组合检索主要使用bool检索来实现多条件查询,包括must(与)、should(或)、must_not(非)等子句。

精准匹配检索与全文检索的本质区别主要表现在两个方面

  • 精准匹配检索不对待检索文本进行分词处理,而是将整个文本视为一个完整的词条进行匹配。
  • 全文检索则需要对文本进行分词处理。在分词后,每个词条将单独进行检索,并通过布尔逻辑(如与、或、非等)进行组合检索,以找到最相关的结果。

后续将以如下数据为例展开

PUT lwy_index
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text",
        "analyzer": "ik_max_word",
        "fields": {
          "keyword":{
            "type": "keyword"
          }
        }
      },
      "popular_degree":{
        "type": "integer"
      },
      "source_class":{
        "type": "keyword"
      }
    }
  }
}

POST lwy_index/_bulk
{"index":{"_id":1}}
{"title":"乌兰图雅经典歌曲30首连播 标清_手机乐视视频", "popular_degree":30, "source_class":"wechat"}
{"index":{"_id":2}}
{"title":"乌兰县地区生产总值22.9亿元", "popular_degree":10, "source_class":"blog"}
{"index":{"_id":3}}
{"title":"乌兰新闻网欢迎您!", "popular_degree":100, "source_class":"news"}
{"index":{"_id":4}}
{"title":"乌兰:你说急什么呢,我30岁了", "popular_degree":50, "source_class":"weibo"}
{"index":{"_id":5}}
{"title":"千城胜景 | 胜景美誉 多彩乌兰", "popular_degree":50, "source_class":"weibo"}
{"index":{"_id":6}}
{"title":"乌兰新世界百货", "popular_degree":80, "source_class":"news"}

精准匹配和全文检索的区别在下例中展示得很清楚

POST lwy_index/_search
{
  "query": {
    "match": {
      "title": "乌兰新闻网欢迎您!"
    }
  },
  "profile": "true"
}

可以看到所有数据都被召回了,profile:true是增加排查问题的功能

image.png

可以看到,match_query在检索的时候将待检索字符串做了分词处理。通过analyzer API可以查看分词结果。

POST lwy_index/_analyze
{
  "text": ["乌兰新闻网欢迎您"],
  "field": "title"
}

再看一下精准匹配的检索实现,代码如下。

POST lwy_index/_search
{
  "profile": "true",
  "query": {
    "term": {
      "title.keyword": "乌兰新闻网欢迎您!"
    }
  }
}

可以看到精准匹配是用整个文本串进行term检索的,不做分词处理。

1.3 精准匹配检索详解

1. term检索:单字段精准匹配

term检索主要应用于单字段精准匹配的场景。在实战过程中,需要避免将term检索应用于text类型的检索。进一步说,term检索针对的是非text类型,用于text类型时并不会报错,但检索结果一般会达不到预期。

若确实需要对text类型字段进行term检索,将会如何?通过下面示例来看一下效果。

POST lwy_index/_search
{
  "profile": "true",
  "query": {
    "term": {
      "title":{
        "value": "乌兰:你说急什么呢,我30岁了"
      }
    }
  }
}

发现该检索过程没有召回结果数据。写入时,“乌兰:你说急什么呢,我30岁了”经过ik_max_word分词处理后,转化为“乌兰”“兰”“你说”“急”“什么”“么”“呢”“我”“30”“岁”的倒排索引词项并进行存储。而检索时,检索的是“乌兰:你说急什么呢,我30岁了”整体,因此并没有数据召回。

2. terms检索:多字段精准匹配

terms检索主要应用于多值精准匹配场景,它允许用户在单个查询中指定多个词条来进行精确匹配。这种查询方式适合从文档中查找包含多个特定值的字段。而terms检索是针对未分析的字段进行精确匹配的,因此它在处理关键词、数字、日期等结构化数据时表现良好。

POST lwy_index/_search
{
  "query": {
    "terms": {
     "source_class":[
       "weibo",
       "wechat"
       ]
    }
  }
}

该查询示例展示了如何使用terms检索在一个字段中查找包含多个特定词条的文档。

terms检索与term检索的核心区别在于:Terms query支持多值匹配,而Term query仅适用于单一值匹配。

3. range检索:范围检索

range检索是es中一种针对指定字段值在给定范围内的文档的检索类型。这种查询适合对数字、日期或其他可排序数据类型的字段进行范围筛选。range检索支持多种比较操作符,如大于(gt)、大于等于(gte)、小于(lt)和小于等于(lte)等,可以实现灵活的区间查询。

POST lwy_index/_search
{
 "query": {
   "range": {
     "popular_degree": {
       "gte": 10,
       "lte": 100
     }
   }
 },
 "sort": [
   {
     "popular_degree": {
       "order": "desc"
     }
   }
 ]
}

寻找popular_degree字段值在10~100之间的文档。通过使用gte和lte操作符搜索

注意:

  1. 当search.allow_expensive_queries设置为false时,对text和keyword类型的range检索不能被执行。
  2. 对于text、keyword类型的区间检索来说,range query的实际意义不大

4. exists检索:是否存在检索

exists检索在es中用于筛选具有特定字段值的文档。这种查询类型适用于检查文档中是否存在某个字段,或者该字段是否包含非空值。通过使用exists检索,你可以有效地过滤掉缺少关键信息的文档。应用场景包括但不限于数据完整性检查、查询特定属性的文档以及对可选字段进行筛选等。

POST lwy_index/_search
{
 "query": {
   "exists": {
     "field": "title.keyword"
   }
 } 
}

查询后将返回包含title.keyword字段且字段值非空的文档。

5. wildcard检索:通配符检索

wildcard检索允许在检索时使用通配符表达式来匹配文档的字段值。通配符包括两种。

星号(*):表示零或多个字符,可用于匹配任意长度的字符串。

问号(?):表示一个字符,用于匹配任意单个字符。

wildcard检索适用于对部分已知内容的文本字段进行模糊检索。请注意,通配符查询可能会导致较高的计算负担,因此在实际应用中应谨慎使用,尤其是在涉及大量文档的情况下。

需要注意的是,wildcard检索主要适用于未分析的字段(如关键字字段),因为它基于原始字段值进行匹配。对于已分析的字段,通配符查询可能无法按预期工作,因为分词过程可能已改变了原始字段值。

POST lwy_index/_search
{
  "profile": "true", 
 "query": {
   "wildcard": {
     "title.keyword": {
       "value": "*乌兰*"
     }
   }
 } 
}

启用 profile:true可以看到 image.png

"type":"MultiTermQueryConstantScoreWrapper"表示查询类型为一对多词条查询的常数分数包装器。这意味着在执行通配符查询时,系统将为匹配的所有文档分配相同的分数。

6. prefix检索:前缀匹配检索

允许根据指定前缀检索文档的字段值。此查询类型适用于检索以特定字符或字符串作为名称开头的文档,例如查找具有相同名称开头的产品型号、姓名或地名等。在这些场景下,前缀查询可以方便地找到满足特定前缀条件的文档

这种查询方式只适用于keyword类型字段

POST lwy_index/_search
{
  "profile": "true", 
 "query": {
   "prefix": {
     "title.keyword": {
       "value": "乌兰"
     }
   }
 } 
}

7. terms set检索

主要用于解决多值字段中的文档匹配问题,在处理具有多个属性、分类或标签的复杂数据时非常有用。其核心功能在于,它可以检索匹配一定数量给定词项的文档,其中匹配的数量可以是固定值,也可以是基于另一个字段的动态值。

从应用场景来说,terms set检索在处理多值字段和特定匹配条件时具有很大的优势。它适用于标签系统、搜索引擎等场景。通过灵活设置匹配数量条件,使用terms set检索的方式可以轻松地找到满足特定需求的文档,对系统中的筛选和推荐功能具有很大帮助

POST lwy_index1/_bulk
{"index":{"_id":1}}
{"title":"电影1", "tags":["喜剧", "动作", "科幻"], "tags_count":3}
{"index":{"_id":2}}
{"title":"电影2", "tags":["喜剧", "爱情", "家庭"], "tags_count":3}
{"index":{"_id":3}}
{"title":"电影3", "tags":["科幻", "动作", "科幻"], "tags_count":3}

GET lwy_index1/_search
{
  "query": {
    "terms_set":{
      "tags":{
        "terms": ["喜剧", "动作", "科幻"],
        "minimum_should_match_field":"tags_count"
      }
    }
  }
}

使用terms_set检索,在名为lwy_index1中检索满足动态匹配数量要求的电影,匹配数量由tags_count字段决定,查询标签包括“喜剧”“动作”和“科幻”。文档1被召回

GET lwy_index1/_search
{
  "query": {
    "terms_set":{
      "tags":{
        "terms": ["喜剧", "动作", "科幻"],
        "minimum_should_match_script":{
          "source": "doc['tags_count'].value * 0.7"
        }
      }
    }
  }
}

在上述代码中,限制了匹配标签数量,由自定义脚本doc['tags_count'].value*0.7动态计算,表示至少有70%的标签是“喜剧”“动作”或“科幻”。例如,在查询结果中_id为1和_id为3的文档满足这个条件,它们就会被返回。

terms set检索是es中一种非常强大的检索方式,适用于处理具有多个属性、分类或标签的复杂数据。通过灵活地设置匹配数量条件,可以轻松地找到满足特定要求的文档。需要注意的是,使用terms set检索时可能会遇到性能问题,特别是在处理大量数据时。为了提高查询性能,可以考虑对数据进行预处理,例如使用聚类算法将标签分组,然后根据分组查询文档

8. fuzzy检索:支持编辑距离的模糊检索

在使用搜索引擎时可能会遇到搜索引擎给出的“Did you mean...”的提示,这一般是由于输入了一个错误的单词或句子。

当用户输入“pager”(单词语法错误)时,搜索引擎返回“Did you mean page?”或者,当用户输入“langauge”(单词拼写错误)时,搜索引擎返回“Did you mean language?”当用户输入“apple”(单词正确,但上下文语义错误时),搜索引擎返回“Did you mean apply?”

类似情况涉及模糊匹配,需要借助“编辑距离”来实现精准的搜索。

编辑距离是指从一个单词转换到另一个单词需要编辑单字符的次数。转换操作又分为的4种不同形式。

image.png

在fuzzy检索中,fuzziness参数用于编辑距离的设置,其默认值为AUTO,支持的数值为[0,1,2]。如果值设置越界,会报错。

fuzzy检索能够在用户输入内容存在拼写错误或上下文不一致时,仍然返回与搜索词相似的文档。通过使用编辑距离算法来度量输入词与文档中词条的相似程度,模糊查询在保证搜索结果相关性的同时,有效地提高了搜索容错能力。

PUT lwy_index1
{
  "mappings": {
   "properties": {
     "title":{
       "type": "text",
       "analyzer": "english"
     }
   }
  }
}

POST lwy_index1/_bulk
{"index":{"_id":1}}
{"title":"Editing Language Skins" }
{"index":{"_id":2}}
{"title":"Mirroring Pages in Page Layouts" }

POST lwy_index1/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "langauge"
      }
    }
  }
}

9. IDs检索

基于给定的ID组快速召回相关数据

GET lwy_index/_search
{
  "query": {
    "ids":{
      "values": [
        "1",
        "2",
        "3"
        ]
    }
  }
}

10. regexp检索:正则匹配检索

虽然该检索方式的功能强大,但建议在非必要情况下避免使用,以保持查询性能的高效和稳定

POST lwy_index/_search
{
  "query": {
    "regexp": {
      "title.keyword":{
        "value": "乌兰....."
      }
    }
  }
}

正则表达式乌兰.....表示以乌兰开头后跟五个任意字符的字段。

1.4 全文检索类型详解

1. match分词检索

适用于高召回率和结果精准度要求较低的场景,在追求高精准度的情境中应慎重选用。因为match检索本质上是由大bool检索和term检索相结合构成的,这意味着在确保较高召回率的前提下,它会适当牺牲精准度以满足各种查询需求

POST lwy_index/_search
{
  "profile": "true", 
  "query": {
    "match": {
      "title": "乌兰新闻" # 会被分词器分成 乌兰 和 新闻
    }
  }
}

match检索的核心就是将待检索的语句根据设定的分词器分解为独立的词项单元,然后对多个词项单元分别进行term检索,最后对各term检索词项进行bool组合。

2. match_phrase检索:短语检索

适用于注重精准度的召回场景。与match检索(分词检索)不同,match_phrase检索更适合称为短语匹配检索。因为match_phrase检索要求查询的词条顺序和文档中的词条顺序保持一致,以确保更高的精准度。因此,场景差异在于match_phrase检索强调短语的完整性和顺序,以提高查询结果的准确性。实战中建议——在需要精确匹配短语时使用match_phrase检索,以满足高精准度召回的需求。

POST lwy_index/_search
{
  "profile": "true", 
  "query": {
    "match_phrase": {
      "title": "乌兰新闻网"
    }
  }
}

image.png

POST lwy_index/_analyze
{
  "field": "title",
  "text": [
    "乌兰新闻网欢迎您"]
}

可以看到分词顺序是相同的 image.png

3. multi_match检索

适用于在多个字段上执行match检索的场景。它提供了一种方便的方法来在多个字段中同时搜索指定的关键词,从而实现跨字段的高效检索。

# 新加一个文档,原先没有message字段
POST lwy_index/_doc/7
{"title":"乌兰新世界百货", "popular_degree":80, "source_class":"news", "message":"乌兰"}

POST lwy_index/_search
{
  "query": {
    "multi_match": {
      "query": "乌兰",
      "fields": ["title^3", "message"]
    }
  }
}

查询主体包含一个multi_match查询。查询关键词为“乌兰”,我们希望在title和message两个字段中进行搜索。为了强调title字段在搜索结果中的重要性,使用“^3”来提高其权重。这意味着匹配title字段的文档将比匹配message字段的文档具有更高的相关性分数。

通过multi_match检索,可以在多个字段上同时搜索关键词“乌兰”,并进行权重调整,实现跨字段的高效检索。

4. match_phrase_prefix 检索

一种灵活且实用的查询类型,它结合了短语匹配和前缀匹配的特点。查询词语需要按顺序匹配文档中的内容,同时允许最后一个词语只匹配其前缀。这使得match_phrase_prefix检索在部分用户输入、搜索建议或自动补全等场景中非常有用,能够在保证查询结果精确性的同时提供良好的用户体验。

POST lwy_index/_search
{
  "profile": "true", 
  "query": {
    "match_phrase_prefix": {
      "title": "乌兰新"
    }
  }
}

image.png

5. query_string检索

允许用户使用Lucene查询语法直接编写复杂的查询表达式,具有高度的灵活性和精确度,支持多字段查询、通配符查询、模糊查询、范围查询等多种检索类型。应用场景包括高级搜索、数据分析和报表等,适合处理需满足特定需求、要求支持与或非表达式的复杂查询任务,通常用于专业领域或需要高级查询功能的应用中。

POST lwy_index/_search
{
  "query": {
    "query_string": {
      "default_field": "title",
      "query": "乌兰 AND 新闻"
    }
  }
}

6. simple_query_string检索

一种用户友好且易于使用的查询方式。它具有类似于query_string查询的灵活性,而且对用户输入的语法错误更加宽容。这种查询方式支持多字段、通配符、模糊等基本检索类型,同时简化了Lucene查询语法。应用场景包括基本搜索和快速筛选等,适用于具有简单而实用的查询功能的应用。使用simple_query_string查询,开发者可以在确保良好用户体验的同时降低查询错误率。

simple_query_string和query_string的区别如下。

  1. simple_query_string对语法的核查并不严格。在输入语句的语法不对时并不会报错。例如:“乌兰AND新闻AND”语法是错误的,simple_query_string可以执行,而query_string不可以
POST lwy_index/_search
{
  "query": {
    "simple_query_string": {
      "query": "乌兰 AND 新闻 AND",
      "fields": ["title"]
    }
  }
}
  1. simple_query_string只支持单词查询、短语查询或者包含查询,不支持使用通配符和正则表达式。这种查询方式更加安全,因为它不会产生性能问题。
  2. query_string它支持使用通配符、正则表达式和复杂的布尔运算。但这种复杂性可能会导致性能问题。

总的来说,如果查询语法比较简单,则可以使用simple_query_string;如果查询语法非常复杂,则可以使用query_string。

1.5 组合检索类型详解

组合检索主要分为两大类:bool组合检索和自定义评分检索。

1. bool组合检索

一种强大且灵活的查询方式,适用于处理复杂的检索场景。当单一或特定类型的查询条件无法满足多样化的需求时,bool组合检索就会成为首选方案。它主要包括以下4种子查询类型。

  • must:查询结果必须满足指定条件。
  • must_not:查询结果必须不满足指定条件。在此情况下,召回的数据评分为0,且不考虑评分。
  • filter:过滤条件,同样不考虑评分,召回的数据评分为0。使用filter可以借助缓存机制提高查询性能。
  • should:查询结果可以满足的部分条件,具体满足条件的最小数量由minimum_should_match参数控制。

使用bool组合检索,开发者可以轻松地实现对多个条件的灵活组合和处理,满足不同场景下的检索需求。

2. 自定义评分检索

当传统的BM25机制不能满足评分要求,且某一个或者多个字段需要提升、降低或者修改权重比例的时候,可以优先考虑自定义评分检索。如果自定义评分检索也无法满足场景需求,那就只能自己开发评分插件来实现了。

1.6 query和filter的区别

查询语句中的query和filter具有不同的用途。

  • query用于评估文档相关性,并对结果进行评分,通常用于搜索场景。
  • filter用于筛选文档,不会对文档评分,通常用于过滤场景。

因此,在性能优化方面,filter比query更有效,因为它不需要进行评分,并且可以被缓存。

请注意,当需要同时评分和筛选文档时,可以使用带有query和filter的组合检索语句,以获得更好的结果

POST lwy_index/_doc/7
{"title":"乌兰新世界百货", "popular_degree":80, "source_class":"weibo", "message":"乌兰"}
POST lwy_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "乌兰新"
          }
        },
        {
          "match": {
            "source_class": "weibo"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "popular_degree": "80"
          }
        }
      ]
    }
  }
}

image.png

1.7 总结

  • 全文检索类检索
    • match适用于召回率高、精准度不高的场景。
    • match_phrase适用于精准度高、召回率不高的场景。
    • match_phrase_prefix适用于短语前缀匹配检索。
    • mulit_match适用于多字段检索。
    • query_string适用于支持与或非表达式的检索。
    • simple_query_string适用于比query_string容错率更高的场景。
  • 精准匹配类检索
    • term检索适用于单字段精准匹配。
    • terms检索适用于多字段精准匹配。
    • range检索适用于范围检索。
    • exists检索适用于判定是否存在检索。
    • wildcard检索适用于类MySQL like检索,非必要不使用。
    • prefix检索适用于前缀匹配检索。
    • fuzzy检索适用于支持编辑距离的模糊查询。
    • IDs检索适用于基于文档ID组检索的场景。
    • regexp检索适用于正则匹配检索,非必要不使用