当前位置 : 首页 » 文章分类 :  开发  »  Elasticsearch-搜索

Elasticsearch-搜索

Elasticsearch 搜索


Search 搜索 API

Search APIs
https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html


GET /index/_search 搜索

Elasticsearch Guide [7.17] » REST APIs » Search APIs » Search API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-search.html

通过 _search 接口可在一个或多个索引或数据流上搜索数据,可通过 q 查询串来指定查询条件,也可以通过 body 参数指定查询条件。

在指定的索引上搜索
GET /<target>/_search
POST /<target>/_search
<target> 路径参数 target 可以是索引名、索引别名、数据流名,还 可以是逗号分割的多个索引/数据流,并且支持通配符 *

如果想在全部索引上搜索,可以使用 *_all,或者忽略 target 参数,例如:
GET /_search
POST /_search

_search 接口的一些参数既可以放在 query string 上,也可以放在 body 中,如果两个地方同时指定了,将以 query string 中的为准

seq_no_primary_term Query 或 Body 参数,boolean 类型,为 true 时返回 _seq_no_primary_term 字段。
version Query 或 Body 参数,boolean 类型,为 true 时返回 _version 字段。

如果不带任何查询参数就会返回 index 下的所有记录。

curl --location --request GET 'http://localhost:9200/article/_doc/_search' \
--header 'Content-Type: application/json' \
--data-raw ''

返回

{
    "took": 102,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "uv_ZjXEBrN9oq5tgVMuj",
                "_score": 1.0,
                "_source": {
                    "title": "es的使用",
                    "pathname": "/article/es",
                    "content": "新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。"
                }
            },
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "u__ojXEBrN9oq5tgGcul",
                "_score": 1.0,
                "_source": {
                    "title": "java基础",
                    "pathname": "/article/java-basic",
                    "content": "java基础介绍"
                }
            }
        ]
    }
}

track_total_hits 命中个数统计

track_total_hits query 参数,integer 或 bool 类型。此设置用于确定 Elasticsearch 是否应该精确地追踪与查询匹配的总命中数。

  • 如果设为一个整数,表示精确计算命中个数的最大值,默认值 10000,查询条件命中的文档个数超过此值时不再精确计算命中个数。
  • 如果设为 true 将每次都精确计算命中个数,会比较耗性能。
  • 如果设为 false 将完全不计算命中个数,返回结果无 hits.total 字段。

默认 _search 接口返回的 hits.total.value 值最大为 10000,搜索条件命中的文档个数超过 10000 时就不准了
默认情况下,Elasticsearch 会为最多 10,000 个命中提供精确计数。超出这个数字后,它会返回一个下限估算(例如,“10,000+”)。
命中个数大于 10000 时,hits.total.relation=gte 表示实际命中个数是大于 10000 的。
命中个数小于 10000 时,hits.total.relation=eq value 是准确的命中数。

{
    "hits":{
        "total":{
            "value":10000,
            "relation":"gte"
        }
    }
}

_source 指定返回字段

_source 参数指定返回结果 hits._source 中的字段数,默认值为 true

_source 参数可放在 query string,也可以放在 body 中。

放在 query string 中时,支持以下值:

  • true 返回文档的全部字段
  • false 不返回任何字段
  • <string> 逗号分割的字段名列表,支持 * 通配符

放在 body 中时可内嵌 excludesexcludes 字段

比如只返回文档的 _iduser_id 字段

{
    "_source": ["_id", "user_id"],
  "sort": {
      "user_id": {
          "order": "asc"
      }
  }
}

指定返回的和排除的

GET /_search
{
    "_source": {
        "includes": [ "obj1.*", "obj2.*" ],
        "excludes": [ "*.description" ]
    },
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}

The _source option
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering


q查询(URI搜索/lucene语法查询)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-search.html#search-api-query-params-q

q 搜索就是 URI 搜索,通过 URI 参数 q 来指定查询相关参数。q 搜索使用 Lucene 语法,不支持完整的 ES DSL 语法,但让我们可以快速做一个查询。
q 搜索会覆盖 body 中的 query 查询参数,如果同时指定,只会使用路径中的 q 参数。

例1、从索引 tweet 里面搜索字段 user 为 kimchy 的记录
GET /tweet/_search?q=user:kimchy

例2、从索引 tweet,user 里面搜索字段 user 为 kimchy的记录
GET /tweet,user/_search?q=user:kimchy
GET /kimchy,elasticsearch/_search?q=tag:wow

例3、从所有索引里面搜索字段 tag 为 wow 的记录
GET /_all/_search?q=tag:wow
GET /_search?q=tag:wow


highlight 高亮搜索

Request Body Search
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-request-body.html#request-body-search-highlighting

指定单个高亮字段:

GET /_search
{
    "query" : {
        "match": { "content": "kimchy" }
    },
    "highlight" : {
        "fields" : {
            "content" : {}
        }
    }
}

指定多字段高亮:

{
    "query": {
        "multi_match": {
            "query": "产品开发",
            "type": "cross_fields",
            "fields": ["title", "content", "summary"]
        }
    },
    "_source": ["_id", "title", "fileName"],
    "from": 0,
    "size": 10,
    "highlight": {
        "fields": {
            "title": {},
            "content": {},
            "summary": {}
        }
    }
}

指定高亮标签和高亮片段fragment大小:

GET /_search
{
    "query" : {
        "match": { "content": "kimchy" }
    },
    "highlight" : {
        "pre_tags": [
            "<span style=\"color:red\">"
        ],
        "post_tags": [
            "</span>"
        ],
        "fragment_size": 40,
        "fields" : {
            "content" : {}
        }
    }
}

sort 排序

Elasticsearch Guide [7.17] » Search your data » Sort search results
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/sort-search-results.html

默认按相关性 _score 倒序

在 Elasticsearch 中,相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序。
指定根据 _score 排序时,默认是倒序 desc,指定其他排序字段时,默认是升序 asc

track_scores 始终计算得分

如果排序字段中同时指定 _score 和其他字段,返回结果有 _score 值,为相关性得分。
如果排序字段中无 _score,只有其他字段,默认不会再计算 _score 分数,返回结果中的 _score 为 null
如果设置 track_scores 为 true,会始终计算 _score 值(无 _score 时值为0)

多字段排序

例1、单字段排序,如按 user_id 升序排序

{
    "sort": {
        "user_id": {
            "order": "asc"
        }
    }
}

例2、多字段排序
结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。

{
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

或者 先 _score 倒序,_score 相同的按 _id 升序

{
  "sort": [
    { "_score": { "order": "desc" } },
    { "_id": { "order": "asc" } }
  ]
}
多值字段排序(mode:min/max/avg/sum)

一种情形是字段有多个值的排序,这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?
对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min, max, avg, sum, median 等排序模式 。
例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:

{
  "sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
  }
}
值缺失时的处理(missing)

missing 参数自定对于没有排序字段的文档,如何进行排序,可设置为 _last 或 _first 或指定值,例如:

"sort" : [
  { "price" : {"missing" : "_last"} }
]
排序字段的返回值(format)

返回结果的 sort 字段中有每个字段的排序字段值。
例如指定 _score 和 _id 排序,结果中每个文档的 sort 字段是当前文档的 _score 和 _id 的值:

{
  "took": 17,
  "timed_out": false,
  "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 },
  "hits": {
    "total": { "value": 24, "relation": "eq" },
    "max_score": null,
    "hits": [
      {
        "_index": "index-1",
        "_type": "_doc",
        "_id": "q4a2lI8B3xTCjXouI7Gk",
        "_score": 2.0,
        "_source": { "id": "85cb46be02274ad8ab02b4dfd285a1e3" },
        "sort": [ 2.0, "q4a2lI8B3xTCjXouI7Gk" ]
      },
      {
        "_index": "index-2",
        "_type": "_doc",
        "_id": "rIa2lI8B3xTCjXouI7Gk",
        "_score": 1.9851243,
        "_source": { "id": "f05386cf20b34db8aa5272e8b8e4d2be" },
        "sort": [ 1.9851243, "rIa2lI8B3xTCjXouI7Gk" ]
      }
    ]
  }
}

可通过 format 参数指定比如 date 等字段在返回结果 sort 字段中值的格式,例如:

"sort" : [
  { "post_date" : {"format": "strict_date_optional_time_nanos"}}
]
嵌套字段的排序

分页

Elasticsearch Guide [7.17] » Search your data » Paginate search results
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html

from/size 分页

和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 from 和 size 参数:
from 参数,可放在路径中,也可放在 body 中,指定应该跳过的初始结果数量,默认是 0
size 参数,可放在路径中,也可放在 body 中,指定应该返回的结果数量,默认是 10
默认 from + size 的值不可超过 10000,可通过参数 index.max_result_window 增加这个限制值,但这会增加内存消耗,因为Elasticsearch必须为每一个可能的结果保留状态。如果需要对多于 10000 的数据进行分页,可以用 search_after 参数

如果每页展示 5 条结果,下面 3 个请求分别查询第 1 到 3 页的结果:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
分布式系统中的深度分页问题

假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。

现在假设我们请求第 1000 页—​结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。

可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。
所以 ES 就默认限制最多只能访问前 1w 个文档。这是通过 index.max_result_window 控制的。

es 目前支持最大的 skip 值是 index.max_result_window ,默认为 10000 。
也就是当 from + size > index.max_result_window 时,es 将返回错误
临时解决方法可以将 index.max_result_window 调高,但不是长久解决方案。

关于分库分表后的分页查询,58 架构师 沈剑 的这篇文章写的非常好
业界难题-“跨库分页”的四种方案
https://cloud.tencent.com/developer/article/1048654


scroll分页(缓存快照,无法跳页,不再推荐)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#scroll-search-results

ES 不再推荐 scroll 分页,深度分页应使用 search_after 代替

为了满足深度分页的场景,es 提供了 scroll 的方式进行分页读取。
原理是缓存首次查询的结果快照,之后从每次根据游标 scroll_id 从快照中取数据。
原理上是对某次查询生成一个游标 scroll_id,后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。

scroll 分页过程:
1、初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照;

GET fs/_search?scroll=3m
{
  "query": {"match_all": {}}
}

初始化的时候就像是普通的 search 一样
其中的 scroll=3m 代表当前查询的数据缓存 3 分钟
结果会返回一个 _scroll_id 用于之后使用

2、遍历时,从这个快照里取数据;
在遍历时候,拿到上一次遍历中的 _scroll_id,然后带 scroll 参数,重复上一次的遍历步骤,直到结果集中返回的 hits 字段为空,表示遍历完成。
每次都要传参数 scroll,刷新搜索结果的缓存时间,另外不需要指定 index 和 type(不要把缓存的时时间设置太长,占用内存)。
请求指定的 scroll_id 时就不需要 /index/_type 等信息了。每读取一页都会重新设置 scroll_id 的生存时间
如果 srcoll_id 的生存期很长,那么每次返回的 scroll_id 都是一样的,直到该 scroll_id 过期,才会返回一个新的 scroll_id。

scroll 快照分页的问题:
1、Search context 开销不小。
2、是一个临时快照,并不是实时的分页结果。

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-scroll


search_after(实时分页,无法跳页,推荐)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#search-after

ES 5.0 开始推出了 Search After 分页机制,可提供更实时的分页游标(live cursor),它的思想是利用上一页的分页结果来加速下一页的分页查询。

search_after 分页使用前一页中的一组排序值来检索匹配的下一页数据。
使用 search_after 分页需要多次请求的 query 和 sort 参数完全一致,如果在处理多个请求期间发生了 refresh 操作(也就是有新数据写入),则结果可能不一致。为避免这种情况发生,可以使用 PIT 时间点生成数据快照

第一次查询时指定唯一且稳定的分页方式

GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

这里为了避免 sort 字段相同值的导致排序不确定,这里增加了 _id 字段。
返回的结果会包含每个文档的 sort 字段的 sort value。这个就是上面所说的 分页游标(live cursor)

使用最后一个文档的 sort value 作为 search after 请求值,我们就可以这样子请求下一页结果了:

GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

search_after 使用方式上跟 scroll 很像,但是相对于 scroll 它是无状态的(stateless),没有 search context 开销;而且它是每次请求都实时计算的,所以也没有一致性问题(相反,有索引变化的话,每次排序顺序会变化呢)。但是比起 from+size 方式,还是有同样的问题没法解决:就是只能顺序的翻页,不能随意跳页。

app 中的信息流翻页很适合这种方式,因为无法跳页。

问题:
1、无法跳页
2、有新的符合查询条件的数据被插入后,会查询到上一页已经返回的数据。
比如用户当前正在看第 2 页的历史数据,如果此时后台数据源新增了一条数据,那么当用户继续上推操作查看第 3 页的历史数据时,就会把第 2 页的最后一条数据获取,并且会把该条数据作为第3页的第一条数据进行展示
解决方法: 查询时参数代入 上次最后一条数据的 create_time ,下一页的数据都必须大于这个 create_time


GET /index/_count 条件计数

Count API
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html

使用和 _search 接口相同的查询条件,可用于查询符合条件的文档个数。


PIT 时间点

Elasticsearch Guide [7.17] » REST APIs » Search APIs » Point in time API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html

在 Elasticsearch 中搜索数据时默认都是在最新实时数据上进行,有些情况下,比如 search_after 分页中需要将 ES 数据固定在一个数据快照上,此时可用 时间点(point in time, PIT) 解决。

POST /index/_pit 时间点

搜索请求前需要先显式地创建时间点,keep_alive 参数指定时间点的有效时间,例如:

POST /my-index-000001/_pit?keep_alive=1m

返回一个 id 字段如下:

{
    "id": "64O1AwESdmVoaWNsZV9mb3VyX3doZWVsFmhMNWNLdnJ0U0NDV2tRUGtWTXdSd1EAFk9LV3JSdzEwUTVPRW1RZksyYjd2bUEAAAAAAAAAAAEWa0p4SnloRFFRTEttVDRmOUJnN0ttZwABFmhMNWNLdnJ0U0NDV2tRUGtWTXdSd1EAAA=="
}

下次请求时将 id 的值放入 pit.id 参数中传过去,例如

POST /_search 
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "pit": {
        "id":  "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", 
        "keep_alive": "1m"  
    }
}

注意:

  • 带有 pit 参数的 search 请求不能指定 index, routing 和 preference 参数,这些参数都从 PIT 时间点中获取。
  • pit.id 告诉 es 在指定的 PIT 时间点上下文中进行查询。
  • keep_alive 参数告诉 es 本次查询需要将 PIT 时间点的有效期延长多长时间。这个参数不需要太大,只需要够本次请求执行即可。

PIT 时间点会消耗磁盘和内存资源

PIT 时间点会消耗磁盘和内存资源:

  • 打开 PIT 时间点会阻止删除不需要的 Lucene Segment,因为这些 segment 还要用到,所以会占用磁盘空间。
  • 打开过多的 PIT 时间点会占用堆内存,尤其在频繁删除、更新的索引上。

DELETE /_pit 删除时间点

超过 keep_alive 时间的 PIT 时间点会自动删除。
为了节省资源,不用的时间点应该及时主动关闭:

DELETE /_pit
{
    "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}

返回:

{
   "succeeded": true, 
   "num_freed": 3     
}

“succeeded”: true 表示和此时间点相关的所有搜索上下文都已被清除,num_freed 是删除的搜索上下文的个数。


slice 搜索分片

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html#search-slicing


Query DSL

Elasticsearch Guide [7.17] » Query DSL
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl.html

DSL(Domain Specific Language) 领域特定语言,Elasticsearch 里描述检索条件的一种语言,格式为 JSON,包括两种类型的语句:

  • Leaf query clauses 叶子查询,叶子查询匹配具体字段的值,例如 match, term, range,叶子查询可单独使用。
  • Compound query clauses 复合查询,以逻辑方式组合多个叶子、复合查询为一个查询,例如 bool

term 精确词查询
match 匹配查询
multi_match 多条件查询


查询(query)和过滤(filter)上下文

Query and filter context
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html

默认情况下,es 会根据相关性对每个匹配结果进行排序,相关性描述每个文档和查询条件的匹配度。
相关性分值是个正的浮点数,在检索结果每个文档的 _score 字段返回,这个值越大,表示此文档和查询条件越相关。
相关性分值计算取决于查询子句是在 query 上下文中还是在 filter 上下文中。

查询(query)上下文

query 上下文中的查询语句解决 “这个文档有多匹配这个查询条件?” 的问题,所以除了决定文档是否匹配,还会计算一个相关性分值放到 _score 字段返回。
查询条件中有 query 参数时,就是 query 上下文。

过滤(filter)上下文

filter 上下文中的查询语句解决 “这个文档是否匹配这个查询条件?” 的问题,答案只有“是”或“不是”,不需要评分。filter 上下文经常用于结构化数据的过滤,例如:
timestamp 是否在 2015 ~ 2016 范围内?
status 是否等于 published?

频繁使用的过滤查询会被 es 自动缓存,用于提高查询性能。

查询条件中有 filter 参数时,就是 filter 上下文。例如:

  • bool 查询中的 filter 或 must_not 参数
  • constant_score 查询中的 filter 参数
  • filter 聚合查询

query和filter的区别

query 需要计算相关性,按照分数进行排序,而且无法cache结果
filter 不需要计算相关性,且会缓存频繁查询的 filter 结果,所以 filter 会更快

如果是用于搜索,需要将最相关的数据先返回,那么用 query
如果只是要根据一些条件筛选出一部分数据,不关注其相关性排序,那么用 filter

默认情况下,ES 通过一定的算法计算返回的每条数据与查询语句的相关度,并通过 score 字段来表征。
但对于非全文索引的使用场景,用户并不 care 查询结果与查询条件的相关度,只是想精确的查找目标数据。
此时,可以通过 query-bool-filter 组合来让 ES 不计算 score,并且尽可能的缓存 filter 的结果集,供后续包含相同 filter 的查询使用,提高查询效率。


复合查询(Compound Query)

Compound queries
https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html

复合查询以逻辑的方式将多个叶子、复合查询组合为一个查询,可能是对查询结果和分数的合并,可能改变其行为,或者从 query 上下文切换为 filter 上下文。


bool 组合查询

Boolean query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html

bool 查询将多个查询子句组合到一起,多个子查询之间的逻辑关系是与(and),返回的文档必须匹配全部子查询。
支持的查询类型有:

  • must 返回文档必须严格匹配 must 条件,会影响相关性得分。
  • should 返回文档应当匹配 should 条件,会影响相关性得分。
  • filter 返回文档必须严格匹配 filter 条件,在 filter 上下文中执行,和 must 唯一的区别是不影响相关性得分。
  • must_not 返回文档必须不匹配 must_not 条件,也在 filter 上下文中执行,不影响相关性得分。

查询语句同时包含 must 和 should 时,返回的文档可以满足 must 条件但不满足 should 条件,因为 must 条件优先级高于 should,但是如果也满足 should 条件,则会提高相关性得分。

通常 should 子句是个数组,返回的文档必须匹配 minimum_should_match 个 should 条件。
通过 minimum_should_match 参数指定返回文档需要匹配的 should 子句的个数或百分比,如果 bool 查询包含至少一个 should 子句且没有 must 和 filter 子句,则 minimum_should_match 默认值为 1,否则默认值为 0

adjust_pure_negative 参数
这个参数的作用是在所有的查询条件(must、filter、should、must_not)都不存在,即查询条件为空的情况下,决定如何处理 must_not 查询。

  • 当 adjust_pure_negative=true 时,如果所有的查询条件都不存在,那么 must_not 查询会被忽略,返回所有的文档。
  • 当 adjust_pure_negative=false 时,如果所有的查询条件都不存在,那么 must_not 查询会被独立执行,返回不满足must_not条件的所有文档。

默认情况下,adjust_pure_negative是true,即如果没有任何其他的查询条件,must_not查询会被忽略,返回所有的文档。

例1、

POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user.id" : "kimchy" }
      },
      "filter": {
        "term" : { "tags" : "production" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },
      "should" : [
        { "term" : { "tags" : "env1" } },
        { "term" : { "tags" : "deployed" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

例2、3种不同的 filter 条件:时间范围+颜色多选+名字单选

{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "timestamp": {
                            "gte": 1645157343381,
                            "lte": 1646053743440
                        }
                    }
                },
                {
                    "terms": {
                        "color": ["青色","黄色"]
                    }
                },
                {
                    "term": {
                        "name": "小明"
                    }
                }
            ]
        }
    },
    "sort": {
        "timestamp": {
            "order": "desc"
        }
    },
    "from": 500,
    "size": 20
}

boosting 查询

Boosting query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html


dis_max 分离最大化查询

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html

分离最大化查询(Disjunction Max Query)将任何与任一查询匹配的文档作为结果返回,但 _score 评分只计算最佳匹配的评分。
dis_max 查询,只取分数最高的那个 query 的分数,但返回结果是全部 query 匹配的结果。

例如在 title 和 body 上进行 term 查询:

GET /_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "term": { "title": "Quick pets" } },
        { "term": { "body": "Quick pets" } }
      ],
      "tie_breaker": 0.7
    }
  }
}

dis_max 查询的得分 = 最佳匹配的得分 + sum(其他query匹配的得分 * tie_breaker)
tie_breaker 参数,默认是 0.0,非最佳匹配 query 的分值权重,取值是 0 到 1.0 之间的小数值。


match 全文查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/full-text-queries.html


match 分词查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query.html

match 查询会对入参的 text 进行分词后再和文档匹配。

例如

GET /_search
{
  "query": {
    "match": {
      "message": {
        "query": "this is a test"
      }
    }
  }
}

可以将 match 查询的 <field>query 合并简化,例如:

GET /_search
{
  "query": {
    "match": {
      "message": "this is a test"
    }
  }
}

例如,使用 match 查询 content 字段中的 java 关键词

{
    "query" : {
        "match" : {
            "content" : "java"
        }
    }
}'

结果:

{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 0.9336268,
        "hits": [
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "u__ojXEBrN9oq5tgGcul",
                "_score": 0.9336268,
                "_source": {
                    "title": "java基础",
                    "pathname": "/article/java-basic",
                    "content": "java基础介绍"
                }
            }
        ]
    }
}

match 查询的参数

query 输入的查询文本

analyzer 对查询语句进行分词的分词器,默认是对应字段的分词器。

operator 对query进行分词后,多个 term 之间的关系,默认 OR,可选 AND

  • OR 只要文档中包含任何一个查询词,就会被认为是匹配的
  • AND 那么只有文档中包含所有查询词,才会被认为是匹配的

prefix_length 用于模糊查询。它指定了一个词的前缀长度,只有这个长度之后的字符才会被模糊匹配。默认0。

max_expansions 用于模糊查询和前缀查询。它指定了最大的扩展词数量,也就是说,查询词可以被扩展成多少个近义词。默认50。

fuzzy_transpositions 用于模糊查询。如果设置为 true,那么查询词中的字符可以被交换位置。例如,如果查询词是 “abcd”,那么 “abdc” 也会被认为是匹配的。默认true。

fuzziness 这个参数用于模糊查询。它指定了允许的最大编辑距离,也就是说,查询词和文档中的词之间可以有多少个字符的差异。

lenient 如果设置为 true,那么如果字段不存在或者查询词格式错误,Elasticsearch 会忽略这个错误,而不是抛出异常。默认false。

zero_terms_query 当查询词被分析器全部过滤掉时,查询的行为。如果设置为 “NONE”,那么查询不会匹配任何文档。如果设置为 “ALL”,那么查询会匹配所有文档。默认 NONE。

auto_generate_synonyms_phrase_query 如果设置为 true,那么查询词会被当作短语查询,并且会自动生成同义词。默认true

boost 用于调整查询的权重,数值越大,权重越高,匹配的文档排名越靠前。

minimum_should_match 最小匹配数量。例如,如果设置为 2,那么至少有两个 term 匹配文档才会被认为是匹配的。

  • 正整数,例如3,表示最小匹配词数量
  • 负整数,例如-2,query 分词后的词数量减去2
  • 百分比,例如75%,query 分词后 75% 的 term 匹配
  • 负百分比,例如-25%,query 分词后 (1-25%) 的 term 匹配
  • 3<90%,query 分词数小于等于3则全都需要匹配,分词数大于3则匹配 90%
  • 2<-25% 9<-3,query 分词数小于等于2则全都需要匹配,分词数在3-9之间则需匹配75%,分词数大于9则总数-3个分词需要匹配。

Elastic Docs ›Elasticsearch Guide [7.17] ›Query DSL minimum_should_match parameter
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html

默认 match 查询参数为:

{
    "query": {
        "match": {
            "content": {
              "query": "查询文本",
              "operator": "OR",
              "minimum_should_match": "2",
              "prefix_length": 0,
              "max_expansions": 50,
              "fuzzy_transpositions": true,
              "lenient": false,
              "zero_terms_query": "NONE",
              "auto_generate_synonyms_phrase_query": true,
              "boost": 1.0
            }
        }
    },
    "from": 0,
    "size": 10
}

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-minimum-should-match.html


match_phrase 短语查询(词序列匹配)

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match phrase query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase.html

Match phrase 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致。
Match phrase 用于查找包含精确词序列的文档

注意:Match phrase 也会先对入参分词,并不是有的博客中说的 Match phrase 查询不对入参分词。

slop 参数告诉 match_phrase 分词相隔多远时仍然能将文档视为匹配,默认值是 0。
slop 参数用于放宽对词序的严格要求。Slop 指定了词条之间可以“移动”的次数,以便仍然被认为是匹配的。例如,slop 为 1 允许查询中的词条交换位置一次。
例如入参 I like riding 想匹配文档 I like swimming and riding,需要将 riding 向前移动两个位置,所以 slop 设为 2 时可匹配到。

{
  "query": {
    "match_phrase": {
      "message": {
        "query": "I like riding",
        "slop": 2
      }
    }
  }
}

match_phrase_prefix 短语前缀查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Match phrase prefix query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-query-phrase-prefix.html

和 Match phrase 查询类似,Match phrase prefix 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致,只不过最后一个单词会当做前缀进行匹配。

例如入参 “quick brown f” 可匹配 “quick brown fox” 或 “two quick brown ferrets”,但不会匹配 “the fox is quick and brown”

GET /_search
{
  "query": {
    "match_phrase_prefix": {
      "message": {
        "query": "quick brown f"
      }
    }
  }
}

combined_fields 联合字段查询(7.11+)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-combined-fields-query.html

combined_fields 查询支持在多个字段上进行匹配,它将多个字段的值合并到一个大的字符串中,然后在这个大的字符串上执行搜索。
combined_fields 是 以词为中心的(term-centric) 查询,首先将 query 查询串分词为多个 term,对于每个term,在多个 fields 中进行匹配。

combined_fields 查询特别适合一个整体被拆分为多个子部分字段,需要在这些子部分字段上匹配的场景
例如文章可拆分为 title, abstract, body,需要在这三个字段上匹配:

GET /_search
{
  "query": {
    "combined_fields" : {
      "query":      "database systems",
      "fields":     [ "title", "abstract", "body"],
      "operator":   "and"
    }
  }
}

combined_fields 和 multi_match 的区别

1、combined_fields 查询要求 fields 中的多个字段的分析器 analyzer 必须相同,如果想在不同 analyzer 的多个字段上匹配,则 multi_match 更合适。multi_match 允许在 text 和 非text 类型字段上一起匹配,也允许 text 类型字段有不同的 analyzer

2、multi_match 的 best_fields 和 most_fields 模式是 以字段为中心的(field-centric)的 查询,相反,combined_fields 是 以词为中心的(term-centric) 查询,operator 和 minimum_should_match 参数都是应用到分词后的每个 term 上的。

举例来说:

GET /_search
{
  "query": {
    "combined_fields" : {
      "query":      "database systems",
      "fields":     [ "title", "abstract"],
      "operator":   "and"
    }
  }
}

的执行模式是:

+(combined("database", fields:["title" "abstract"]))
+(combined("systems", fields:["title", "abstract"]))

也就是说,每个 term(在这里是 database 和 systems)必须在 “title”、”abstract” 中的至少一个字段上出现,文档才能匹配。

3、multi_match 的 cross_fields 模式也是 以词为中心的(term-centric) 查询方式,其中的 operator 和 minimum_should_match 参数也会应用在每个 term 上,而 combined_fields 查询相比 cross_fields multi_match 查询的优势是其基于 BM25F 的更强壮和可解释的相关性算法。
combined_fields 查询中的多个 field 的相关性算法也必须相同。


multi_match 多字段查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-multi-match-query.html

multi_match 查询支持在多个字段中搜索单个查询字符串,当希望在两个或多个字段中的任何一个中查找包含给定查询字符串的文档时非常有用。

例如在 subject 或 message 中查询 “this is a test”:

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "this is a test", 
      "fields": [ "subject", "message" ] 
    }
  }
}

可通过通配符指定字段名,例如:

GET /_search
{
  "query": {
    "multi_match" : {
      "query":    "Will Smith",
      "fields": [ "title", "*_name" ] 
    }
  }
}

指定每个字段的评分权重(boost)

可通过 ^ 单独设置某个字段的权重(boost),比如下面将 subject 字段的得分乘以3倍,默认每个字段的 boost 都是 1.0

GET /_search
{
  "query": {
    "multi_match" : {
      "query" : "this is a test",
      "fields" : [ "subject^3", "message" ] 
    }
  }
}

如果不指定 fields 参数,multi_match 会在索引的默认字段 index.query.default_field 上匹配,默认是 *.* 即全部字段。


multi_match 的类型

multi_match 的 type 参数可指定类型:

  • best_fields 默认,返回和 fields 中任意字段匹配的文档,但 _score 得分只用 best_fields 最佳字段计算(可通过 tie_breaker 参数调)。
  • most_fields 返回 fields 中有任意字段匹配的文档,_score 得分使用每个 filed 上的得分综合计算。
  • cross_fields 将具有相同 analyzer 分词器的字段合并看做一个大字段进行匹配。
  • phrase 在每个 fields 上执行 match_phrase 查询,_score 只计算最佳匹配的得分。
  • phrase_prefix 在每个 fields 上执行 match_phrase_prefix 查询,_score 只计算最佳匹配的得分。
  • bool_prefix 在每个 fields 上执行 match_bool_prefix 查询,_score 只计算最佳匹配的得分。

best_fields 最佳匹配(默认)

best_fields 适合要求 query 的多个词在一个字段中匹配到的情况,即多个词在同一个字段中匹配上才最有意义的
例如搜索 “brown fox”,subject 中只有 brown,或 message 中只有 fox,虽然都能匹配,但可能不是想要的结果,在 subject(或 message) 中同时包含 brown 和 fox 两个字段的才是最佳匹配。

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "brown fox",
      "type":       "best_fields",
      "fields":     [ "subject", "message" ],
      "tie_breaker": 0.3
    }
  }
}

best_fields 查询对每个 field 生成一个 query 语句,组合为 dis_max 查询。
best_fields 内部会转为 dis_max 分离最大化查询,所以也可以使用 dis_max 查询的 tie_breaker 参数,上面的查询等价于:

GET /_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "match": { "subject": "brown fox" }},
        { "match": { "message": "brown fox" }}
      ],
      "tie_breaker": 0.3
    }
  }
}

和 dis_max 查询一样,默认 best_fields 只用最佳匹配的得分作为结果的 _score,不过可以通过 tie_breaker 参数调整非最佳匹配的得分权重,tie_breaker 默认是 0


most_fields 多字段匹配

most_fields 适合同一份文本在不同字段中是不同分词形式存储的情况。
例如,主字段可能包含同义词、词干和无音调符号的词。第二个字段可能包含原始词,第三个字段可能包含连续词组(shingles)。通过组合所有三个字段的得分,可以实现:通过与主字段匹配可以尽可能多的匹配到文档,然后使用第二和第三个字段将最相似的结果排序到列表开头。
例如 content 是原始文本,content.ik_max 是使用 ik_max 分词器的文本,content.ik_smart, content.jieba 分别是使用其他两个分词器处理的文本,匹配时综合考虑这几个字段上的得分。

查询

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "quick brown fox",
      "type":       "most_fields",
      "fields":     [ "title", "title.original", "title.shingles" ]
    }
  }
}

内部转为 bool 复合查询执行:

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":          "quick brown fox" }},
        { "match": { "title.original": "quick brown fox" }},
        { "match": { "title.shingles": "quick brown fox" }}
      ]
    }
  }
}

每个 match 语句的得分加到一起,除以 match 语句的个数,就是整体的得分。


cross_fields 跨字段匹配

cross_fields 适合内容被拆分到多个字段存储的情况。
例如,名字被拆分为 first_name 和 last_name 两个字段存储,在这两个字段上进行 cross_fields 类型的 multi_match 匹配时会将 first_name 和 last_name 组合为一个字段匹配,正好能匹配上。

cross_fields 执行时先将 query 分词,然后用每个 term 在全部字段上匹配。

例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

执行时,先用 Will 在 first_name 和 last_name 上匹配,再用 Smith 在 first_name 和 last_name 上匹配。
所以,全部 term 必须在至少一个 field 中出现,此 doc 才算匹配。

cross_field 如果遇到 fields 中有不同分词器的情况,会先将 fields 按分词器类型分组 group,然后使用最佳匹配的组来计算得分。
例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Jon",
      "type":       "cross_fields",
      "fields":     [
        "first", "first.edge",
        "last",  "last.edge"
      ]
    }
  }
}

first 和 last 是同一个分词器,first.edge 和 last.edge 是同一个分词器,则上面的查询会如此执行:

    blended("jon", fields: [first, last])
| (
    blended("j",   fields: [first.edge, last.edge])
    blended("jo",  fields: [first.edge, last.edge])
    blended("jon", fields: [first.edge, last.edge])
)

cross_field 也有 tie_breaker 参数可控制 _score 得分计算,默认只使用 最佳匹配group 的得分(即 tie_breaker=0.0)


field-centric 和 term-centric

field-centric 以字段为中心

multi_match 的 best_fields 和 most_fields 模式是 以字段为中心的(field-centric)的 查询方式,会为每个 field 字段生成一个 query 语句。operator 和 minimum_should_match 参数也是应用到 field 级别的。
例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "best_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and" 
    }
  }
}

执行模式为:

  (+first_name:will +first_name:smith)
| (+last_name:will  +last_name:smith)

也就是说,全部 term(will 和 smith)必须在单个 field 中出现,文档才匹配

term-centric 以词为中心

multi_match 的 cross_fields 模式 和 combined_fields 查询是 以词为中心的(term-centric) 查询方式,为 query 分词后的每个 term 生成一个 query 语句,operator 和 minimum_should_match 参数也会应用在每个 term 上。
例如

GET /_search
{
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

执行模式为:

+(first_name:will  last_name:will)
+(first_name:smith last_name:smith)

也就是说,每个 term(在这里是 will 和 smith)必须在 “first_name”、”last_name” 中的至少一个字段上出现,文档才能匹配


query_string 查询(类Lucene语法查询)

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Query string query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-query-string-query.html

query_string 查询会严格校验查询串的语法,语法错误时会报错。
由于 query_string 查询语法错误时会报错,不建议在给用户输入的搜索框中使用,可选方案:

  • 如果需要支持 Lucene 语法,使用 simple_query_string 查询
  • 如果不需要支持 Lucene 语法,使用 match 查询
GET /_search
{
    "query": {
        "query_string" : {
            "query" : "(new york city) OR (big apple)",
            "default_field" : "content"
        }
    }
}

simple_query_string 简化lucene查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Simple query string query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-simple-query-string-query.html


Term 级查询

Term-level queries
https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html

term 查询用于精确匹配一个字段中的确切值。它不会对查询字符串进行分词,而是将整个字符串作为单个词项进行搜索。
注意字段本身内容是否分词,取决于索引 mapping 中此字段的类型

假设有一个文档,其字段值为 “Quick brown fox”。这个字段值在存入 Elasticsearch 时通常会经过分词处理。标准的分词器会将 “Quick brown fox” 分解为三个词项:[“Quick”, “brown”, “fox”]。
使用 “Quick” 做 term 查询时,可以和分词项 “Quick” 匹配,所以可以查到。
使用 “Quick brown” 做 term 查询时,因为 “Quick brown” 作为整体无法和任何分词项匹配,所以无法查询到。


term 等值匹配

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html


terms In多值匹配

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html

terms 查询返回包含入参 terms 列表中一个或多个精确匹配的文档。

例如,查询 user.id 包含 kimchy 或 elkbee 的文档,

GET /_search
{
  "query": {
    "terms": {
      "user.id": [ "kimchy", "elkbee" ],
      "boost": 1.0
    }
  }
}

注意:
1、如果字段存储了多个值(array values),其中任意一个在查询 terms 中即可匹配
例如 es 中有文档 "tags": ["tag1", "tag2", "tag3"]
查询 terms 是 {"query": {"terms": {"tag": ["tag3", "tag4"] }}}
也是可以匹配的,因为 tag3 出现在了查询 terms 列表中
也就是说,查询 {"terms": {"tag": ["tag3", "tag4"] }} 会匹配全部 tag 字段(无论tag字段存储了1个值还是多个值)包含 tag3 或 tag4 的文档。

2、在被分词的 text 字段上使用 terms 查询,将匹配任何包含 terms 中任何项的文档,而不仅仅是完全匹配。


terms_set 数组个数匹配

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-terms-set-query.html

terms_set 查询和 terms 查询类似,但是可以指定 term 的匹配个数。

terms_set 查询特别适合做多标签匹配,比如es文档中 tag 是个 keyword 数组,查询参数是 terms 数组,并且需要至少匹配 terms 中的 n 个才召回,此时适合使用 terms_set 查询

参数:
minimum_should_match_field 指定一个 Numeric 类型的字段名,这个字段定义了一个文档需要匹配多少个项才能被认为是匹配的
minimum_should_match_script 脚本指定 terms 需要匹配的个数,source 中可以使用变量 params.num_terms 表示 terms 参数的个数

terms_set 查询有两种用法:

  • 在每个文档上增加一个和 keyword 数组字段搭配使用的 long 类型字段,指定此文档 keyword 数组字段需要匹配的个数,在查询中使用 minimum_should_match_field 参数指定该字段名。
  • 不用提前定义每个文档的匹配个数,只在查询时使用 minimum_should_match_script 脚本指定需要和入参 terms 匹配的个数

minimum_should_match_script

minimum_should_match_script 查询示例

Elasticsearch array property must contain given array items
https://stackoverflow.com/questions/31916523/elasticsearch-array-property-must-contain-given-array-items

1、创建索引,tag 字段存储标签数组

{
  "mappings": {
    "properties": {
      "tag": {
        "type": "keyword"
      }
    }
  }
}

2、写入文档

doc1.tag : ["tag1","tag2"]
doc2.tag : ["tag2","tag3"]
doc3.tag : ["tag3","tag2","tag1"]

3、查询
(1)查询 tags 字段包含全部 ["tag1", "tag2", "tag3"] 的文档,少1个都不行,只召回 doc3

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "3"
        }
      }
    }
  }
}

source 中可以使用变量 params.num_terms 表示 terms 参数的个数,下面的查询中,由于 terms 数组长度为3,所以 params.num_terms=3

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "params.num_terms"
        }
      }
    }
  }
}

(2)tags 字段包含 ["tag1", "tag2", "tag3"] 中任意2个即可匹配,并且 返回结果中doc3相关性得分最高,排在第一位,因为doc3与terms匹配个数最多

{
  "query": {
    "terms_set": {
      "tags": {
        "terms": [ "tag1", "tag2", "tag3" ],
        "minimum_should_match_script": {
          "source": "2"
        }
      }
    }
  }
}

minimum_should_match_field

minimum_should_match_field 查询示例
1、创建索引,required_matches 指定 keyword 数组字段 programming_languages 需要匹配的个数

{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "programming_languages": {
        "type": "keyword"
      },
      "required_matches": {
        "type": "long"
      }
    }
  }
}

2、写入数据

PUT /job-candidates/_doc/1?refresh
{
  "name": "Jane Smith",
  "programming_languages": [ "c++", "java" ],
  "required_matches": 2
}

3、查询
minimum_should_match_field 指定为 required_matches,也就是根据每个文档的 required_matches 字段决定 programming_languages 需要匹配的 terms 个数

GET /job-candidates/_search
{
  "query": {
    "terms_set": {
      "programming_languages": {
        "terms": [ "c++", "java", "php" ],
        "minimum_should_match_field": "required_matches"
      }
    }
  }
}

prefix 前缀匹配

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-prefix-query.html

{
  "query": {
    "prefix": {
      "name": "张"
    }
  },
  "_source": ["_id", "name"]
}

range 范围查询

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html


exists 字段是否存在


wildcard 通配符查询

Elasticsearch Guide [7.17] » Query DSL » Term-level queries » Wildcard query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-wildcard-query.html

* 匹配任何字符序列(包括空字符)
? 匹配任何单个字符

注意:避免使用以 *? 开头的查询串,速度会很慢

例如查询姓张的

GET /_search
{
    "query": {
        "wildcard" : { "name": "张*" }
    }
}

regexp 正则匹配


join 关联查询

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/joining-queries.html

nested 嵌套查询

Nested query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html


脚本查询

Elastic Docs ›Elasticsearch Guide [8.8] ›Query DSL ›Specialized queries
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-query.html

脚本查询通常用在 filter 过滤上下文中,脚本过滤查询的功能也可通过 7.11+ 版本提供的 runtime fields 运行时字段 功能来实现。


特殊查询

script score 脚本评分查询

Elasticsearch Guide [7.17] » Query DSL » Specialized queries » Script score query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html

脚本评分查询(script_score) 可通过入参脚本定制文档的 相关性评分 函数。

例如下面的查询用 my-int 字段除以 10 作为每个文档的相关性得分

GET /_search
{
  "query": {
    "script_score": {
      "query": {
        "match": { "message": "elasticsearch" }
      },
      "script": {
        "source": "doc['my-int'].value / 10 "
      }
    }
  }
}

脚本评分查询的参数:

  • query 查询对象,用于过滤
  • script 脚本对象
  • min_score (可选)最小分数阈值,分数小于此阈值的文档不会返回。
  • boost (可选)如果指定了 boost 参数,最终的得分是脚本计算出的分值乘以 boost,默认值是 1.0

脚本对象

https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html

Elasticsearch 中任何使用脚本的地方都用下面的格式来定义脚本:

"script": {
  "lang":   "...",
  "source" | "id": "...",
  "params": { ... }
}
  • lang 脚本语言,默认是 painless
  • source, id 脚本自身,可以通过 source 参数直接内联写入脚本内容,也可以通过 id 指定提前创建的脚本(通过脚本存储 API 创建脚本)
  • params 指定传入脚本的参数,参数名可自定义,和脚本中对应上即可。

例如:

GET my-index-000001/_search
{
  "script_fields": {
    "my_doubled_field": {
      "script": { 
        "source": "doc['my_field'].value * params['multiplier']", 
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}

Elasticsearch 首次遇到新脚本会编译并缓存脚本,编译脚本是个比较耗时的动作,所以尽量保持 source 中的脚本内容保持不变,将变量放到 param 参数中。
比如上面将倍数 multiplier 改为参数可保证编译的脚本不变,变的只是 multiplier 的值,如果 source 直接写成 "source": "return doc['my_field'].value * 2" 每次改变倍数都会导致重新编译脚本。

创建和存储脚本

https://www.elastic.co/guide/en/elasticsearch/reference/current/create-stored-script-api.html


向量函数

向量函数需要 O(n) 线性扫描匹配的文档,文档量较大时会很慢,建议先通过 query 对文档进行过滤。

cosineSimilarity 余弦相似度

向量字段的类型需设置为 dense_vector,其中 dims 是向量维度

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "my_dense_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "status" : {
        "type" : "keyword"
      }
    }
  }
}

插入数据:

PUT my-index-000001/_doc/1
{
  "my_dense_vector": [0.5, 10, 6],
  "status" : "published"
}

检索,用 status 字段做了筛选,用入参的 query_vector 向量和文档的 my_dense_vector 向量做相似度计算,结果再加 1.0,余弦相似度最小值 0.74,返回 top 10

GET my-index-000001/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "term" : {
              "status" : "published" 
            }
          }
        }
      },
      "min_score": 0.74,
      "boost": 1.0,
      "script": {
        "source": "cosineSimilarity(params.query_vector, 'my_dense_vector') + 1.0", 
        "params": {
          "query_vector": [4, 3.4, -0.2]  
        }
      }
    }
  }
}
dotProduct 点积
l1norm L1距离(曼哈顿距离)
l2norm L2距离(欧氏距离)

聚合查询

指标聚合

value_count 计数

{
    "size":0,
    "aggs": {
        "length_count": {
            "value_count": {
                "field": "length"
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_count": {
            "value": 2639
        }
    }
}

avg 平均值

{
    "size":0,
    "aggs": {
        "avg_length": {
            "avg": {
                "field": "length"
            }
        }
    }
}

结果

{
    "aggregations": {
        "avg_length": {
            "value": 1774.666161424782
        }
    }
}

sum 求和

{
    "size":0,
    "aggs": {
        "sum_length": {
            "sum": {
                "field": "length"
            }
        }
    }
}

结果:

{
    "aggregations": {
        "sum_length": {
            "value": 4683344.0
        }
    }
}

桶聚合

在 ES 中如果没有明确指定指标聚合,默认使用 Value Count 指标聚合,统计桶内文档总数。
默认情况,ES会根据 doc_count 文档总数,降序排序。

https://www.tizi365.com/archives/646.html

terms 分组聚合

{
    "size": 0,  // size=0代表不需要返回query查询结果,仅仅返回aggs统计结果
    "aggs": {
        "agg_groupby": { // 自定义的聚合名字
            "terms": {
                "field": "title.keyword",  // 聚合字段
                "size": 10,
                "order": { "_count": "desc" }
            }
        }
    }
}

结果

{
    "aggregations": {
        "agg_groupby": {  // 和请求中对应的自定义聚合名字
            "doc_count_error_upper_bound": 0,
            "sum_other_doc_count": 2342,
            "buckets": [    // 桶
                {
                    "key": "算法1",  // 桶的标识
                    "doc_count": 112   // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": "数据1",
                    "doc_count": 108
                },
                {
                    "key": "备份1",
                    "doc_count": 77
                }
            ]
        }
    }
}

histogram 直方图

根据数值间隔分组,适合绘制柱状图

{
    "size": 0,
    "aggs": {
        "length_hist": {
            "histogram": {
                "field": "length",
                "interval" : 300   // 分桶间隔
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_hist": {
            "buckets": [
                {
                    "key": 3300.0,   // 桶标识
                    "doc_count": 31  // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": 3600.0,
                    "doc_count": 51
                },
                {
                    "key": 3900.0,
                    "doc_count": 728
                }
            ]
        }
    }
}

date_histogram 日期直方图

用于根据时间、日期分桶的场景

calendar_interval:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)

{
    "size": 0,
    "aggs": {
        "date_hist": {
            "date_histogram": {
                "field": "date",
                "calendar_interval": "year",  // 间隔
                "format" : "yyyy"  // 返回结果中桶key的时间格式
            }
        }
    }
}

结果:

{
    "aggregations": {
        "date_hist": {
            "buckets": [
                {
                    "key_as_string": "2021",  // 桶标识,字符串类型
                    "key": 1609459200000,   // 桶标识,毫秒时间戳
                    "doc_count": 35    // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key_as_string": "2022",
                    "key": 1640995200000,
                    "doc_count": 18
                },
                {
                    "key_as_string": "2023",
                    "key": 1672531200000,
                    "doc_count": 9
                }
            ]
        }
    }
}

range 范围聚合

{
    "size": 0,
    "aggs": {
        "length_range": {
            "range": {
                "field": "length",
                "ranges" : [
                    {"to": 1000},  // <= 1000 的在一个桶
                    {"from": 1000, "to": 3000},  // 1000 到 3000 的在一个桶
                    {"from": 3000} // 3000+ 的在一个桶
                ]
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_range": {
            "buckets": [
                {
                    "key": "*-1000.0",   // 桶标识
                    "to": 1000.0,
                    "doc_count": 1313   // 不指定metric指标,默认使用 value_count 计数
                },
                {
                    "key": "1000.0-3000.0",
                    "from": 1000.0,
                    "to": 3000.0,
                    "doc_count": 481
                },
                {
                    "key": "3000.0-*",
                    "from": 3000.0,
                    "doc_count": 845
                }
            ]
        }
    }
}

为了方便解析返回结果,可以通过 key 指定每个桶的标识

{
    "size": 0,
    "aggs": {
        "length_range": {
            "range": {
                "field": "length",
                "ranges" : [
                    {"key": "short", "to": 1000},
                    {"key": "medium", "from": 1000, "to": 3000},
                    {"key": "long", "from": 3000}
                ]
            }
        }
    }
}

结果:

{
    "aggregations": {
        "length_range": {
            "buckets": [
                {
                    "key": "short",
                    "to": 1000.0,
                    "doc_count": 1313
                },
                {
                    "key": "medium",
                    "from": 1000.0,
                    "to": 3000.0,
                    "doc_count": 481
                },
                {
                    "key": "long",
                    "from": 3000.0,
                    "doc_count": 845
                }
            ]
        }
    }
}

桶聚合+指标查询

桶聚合一般不单独使用,都是配合指标聚合一起使用,对数据分组之后肯定要统计桶内数据。

内置排序参数:

  • _count - 按文档数排序。对 terms, histogram, date_histogram 有效
  • _term - 按词项的字符串值的字母顺序排序。只在 terms 内使用
  • _key - 按每个桶的键值数值排序, 仅对 histogram 和 date_histogram 有效

或者可以按指标排序,order 的 key 是 聚合指标的自定义名字。

terms分组+sum总和倒序

先 terms 按 title.keyword 分桶,计算每个桶内的 length 字段 sum 总和,结果按 sum 倒序

{
  "size": 0,
  "aggs": {
    "group_by_title": {   // 按 title 分桶
      "terms": {
        "field": "title.keyword",
        "size": 3,
        "order": {
          "sum_length": "desc"   // 按嵌套聚合查询 sum_length 倒序排序
        }
      },
      "aggs": {  // 嵌套聚合查询,桶内按 length 求和
        "sum_length": {
          "sum": {
            "field": "length"
          }
        }
      }
    }
  }
}
{
    "aggregations": {
        "group_by_title": {
            "doc_count_error_upper_bound": -1,
            "sum_other_doc_count": 1125,
            "buckets": [
                {
                    "key": "算法1",
                    "doc_count": 3,
                    "sum_length": {
                        "value": 10073.0
                    }
                },
                {
                    "key": "数据1",
                    "doc_count": 3,
                    "sum_length": {
                        "value": 9331.0
                    }
                },
                {
                    "key": "算力1",
                    "doc_count": 2,
                    "sum_length": {
                        "value": 6665.0
                    }
                }
            ]
        }
    }
}

SQL查询

POST _sql?format=txt
{
  "query": "SELECT title, count(*) FROM my_index group by title order by count(*) desc limit 10"
}

sql转dsl

POST /_sql/translate
{
  "query": "sql"
}

https://segmentfault.com/a/1190000038394618


Elasticsearch SQL 客户端

SQL CLI
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/sql-cli.html

/usr/share/elasticsearch/bin 目录中有个jar包 elasticsearch-sql-cli-7.6.1.jar
可直接 java -jar 执行,连接指定的 Elasticsearch,连接后执行 sql,例如:

java -jar elasticsearch-sql-cli-7.6.1.jar http://10.153.106.19:8200
sql> select title, count(*) from my_index group by title order by count(*) desc limit 10;
     title     |   count(*)    
---------------+------------
title1         |7              
title2         |6              

搜索你的数据

Elasticsearch Guide [7.17] » Search your data
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-your-data.html


高亮

Elastic Docs ›Elasticsearch Guide [8.6] ›Search your data > Highlightingedit
https://www.elastic.co/guide/en/elasticsearch/reference/8.6/highlighting.html

fields 指定高亮字段
pre_tags, post_tags 包围高亮的 HTML 标签,默认 pre_tags 是 <em>,默认 post_tags 是 </em>
fragment_size 指定单个高亮片段(fragment)的长度

例1,指定高亮字段 body

GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "fields" : {
      "body" : {}
    }
  }
}

例2,指定高亮字段 body 和 前后标签

GET /_search
{
  "query" : {
    "match": { "user.id": "kimchy" }
  },
  "highlight" : {
    "pre_tags" : ["<tag1>"],
    "post_tags" : ["</tag1>"],
    "fields" : {
      "body" : {}
    }
  }
}

准实时(near-realtime)搜索

Elasticsearch Guide [7.17] » Search your data » Near real-time search
https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html

elastic 底层采用的是 lucene 这个库来实现倒排索引的功能,在 lucene 的概念里每一条记录称为 document(文档),lucene 使用 segment(分段) 来存储数据。Segment 是最小的数据存储单元。其中包含了文档中的词汇字典、词汇字典的倒排索引以及 Document 的字段数据。因此 Segment 直接提供了搜索功能。但是 Segment 能提供搜索的前提是数据必须被提交(Lucene commit),即文档经过一系列的处理之后生成倒排索引等一系列数据。

由于 Lucene commit 非常耗时,es 并不会每收到一条数据就提交一次,而是维护一个 Lucene 内部的 in-memory buffer(内存缓存区),新增一条记录时,es 只是将数据写入内存缓存区,每隔一段时间(默认为1秒),Elasticsearch 会执行一次 refresh 操作:lucene中所有的缓存数据都被写入到一个新的Segment,清空缓存数据。此时数据就可以被搜索。当然,每次执行refresh操作都会生成一个新的Segment文件,这样一来Segment文件有大有小,相当碎片化。Elasticsearch内部会开启一个线程将小的Segment合并(Merge)成大的Segment,减少碎片化,降低文件打开数,提升IO性能。

新增一条记录时,es 会把数据写到 translog 和 in-memory buffer(内存缓存区) 中,数据写入内存缓冲区后并不能立即被搜索到,需要等 refresh 操作(默认1秒一次)执行后刷入 segment 才能被搜索到,所以 es 搜索是 near-realtime 准实时的,而不是实时的。


ES 查询优化

和美大家说 | Elasticsearch之相关性调整实践
https://mp.weixin.qq.com/s/kcCfP8zyGyKiOQdCOvzs_Q


文本分析

POST /_analyze 分析文本

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Analyze API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-analyze.html

对一段文本进行分词

GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze

参数
text 要分析的文本。必填。
field 有 field 时会使用 field 的默认分析器。
analyzer 分析器,可以是内置分析器,也可以是自定义分析器。如果不设置,使用 field 的分析器。如果不指定 field,使用 index 的默认分析器。如果也不指定 index,或者 index 没有默认分析器,使用 standard 分析器。
tokenizer 分词器。将 text 转换为 token 的分词器。

不指定额外分词器时,es 使用一个名为 standard 的默认分析器,对于中文,standard 分词器只是将汉字拆分成一个个的汉字。

例1,测试 ik_smart 分析器:

GET /_analyze
{
    "text": "中国的",
    "analyzer": "ik_smart"
}

返回分词结果

{
  "tokens": [
    {
      "token": "中国",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "的",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_CHAR",
      "position": 1
    }
  ]
}

例2,测试 分词器 和 词过滤器

POST _analyze
{
  "tokenizer": "standard",
  "filter":  [ "lowercase", "asciifolding" ],
  "text":      "Is this déja vu?"
}

ik_max_word/ik_smart 最佳实践

ik_max_word 和 ik_smart

  • ik_max_word: 会将文本做最细粒度的拆分,会尽可能多地切分词语,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询。

两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用ik_smart

GET /_analyze
{
    "analyzer": "ik_smart",
  "text" : "怪兽小当家,我是masikkk"
}

分词结果

{
    "tokens": [
        {
            "token": "怪兽",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "小",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "当家",
            "start_offset": 3,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "我",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 3
        },
        {
            "token": "是",
            "start_offset": 7,
            "end_offset": 8,
            "type": "CN_CHAR",
            "position": 4
        },
        {
            "token": "masikkk",
            "start_offset": 8,
            "end_offset": 15,
            "type": "ENGLISH",
            "position": 5
        }
    ]
}

里面有 ik 分词演示
SpringBoot集成Elasticsearch7.4 实战(一)
https://www.jianshu.com/p/1fbfde2aefa5


创建自定义分析器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-custom-analyzer.html

一个分析器 analyzer 包括:

  • 一个分词器(tokenizer)
  • 0个或多个词过滤器(token filters)
  • 0个或多个字符过滤器(character filters)

参数:
type 对于自定义分析器,类型为 custom
tokenizer 分词器,可使用内置分词器,或三方插件分词器(例如ik)
char_filter 字符过滤器
filter 词过滤器


基于ik分词器+同义词+停用词创建自定义分析器

1、关闭索引
2、创建自定义分析器
3、打开索引

如果 ik 的词库发生改变,实测基于 ik 自定义的分析器必须关闭索引、设置分析器、打开索引后,新加的词才能生效

POST /my_index/_close

PUT /my_index/_settings
{
  "analysis": {
    "filter": {
      "my_synonyms": {
        "type": "synonym_graph",
        "synonyms": [
          "项目,案例"
        ]
      },
      "my_stop": {
        "type": "stop",
        "ignore_case": true,
        "stopwords": ["的", "一", "不", "在", "人", "有", "是", "为", "以", "于", "上", "他", "而", "后", "之", "来", "及", "了", "因", "下", "可", "到", "由", "这", "与", "也", "此", "但", "并", "个", "其", "已", "无", "小", "我", "们", "起", "最", "再", "今", "去", "好", "只", "又", "或", "很", "亦", "某", "把", "那", "你", "乃", "它", "吧", "被", "比", "别", "趁", "当", "从", "到", "得", "打", "凡", "儿", "尔", "该", "各", "给", "跟", "和", "何", "还", "即", "几", "既", "看", "据", "距", "靠", "啦", "了", "另", "么", "每", "们", "嘛", "拿", "哪", "那", "您", "凭", "且", "却", "让", "仍", "啥", "如", "若", "使", "谁", "虽", "随", "同", "所", "她", "哇", "嗡", "往", "哪", "些", "向", "沿", "哟", "用", "于", "咱", "则", "怎", "曾", "至", "致", "着", "诸", "自"]
      }
    },
    "analyzer": {
      "ik_max_custom": {
        "type": "custom",
        "tokenizer": "ik_max_word",
        "filter": [
          "my_synonyms", "my_stop"
        ]
      },
      "ik_smart_custom": {
        "type": "custom",
        "tokenizer": "ik_smart",
        "filter": [
          "my_synonyms", "my_stop"
        ]
      }
    }
  }
}

POST /my_index/_open

Tokenizer 分词器

Elasticsearch Guide [7.17] ›Text analysis ›Tokenizer reference
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-tokenizers.html


Token Filter 词过滤器

Elasticsearch Guide [7.17] ›Text analysis ›Token filter reference
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-tokenfilters.html

词过滤器(Token Filter) 对 分词器(tokenizer) 产生的 token流 进行修改(例如变小写 lowercasing)、删除(例如删除停用词 stopwords)、添加(例如同义词 synonyms)。
Elasticsearch 内置了多个词过滤器。


stop 停用词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-stop-tokenfilter.html

停用词也叫停止词,是指文本在被分词之后的词语中包含的没有搜索意义的词,例如 ”的地得“。

停用词过滤器 从分词器产生的 token流 中删除停用词,不指定时,默认会删除下面这些英文停用词:

a, an, and, are, as, at, be, but, by, for, if, in, into, is, it, no, not, of, on, or, such, that, the, their, then, there, these, they, this, to, was, will, with

中文停用词
https://www.ranks.nl/stopwords/chinese-stopwords

参数
stopwords 停用词数组
ignore_case 默认false,是否忽略大小写


synonym 同义词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-tokenfilter.html


synonym_graph 多同义词过滤器

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-graph-tokenfilter.html

synonym_graph 可处理多单词同义词,且仅可在搜索分析器中使用。

创建 synonym_graph 过滤器,指定同义词配置文件 analysis/synonym.txt,然后创建自定义分词器 search_synonyms 指定使用 graph_synonyms 同义词过滤器:

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "search_synonyms": {
            "tokenizer": "whitespace",
            "filter": [ "graph_synonyms" ]
          }
        },
        "filter": {
          "graph_synonyms": {
            "type": "synonym_graph",
            "synonyms_path": "analysis/synonym.txt"
          }
        }
      }
    }
  }
}

analysis/synonym.txt 是相对于 config 目录的文件。

参数:
expand 是否进行同义词扩展,默认 true
lenient 默认false,设为true时可忽略同义词配置文件中的错误

也可以不使用配置文件,直接在请求参数中配置同义词

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "synonym": {
            "type": "synonym_graph",
            "synonyms": [
              "lol, laughing out loud",
              "universe, cosmos"
            ]
          }
        }
      }
    }
  }
}
Elasticsearch 同义词配置格式

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-synonym-graph-tokenfilter.html#_solr_synonyms_2

# => 左边的词替换为所有右边的词
i-pod, i pod => ipod
sea biscuit, sea biscit => seabiscuit

# 逗号分割同义词,expand=true 时逗号分割的词可任意替换
ipod, i-pod, i pod
foozball , foosball
universe , cosmos
lol, laughing out loud

# expand=true 时,等同于 ipod, i-pod, i pod
ipod, i-pod, i pod => ipod, i-pod, i pod

# expand=false 时,等同于 ipod, i-pod, i pod
ipod, i-pod, i pod => ipod

# 相同的左侧 key 映射会自动合并
foo => foo bar
foo => baz
# 等同于:
foo => foo bar, baz

synonym 同义词

一样,却又不同:借助同义词让 Elasticsearch 更加强大
https://www.elastic.co/cn/blog/boosting-the-power-of-elasticsearch-with-synonyms

Elasticsearch:使用同义词 synonyms 来提高搜索效率
https://blog.csdn.net/UbuntuTouch/article/details/108003222

索引时同义词 VS 搜索时同义词

可在索引时使用同义词,也可在搜索时使用。

1、在索引时应用同义词
索引文档时进行一次性同义词替换或扩展,结果将一直保存在搜索索引中。
优势是性能好,因为在索引文档时已经做了同义词扩展,无需再在每次查询时完成一遍扩展过程。
索引时使用同义词有几个劣势:

  • 由于必须对所有同义词进行索引,所以索引规模会变大。
  • 搜索得分(依赖于字词统计数据)可能会受影响,因为同义词也会计算在内,所以不常见单词的统计数据会存在偏差。
  • 除非进行重新索引,否则无法针对既有文档更改同义词规则。

后2点劣势较严重,不建议使用。

2、搜索时使用同义词
劣势是每次查询时都必须执行同义词扩展操作
优势:

  • 索引规模不受影响。
  • 语料库中的字词统计数据保持不变。
  • 如需变更同义词规则,无需对文档进行重新索引。

此外,搜索时扩展同义词还能够允许使用更加复杂的 synonym_graph 词过滤器。synonym_graph 可处理多单词同义词,且仅可在搜索分析器中使用。

创建/更新同义词必须关闭索引

尽管更改同义词规则不需要对文档进行重新索引,但是如要更改的话,必须暂时关闭再重新打开索引。
因为分析器在下列时候才会创建实例:创建索引时,重启节点时,以及重新打开已关闭的索引时。


Token Graph 词图

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/token-graphs.html

分词器将文本转换为 token 流,同时也会记录每个 token 的位置。
position 0, 1, 2 作为顶点,token 作为边(弧),可以创建一个 DAG 图。
例如 quick brown fox:
1 -quick-> 1 -brown-> 2 -fox-> 3


检索性能调优

Elasticsearch Guide [7.17] » How to » Tune for search speed
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

增加系统缓存

Elasticsearch 重度依赖操作系统的文件系统缓存,为了加快检索速度,要保证至少一半可用内存用于系统缓存,以保证 ES 可将热点索引放到物理内存中。
这也是为什么官方建议堆内存不要超过可用内存的一半。

使用更快的硬件

1、保证文件系统缓存足够
2、使用 SSD 磁盘

优化文档模型

避免 nested 嵌套字段,避免 join 关系字段。
nested 字段有几倍的性能损失,join 关系字段会带来几百倍的性能损失。

使用copy_to合并字段

将多个字段的内容 copy_to 到一个字段中,方便搜索

提前计算中间结果

比如经常需要对 price 字段进行聚合查询,聚合为 0-200,200-500,500+,可以在插入文档时就计算好这个聚合分类

使用keyword代替数字类型

ES 对 integer, long 等数字类型做了优化更适合 range 查询,但 keyword 类型适合 term 级别查询。
并不是所有的数字类型数据都要存储为数字类型(integer, long等),例如数字类型的 ISBN 号、产品ID等,很少会用做范围 range 查询,更多的是 term 查询,所以更适合保存为 keyword 类型。

避免脚本搜索

强制合并只读索引

将只读索引强制合并为单个 Segment 可加快查询。对于 log 等基于时间写入的索引尤其适用,只保留当天的日志可写,将之前的日志合并为一个索引。

预热全局序号

全局序号用于加速聚合查询,是 fielddata 缓存结构的一部分,位于 JVM 堆内存中,在查询时触发。
对于常用的聚合字段,告诉 ES 提前预热该字段的全局序号缓存,可加快聚合查询速度。

预热文件系统缓存

机器重启后操作系统文件缓存会丢失,随着查询请求才会逐渐将索引中的热区域加载到系统缓存中。
对于常用的索引,配置到 "index.store.preload": ["index1", "index2"],以便启动时自动预热数据

冷->热->缓存

冷数据:查询条件term的索引文件还未加载系统缓存,读磁盘
热数据:查询条件term的索引文件已加载到系统缓存,使用同条件查询一次后触发,读内存
缓存后:用完全相同的查询条件再次查询,结果是完全缓存在内存的

索引数据排序

Elasticsearch 从 6.0 版本开始引入了一个新的特征,叫 Index Sorting(索引排序)。用户可以将索引数据按照指定的顺序存储在硬盘上,这样在搜索排序取前 N 条时,不需要访问所有的匹配中的记录再进行排序,只需要访问前 N 条记录即可。


上一篇 Elasticsearch-集群及运维

下一篇 Elasticsearch-索引

阅读
评论
18.5k
阅读预计82分钟
创建日期 2024-01-02
修改日期 2024-06-13
类别
目录
  1. Search 搜索 API
    1. GET /index/_search 搜索
      1. track_total_hits 命中个数统计
      2. _source 指定返回字段
      3. q查询(URI搜索/lucene语法查询)
      4. highlight 高亮搜索
      5. sort 排序
        1. 默认按相关性 _score 倒序
        2. track_scores 始终计算得分
        3. 多字段排序
        4. 多值字段排序(mode:min/max/avg/sum)
        5. 值缺失时的处理(missing)
        6. 排序字段的返回值(format)
        7. 嵌套字段的排序
      6. 分页
        1. from/size 分页
        2. 分布式系统中的深度分页问题
        3. scroll分页(缓存快照,无法跳页,不再推荐)
        4. search_after(实时分页,无法跳页,推荐)
    2. GET /index/_count 条件计数
    3. PIT 时间点
      1. POST /index/_pit 时间点
      2. PIT 时间点会消耗磁盘和内存资源
      3. DELETE /_pit 删除时间点
      4. slice 搜索分片
  2. Query DSL
    1. 查询(query)和过滤(filter)上下文
      1. 查询(query)上下文
      2. 过滤(filter)上下文
      3. query和filter的区别
  3. 复合查询(Compound Query)
    1. bool 组合查询
    2. boosting 查询
    3. dis_max 分离最大化查询
  4. match 全文查询
    1. match 分词查询
      1. match 查询的参数
    2. match_phrase 短语查询(词序列匹配)
    3. match_phrase_prefix 短语前缀查询
    4. combined_fields 联合字段查询(7.11+)
      1. combined_fields 和 multi_match 的区别
    5. multi_match 多字段查询
      1. 指定每个字段的评分权重(boost)
      2. multi_match 的类型
        1. best_fields 最佳匹配(默认)
        2. most_fields 多字段匹配
        3. cross_fields 跨字段匹配
      3. field-centric 和 term-centric
        1. field-centric 以字段为中心
        2. term-centric 以词为中心
    6. query_string 查询(类Lucene语法查询)
    7. simple_query_string 简化lucene查询
  5. Term 级查询
    1. term 等值匹配
    2. terms In多值匹配
    3. terms_set 数组个数匹配
      1. minimum_should_match_script
      2. minimum_should_match_field
    4. prefix 前缀匹配
    5. range 范围查询
    6. exists 字段是否存在
    7. wildcard 通配符查询
    8. regexp 正则匹配
  6. join 关联查询
    1. nested 嵌套查询
    2. 脚本查询
  7. 特殊查询
    1. script score 脚本评分查询
      1. 脚本对象
      2. 创建和存储脚本
      3. 向量函数
        1. cosineSimilarity 余弦相似度
        2. dotProduct 点积
        3. l1norm L1距离(曼哈顿距离)
        4. l2norm L2距离(欧氏距离)
  8. 聚合查询
    1. 指标聚合
      1. value_count 计数
      2. avg 平均值
      3. sum 求和
    2. 桶聚合
      1. terms 分组聚合
      2. histogram 直方图
      3. date_histogram 日期直方图
      4. range 范围聚合
    3. 桶聚合+指标查询
      1. terms分组+sum总和倒序
  9. SQL查询
    1. Elasticsearch SQL 客户端
  10. 搜索你的数据
    1. 高亮
    2. 准实时(near-realtime)搜索
    3. ES 查询优化
  11. 文本分析
    1. POST /_analyze 分析文本
    2. ik_max_word/ik_smart 最佳实践
    3. 创建自定义分析器
      1. 基于ik分词器+同义词+停用词创建自定义分析器
    4. Tokenizer 分词器
    5. Token Filter 词过滤器
      1. stop 停用词过滤器
      2. synonym 同义词过滤器
      3. synonym_graph 多同义词过滤器
        1. Elasticsearch 同义词配置格式
    6. synonym 同义词
      1. 索引时同义词 VS 搜索时同义词
      2. 创建/更新同义词必须关闭索引
    7. Token Graph 词图
  12. 检索性能调优
    1. 增加系统缓存
    2. 使用更快的硬件
    3. 优化文档模型
    4. 使用copy_to合并字段
    5. 提前计算中间结果
    6. 使用keyword代替数字类型
    7. 避免脚本搜索
    8. 强制合并只读索引
    9. 预热全局序号
    10. 预热文件系统缓存
    11. 冷->热->缓存
    12. 索引数据排序

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论