前言
刚开始接触 Elasticsearch 的时候被 Elasticsearch 的搜索功能搞得晕头转向, 每次想在 Kibana 里面查询某个字段的时候, 查出来的结果经常不是自己想要的, 然而又不知道问题出在了哪里. 出现这个问题归根结底是因为对于 Elasticsearch 的底层索引原理以及各个查询搜索方式的不了解, 在 Elasticsearch 中仅仅字符串相关的查询就有 19 个之多, 如果不弄清楚查询语句的工作方式, 应用可能就不会按照我们预想的方式运作. 这篇文章就详细介绍了 Elasticsearch 的 19 种搜索方式及其原理, 老板再也不用担心我用错搜索语句啦!
简介
Elasticsearch 为所有类型的数据提供实时搜索和分析, 不管数据是结构化文本还是非结构化文本, 数字数据或地理空间数据, 都能保证在支持快速搜索的前提下对数据进行高效的存储和索引. 用户不仅可以进行简单的数据检索, 还可以聚合信息来发现数据中的趋势和模式.
搜索是 Elasticsearch 系统中最重要的一个功能, 它支持结构化查询, 全文查询以及结合二者的复杂查询. 结构化查询有点像 SQL 查询, 可以对特定的字段进行筛选, 然后按照特定的字段进行排序得到结果. 全文查询会根据查询字符串寻找相关的文档, 并且按照相关性排序.
Elasticsearch 内包含很多种查询类型, 下面介绍是其中最重要的 19 种. 如果你的 App 想要添加一个搜索框, 为用户提供搜索操作, 并且数据量很大用 MySQL 会造成慢查询想改用 Elasticsearch, 那么我相信这篇文章会给你带来很大的帮助.
query 和 filter 区别
在正式进入到搜索部分之前, 我们需要区分 query(查询)和 filter(过滤)的区别.
在进行 query 的时候, 除了完成匹配的过程, 我们实际上在问 "这个结果到底有多匹配我们的搜索关键词". 在所有的返回结果的后面都会有一个_score 字段表示这个结果的匹配程度, 也就是相关性. 相关性越高的结果就越排在前面, 相关性越低就越靠后. 当两个文档的相关性相同的时候, 会根据 lucene 内部的 doc_id 字段来排序, 这个字段对于用户是不可见的也不能控制.
而在进行 filter 的时候, 仅仅是在问 "这个文档符不符合要求", 这仅仅是一个过滤的操作判断文档是否满足我们的筛选要求, 不会计算任何的相关性. 比如 timestamp 的范围是否在 2019 和 2020 之间, status 状态是否是 1 等等.
在一个查询语句里面可以同时存在 query 和 filter, 只不过只有 query 的查询字段会进行相关性_score 的计算, 而 filter 仅仅用来筛选. 比如在下面的查询语句里面, 只有 title 字段会进行相关性的计算, 而下面的 status 只是为了筛选并不会计算相关性.
- GET /_search
- {
- "query": {
- "bool": {
- "must": [
- {"match": {"title": "Search"}}
- ],
- "filter": [
- {"term": {"state": 1}}
- ]
- }
- }
- }
对于在实际应用中应该用 query 还是用 filter 需要根据实际的业务场景来看. 如果你的产品的搜索只是需要筛选得到最后的搜索结果并不需要 Elasticsearch 的相关性排序(你可能自定义了其他的排序规则), 那么使用 filter 就完全能够满足要求并且能够有更好的性能(filter 不需要计算相关性而且会缓存结果); 如果需要考虑文档和搜索词的相关性, 那么使用 query 就是最好的选择.
相关性
上面讲到了在使用 query 查询的时候会计算相关性并且进行排序, 很多人都会好奇相关性是怎么计算的?
相关性的计算是比较复杂的, 详细的文档可以看这两篇博客 -- 什么是相关性和 Elasticsearch 使用教程之_score(评分)介绍, 我这里只是做一个简单的介绍.
Elasticsearch 的相似度计算主要是利用了全文检索领域的计算标准 --TF/IDF(Term Frequency/Inverted Document Frequency)也就是检索词频率和反向文档频率
TF(检索词频率): 检索词在这个字段里面出现的频率越高, 相关性越高. 比如搜索词出现 5 次肯定比出现 1 次的文档相关性更高.
IDF(反向文档频率): 包含检索词的文档的频率越高, 这个检索词的相关性比重越低. 如果一个检索词在所有的文档里面都出现了, 比如中文的的, 那么这个检索词肯定就不重要, 相对应的根据这个检索词匹配的文档的相关性权重应该下降.
字段长度: 注意这个字段是文档的里面被搜索的字段, 不是检索词. 如果这个字段的长度越长, 相关性就越低. 这个主要是因为这个检索词在字段内的重要性降低了, 文档就相对来说不那么匹配了.
在复合查询里面, 比如 bool 查询, 每个子查询计算出来的评分会根据特定的公式合并到综合评分里面, 最后根据这个综合评分来排序. 当我们想要修改不同的查询语句的在综合评分里面的比重的时候, 可以在查询字段里面添加 boost 参数, 这个值是相对于 1 来说的. 如果大于 1 则这个查询参数的权重会提高; 如果小于 1, 权重就下降.
这个评分系统一般是系统默认的, 我们可以根据需要定制化我们自己的相关性计算方法, 比如通过脚本自定义评分.
分析器
分析器是针对 text 字段进行文本分析的工具. 文本分析是把非结构化的数据 (比如产品描述或者邮件内容) 转化成结构化的格式从而提高搜索效率的过程, 通常在搜索引擎里面应用的比较多.
text 格式的数据和 keyword 格式的数据在存储和索引的时候差别比较大. keyword 会直接被当成整个字符串保存在文档里面, 而 text 格式数据, 需要经过分析器解析之后, 转化成结构化的文档再保存起来. 比如对于 the quick fox 字符串, 如果使用 keyword 类型, 保存直接就是 the quick fox, 使用 the quick fox 作为关键词可以直接匹配, 但是使用 the 或者 quick 就不能匹配; 但是如果使用 text 保存, 那么分析器会把这句话解析成 the,quick,fox 三个 token 进行保存, 使用 the quick fox 就无法匹配, 但是单独用 the,quick,fox 三个字符串就可以匹配. 所以对于 text 类型的数据的搜索需要格外注意, 如果你的搜索词得不到想要的结果, 很有可能是你的搜索语句有问题.
分析器的工作过程大概分成两步:
分词(Tokenization): 根据停止词把文本分割成很多的小的 token, 比如 the quick fox 会被分成 the,quick,fox, 其中的停止词就是空格, 还有很多其他的停止词比如 & 或者 #, 大多数的标点符号都是停止词
归一化(Normalization): 把分隔的 token 变成统一的形式方便匹配, 比如下面几种
把单词变成小写, Quick 会变成 quick
提取词干, foxes 变成 fox
合并同义词, jump 和 leap 是同义词, 会被统一索引成 jump
Elasticsearch 自带了一个分析器, 是系统默认的标准分析器, 使用标准分词器 http://unicode.org/reports/tr29/ , 大多数情况下都能够有不错的分析效果. 用户也可以定义自己的分析器, 用于满足不同的业务需求.
想要知道某个解析器的分析结果, 可以直接在 ES 里面进行分析, 执行下面的语句就行了:
- POST /_analyze
- {
- "analyzer": "standard",
- "text": "1 Fire's foxes"
- }
返回的结果是:
- {
- "tokens" : [
- {
- "token" : "1",
- "start_offset" : 0,
- "end_offset" : 1,
- "type" : "<NUM>",
- "position" : 0
- },
- {
- "token" : "fire's",
- "start_offset" : 2,
- "end_offset" : 8,
- "type" : "<ALPHANUM>",
- "position" : 1
- },
- {
- "token" : "fox",
- "start_offset" : 9,
- "end_offset" : 12,
- "type" : "<ALPHANUM>",
- "position" : 2
- }
- ]
- }
返回的 tokens 内部就是所有的解析结果, token 表示解析的词语部分, start_offset 和 end_offset 分别表示 token 在原 text 内的起始和终止位置, type 表示类型, position 表示这个 token 在整个 tokens 列表里面的位置.
OK! 有了上面的基础知识, 就可以进行下面的搜索的介绍了.
term 搜索
term 搜索不仅仅可以对 keyword 类型的字段使用, 也可以对 text 类型的数据使用, 前提是使用的搜索词必须要预先处理一下 -- 不包含停止词并且都是小写(标准解析器), 因为文档里面保存的 text 字段分词后的结果, 用 term 是可以匹配的.
exists
返回所有指定字段不为空的文档, 比如这个字段对应的值是 null 或者 [] 或者没有为这个字段建立索引.
- GET /_search
- {
- "query": {
- "exists": {
- "field": "user"
- }
- }
- }
如果字段是空字符串 ""或者包含 null 的数组[null,"foo"], 都会被当作字段存在.
这个方法可以用来搜索没有被索引的值或者不存在的值.
fuzzy
fuzzy 查询是一种模糊查询, 会根据检索词和检索字段的编辑距离 (Levenshtein Distance) 来判断是否匹配. 一个编辑距离就是对单词进行一个字符的修改, 这种修改可能是
修改一个字符, 比如 box 到 fox
删除一个字符, 比如 black 到 lack
插入一个字符, 比如 sic 到 sick
交换两个相邻的字符的位置, 比如 act 到 cat
在进行 fuzzy 搜索的时候, ES 会生成一系列的在特定编辑距离内的变形, 然后返回这些变形的准确匹配. 默认情况下, 当检索词的长度在 0..2 中间时, 必须准确匹配; 长度在 3..5 之间的时候, 编辑距离最大为 1; 长度大于 5 的时候, 最多允许编辑距离为 2.
可以通过配置 fuzziness 修改最大编辑距离, max_expansions 修改最多的变形的 token 的数量
比如搜索是以下条件的时候:
- GET /_search
- {
- "query": {
- "fuzzy": {
- "name": "Accha"
- }
- }
- }
返回结果有 Iccha,AccHa,accha 还有 ccha
ids
根据文档的_id 数组返回对应的文档信息
- GET /_search
- {
- "query": {
- "ids": {
- "values": ["1","4","100"]
- }
- }
- }
- prefix
返回所有包含以检索词为前缀的字段的文档.
- GET /_search
- {
- "query": {
- "prefix": {
- "name": "ac"
- }
- }
- }
返回所有以 ac 开头的字段, 比如 acchu,achu,achar 等等
在某些场景下面比如搜索框里面, 需要用户在输入内容的同时也要实时展示与输入内容前缀匹配的搜索结果, 就可以使用 prefix 查询. 为了加速 prefix 查询, 还可以在设置字段映射的时候, 使用 index_prefixes 映射. ES 会额外建立一个长度在 2 和 5 之间索引, 在进行前缀匹配的时候效率会有很大的提高.
range
对字段进行范围的匹配.
- GET /_search
- {
- "query": {
- "range": {
- "age": {
- "gte": 10,
- "lte": 20
- }
- }
- }
- }
搜索年龄在 10(包含)和 20(包含)之间的结果
regexp
正则表达式匹配. 通过正则表达式来寻找匹配的字段, lucene 会在搜索的时候生成有限状态机, 其中包含很多的状态, 默认的最多状态数量是 10000
- GET /_search
- {
- "query": {
- "regexp": {
- "name": "ac.*ha"
- }
- }
- }
这个搜索会匹配 achha,achintha 还有 achutha
term
根据检索词来准确匹配字段. 官方文档建议不要用 term 去搜索 text 类型的字段, 因为分析器的原因很有可能不会出现你想要的结果. 但是直接使用 term 去搜索 text 字段还是可以工作的, 前提是明白为什么会返回这些数据. 比如通过下面的搜索:
- GET /_search
- {
- "query": {
- "term": {
- "name": {
- "value": "accha"
- }
- }
- }
- }
如果 name 字段是 keyword 类型的, 没有进行解析, 那么只会匹配所有 name 是 accha 的文档.
如果 name 字段是 text 类型的, 原字段经过分词, 小写化处理之后, 只能匹配到解析之后的单独 token, 比如使用标准解析器, 这个搜索会匹配 Accha Baccha,so cute accha baccha 或者 Accha Baccha Shivam 等字段.
terms
根据检索词列表来批量搜索文档, 每个检索词在搜索的时候相当于 or 的关系, 只要一个匹配就行了. Elasticsearch 最多允许 65,536 个 term 同时查询.
- GET /_search
- {
- "query": {
- "terms": {
- "name": [
- "accha",
- "ghazali"
- ]
- }
- }
- }
上面的查询会匹配 name 字段为 accha 和 ghazali 的文档.
除了直接指定查询的 term 列表, 还可以使用 Terms lookUp 功能, 也就是指定某一个存在的文档的某一个字段 (可能是数字, 字符串或者列表) 来作为搜索条件, 进行 terms 搜索.
比如有一个文件 index 是 my_doc,id 是 10,name 字段是 term 并且值为 accha, 搜索可以这样写:
- {
- "query": {
- "terms": {
- "name": {
- "index": "my_doc",
- "id": "10",
- "path": "name"
- }
- }
- }
- }
这样就可以返回所有 name 字段值是 accha 的文档里, 这个通常可以用来查询所有和某个文档某个字段重复的文档并且不需要提前知道这个字段的值是什么.
terms_set
terms_set 和 terms 十分类似, 只不过是多了一个最少需要匹配数量 minimum_should_match_field 参数. 当进行匹配的时候, 只有至少包含了这么多的 terms 中的 term 的时候, 才会返回对应的结果.
- GET /_search
- {
- "query": {
- "terms_set": {
- "programming_languages": {
- "terms": ["c++","java","php"],
- "minimum_should_match_field": "required_match"
- }
- }
- }
- }
- {
- "name":"Jane Smith",
- "programming_languages":[
- "c++",
- "java"
- ],
- "required_matches":2
- }
那么只有 programming_languages 列表里面至少包含 ["c++", "java", "php"] 其中的 2 项才能满足条件
还可以使用 minimum_should_match_script 脚本来配置动态查询
- {
- "query": {
- "terms_set": {
- "programming_languages": {
- "terms": ["c++","java","php"],
- "minimum_should_match_script": {
- "source": "Math.min(params.num_terms, doc['required_matches'].value)"
- }
- }
- }
- }
- }
其中 params.num_terms 是在 terms 字段中的元素的个数
wildcard
通配符匹配, 返回匹配包含通配符的检索词的结果.
目前只支持两种通配符:
?: 匹配任何单一的字符
*: 匹配 0 个或者多个字符
在进行 wildcard 搜索的时候最好避免在检索词的开头使用 * 或者?, 这会降低搜索性能.
- GET /_search
- {
- "query": {
- "wildcard": {
- "name": {
- "value": "acc*"
- }
- }
- }
- }
这个搜索会匹配 acchu,acche 或者 accio 父
text 搜索
text 搜索实际上是针对被定义为 text 类型的字段的搜索, 通常搜索的时候不能根据输入的字符串的整体来理解, 而是要预先处理一下, 把搜索词变成小的 token, 再来查看每个 token 的匹配.
interval
返回按照检索词的特定排列顺序排列的文档. 这个查询比较复杂, 这里只是简单的介绍, 详细的介绍可以看官方文档
比如我们想查询同时包含 raj 和 nayaka 的字段并且 ray 正好在 nayaka 前面, 查询语句如下:
- POST /_search
- {
- "query": {
- "intervals": {
- "name": {
- "match": {
- "query": "raj nayaka",
- "max_gaps": 0,
- "ordered": true
- }
- }
- }
- }
- }
上面的查询会匹配 Raj Nayaka Acchu Valmiki 和 Yateesh Raj Nayaka.
如果把 ordered:true 去掉, 就会匹配 nayaka raj.
如果把 max_gaps:0 去掉, 系统会用默认值 - 1 也就是没有距离要求, 就会匹配 Raj Raja nayaka 或者 Raj Kumar Nayaka
其中有两个关键词 ordered 和 max_gaps 分别用来控制这个筛选条件是否需要排序以及两个 token 之间的最大间隔
match
查找和检索词短语匹配的文档, 这些检索词在进行搜索之前会先被分析器解析, 检索词可以是文本, 数字, 日期或者布尔值. match 检索也可以进行模糊匹配.
- GET /_search
- {
- "query": {
- "match": {
- "name": "nagesh acchu"
- }
- }
- }
以上的查询会匹配 NaGesh Acchu,Acchu Acchu 和 acchu. 系统默认是在分词后匹配任何一个 token 都可以完成匹配, 如果修改 operator 为 AND, 则会匹配同时包含 nagesh 和 acchu 的字段.
- GET /_search
- {
- "query": {
- "match": {
- "name": {
- "query": "nagesh acchu",
- "operator": "and"
- }
- }
- }
- }
上面这个查询就只会返回 NaGesh Acchu
查询的时候也可以使用模糊查询, 修改 fuzziness 参数
- GET /_search
- {
- "query": {
- "match": {
- "name": {
- "query": "nagesh acchu",
- "operator": "and",
- "fuzziness": 1
- }
- }
- }
- }
上面的语句会匹配 NaGesh Acchu 还有 Nagesh Bacchu
match_bool_prefix
match_bool_prefix 会解析检索词, 然后生成一个 bool 复合检索语句. 如果检索词由很多个 token 构成, 除了最后一个会进行 prefix 匹配, 其他的会进行 term 匹配.
比如使用 nagesh ac 进行 match_bool_prefix 搜索
- GET /_search
- {
- "query": {
- "match_bool_prefix": {
- "name": "nagesh ac"
- }
- }
- }
上面的查询会匹配 Nagesh Nagesh,Rakshith Achar 或者 ACoco
实际查询等价于
- GET /_search
- {
- "query": {
- "bool": {
- "should": [
- {
- "term": {
- "name": {
- "value": "nagesh"
- }
- }
- },
- {
- "prefix": {
- "name": {
- "value": "ac"
- }
- }
- }
- ]
- }
- }
- }
- match_phrase
词组匹配会先解析检索词, 并且标注出每个的 token 相对位置, 搜索匹配的字段的必须包含所有的检索词的 token, 并且他们的相对位置也要和检索词里面相同.
- GET /_search
- {
- "query": {
- "match_phrase": {
- "name": "Bade Acche"
- }
- }
- }
这个搜索会匹配 Bade Acche Lagte, 但是不会匹配 Acche Bade Lagte 或者 Bade Lagte Acche.
如果我们不要求这两个单词相邻, 希望放松一点条件, 可以添加 slop 参数, 比如设置成 1, 代表两个 token 之间相隔的最多的距离(最多需要移动多少次才能相邻). 下面的查询语句会匹配 Bade Lagte Acche
- GET /_search
- {
- "query": {
- "match_phrase": {
- "name": {
- "query": "Bade Acche",
- "slop": 1
- }
- }
- }
- }
- match_phrase_prefix
match_phrase_prefix 相当于是结合了 match_bool_prefix 和 match_phrase.ES 会先解析检索词, 分成很多个 token, 然后除去最后一个 token, 对其他的 token 进行 match_phrase 的匹配, 即全部都要匹配并且相对位置相同; 对于最后一个 token, 需要进行前缀匹配并且匹配的这个单词在前面的 match_phrase 匹配的结果的后面.
- GET /_search
- {
- "query": {
- "match_phrase_prefix": {
- "name": "acchu ac"
- }
- }
- }
上面的查询能够匹配 Acchu Acchu1 和 Acchu Acchu Papu, 但是不能匹配 acc acchu 或者 acchu pa
multi_match
multi_match 可以同时对多个字段进行查询匹配, ES 支持很多种不同的查询类型比如 best_fields(任何字段 match 检索词都表示匹配成功),phrase(用 match_phrase 代替 match)还有 cross_field(交叉匹配, 通常用在所有的 token 必须在至少一个字段中出现)等等
下面是普通的 best_fields 的匹配
- GET /_search
- {
- "query": {
- "multi_match": {
- "query": "acchu",
- "fields": [
- "name",
- "intro"
- ]
- }
- }
- }
只要 name 或者 intro 字段任何一个包含 acchu 都会完成匹配.
如果使用 cross_fields 匹配如下
- GET /_search
- {
- "query": {
- "multi_match": {
- "query": "call acchu",
- "type": "cross_fields",
- "fields": [
- "name",
- "intro"
- ],
- "operator": "and"
- }
- }
- }
上面的匹配需要同时满足下面两个条件:
name 中出现 call 或 intro 中出现 call
name 中出现 acchu 或 intro 中出现 acchu
所以这个查询能够匹配 name 包含 acchu 和 intro 包含 call 的文档, 或者匹配 name 同时包含 call 和 acchu 的文档.
common
common 查询会把查询语句分成两个部分, 较为重要的分为一个部分(这个部分的 token 通常在文章中出现频率比较低), 不那么重要的为一个部分(出现频率比较高, 以前可能被当作停止词), 然后分别用 low_freq_operator,high_freq_operator 以及 minimum_should_match 来控制这些语句的表现.
在进行查询之前需要指定一个区分高频和低频词的分界点, 也就是 cutoff_frequency, 它既可以是小数比如 0.001 代表该字段所有的 token 的集合里面出现的频率也可以是大于 1 的整数代表这个词出现的次数. 当 token 的频率高于这一个阈值的时候, 他就会被当作高频词.
- GET /_search
- {
- "query": {
- "common": {
- "body": {
- "query": "nelly the elephant as a cartoon",
- "cutoff_frequency": 0.001,
- "low_freq_operator": "and"
- }
- }
- }
- }
其中高频词是 the,a 和 as, 低频词是 nelly,elephant 和 cartoon, 上面的搜索大致等价于下面的查询
- GET /_search
- {
- "query": {
- "bool": {
- "must": [
- {"term": {"body": "nelly"}},
- {"term": {"body": "elephant"}},
- {"term": {"body": "cartoon"}}
- ],
- "should": [
- {"term": {"body": "the"}},
- {"term": {"body": "as"}},
- {"term": {"body": "a"}}
- ]
- }
- }
- }
但是第一个查询的效率要优于第二个, 因为 common 语句有性能上的优化, 只有重要的 token 匹配之后的文档, 才会在不重要的文档的查询时候计算_score; 不重要的 token 在查询的时候不会计算_score
query_string
输入一个查询语句, 返回和这个查询语句匹配的所有的文档.
这个查询语句不是简单的检索词, 而是包含特定语法的的搜索语句, 里面包含操作符比如 AND 和 OR, 在进行查询之前会被一个语法解析器解析, 转化成可以执行的搜索语句进行搜索. 用户可以生成一个特别复杂的查询语句, 里面可能包含通配符, 多字段匹配等等. 在搜索之前 ES 会检查查询语句的语法, 如果有语法错误会直接报错.
- GET /_search
- {
- "query": {
- "query_string": {
- "default_field": "name",
- "query": "acchu AND nagesh"
- }
- }
- }
上面的查询会匹配所有的同时包含 acchu 和 nagesh 的结果. 简化一下可以这样写:
- GET /_search
- {
- "query": {
- "query_string": {
- "query": "name: acchu AND nagesh"
- }
- }
- }
query_string 里面还支持更加复杂的写法:
name: acchu nagesh: 查询 name 包含 acchu 和 nagesh 其中的任意一个
book.\*:(quick OR brown)
:book 的任何子字段比如 book.title 和 book.content, 包含 quick 或者 brown
_exists_: title:title 字段包含非 null 值
name: acch*: 通配符, 匹配任何 acch 开头的字段
name:/joh?n(ath[oa]n)/
: 正则表达式, 需要把内容放到两个斜杠 / 中间
name: acch~: 模糊匹配, 默认编辑距离为 2, 不过 80% 的情况编辑距离为 1 就能解决问题 name: acch~1
count:[1 TO 5]: 范围查询, 或者 count:>10
下面的查询允许匹配多个字段, 字段之间时 OR 的关系
- GET /_search
- {
- "query": {
- "query_string": {
- "fields": [
- "name",
- "intro"
- ],
- "query": "nagesh"
- }
- }
- }
- simple_query_string
和上面的 query_string 类似, 但是使用了更加简单的语法. 使用了下面的操作符:
+ 表示 AND 操作
| 表示 OR 操作
- 表示否定
" 用于圈定一个短语
* 放在 token 的后面表示前缀匹配
()表示优先级
~N 放在 token 后面表示模糊查询的最大编辑距离 fuzziness
~N 放在 phrase 后面表示模糊匹配短语的 slop 值
- GET /_search
- {
- "query": {
- "simple_query_string": {
- "query": "acch* + foll~2 + -Karen",
- "fields": [
- "intro"
- ]
- }
- }
- }
上面的搜索相当于搜索包含前缀为 acch 的, 和 foll 编辑距离最大是 2 的并且不包含 Karen 的字段, 这样的语句会匹配 call me acchu 或者 acchu follow me
总结
Elasticsearch 提供了强大的搜索功能, 使用 query 匹配可以进行相关性的计算排序但是 filter 可能更加适用于大多数的过滤查询的情况, 如果用户对于标准解析器不太满意可以自定义解析器或者第三方解析器比如支持中文的 IK 解析器.
在进行搜索的时候一定要注意搜索 keyword 和 text 字段时候的区别, 使用 term 相关的查询只能匹配单个的 token 但是使用 text 相关的搜索可以利用前面的 term 搜索进行组合查询, text 搜索更加灵活强大, 但是性能相对差一点.
参考
什么是相关性?
Elasticsearch 使用教程之_score(评分)介绍
- Full text queries
- Term-level queries
- Elasticsearch query performance using filter query
- Unicode Text Segmentation http://unicode.org/reports/tr29/
短词匹配
Top hits query with same score?
更多精彩内容请看我的个人博客
来源: https://www.cnblogs.com/sunshuyi/p/12716828.html