ElasticSearch第二弹——DSL查询7

发布于:2025-02-28 ⋅ 阅读:(8) ⋅ 点赞:(0)

DSL查询

快速入门 

GET /items/_search
{
  "query":{
    "match_all":{
    }
  }
}

叶子查询

 match_query和multi_match_query的区别

  • match精准定位——只在 一个字段 里搜索(比如只在“书名”里找关键词)。

  • multi_match广撒网——同时在 多个字段 里搜索(比如同时在“书名”和“简介”里找关键词)。


生活化例子

假设你有一堆书籍信息(书名、简介、作者),现在想找和“冒险”相关的书:

  1. 用 match

    • 你明确知道“冒险”这个词只会在 书名 里出现,于是直接搜索书名字段。

    • 结果:只返回书名中包含“冒险”的书籍(比如《冒险岛》)。

  2. 用 multi_match

    • 你不确定“冒险”出现在书名还是简介里,于是同时搜索书名和简介两个字段。

    • 结果:返回书名或简介中包含“冒险”的书籍(比如《神秘岛》的书名没有“冒险”,但简介里有“这是一场惊险的冒险旅程”)。


精确查询 

ES当中term查询的注意事项:

在 Elasticsearch(ES)中,term 查询是一种精确匹配查询,主要用于匹配不分词的字段或字段中的确切值。使用 term 查询时需要注意以下几点:

1. term 查询不会进行分词

  • term 查询适用于 keyword 类型的字段,而不适用于 text 类型的字段
  • text 类型的字段会在索引时被分词,而 term 查询不会对查询值进行分词,因此可能得不到预期的结果。
  • 如果 titletext 类型,ES 可能存储的是 ["elasticsearch"],但 term 查询不会对 "Elasticsearch" 进行分词,所以可能查不到结果。

    解决方案:

    对于 text 类型字段,使用 match 查询

查询值必须与索引中的值完全匹配

term 查询不会进行模糊匹配,查询的值必须与存储的值完全一致,包括大小写和空格。

适用于 keywordnumericbooleandate 类型的字段

term 查询不仅适用于 keyword 类型字段,还适用于 integerbooleandate 等非分词字段。

# term所有
GET /items/_search
{
  "query": {
    "match": {
      "name": "拉杆箱"
    }
  }
}
# range所有
GET /items/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 20000,
        "lte": 50000
      }
    }
  }
}

 

复合查询 (bool查询)

bool查询是什么?

bool 查询(Boolean Query)是 Elasticsearch(ES)提供的一种复合查询类型,用于组合多个查询条件,类似于 SQL 的 ANDORNOT 逻辑操作。它允许你灵活地构造复杂的查询,以实现更精确的搜索结果。

bool 查询的基本结构

bool 查询由多个子查询组成,每个子查询有不同的作用:

  • must:必须匹配(类似 SQL AND
  • should:可以匹配(类似 SQL OR,但影响相关性评分)
  • must_not:不能匹配(类似 SQL NOT
  • filter:必须匹配,但不会影响相关性评分(更快)

基本结构当中例如must里面需要和叶子查询进行组合 

bool 子句 作用 影响相关性
must 必须匹配,相当于 SQL AND 影响
should 可以匹配,相当于 SQL OR,但提高相关性 影响
must_not 不能匹配,相当于 SQL NOT 不影响
filter 必须匹配,但不影响评分,性能更好 不影响

排序和分页

 

在 Elasticsearch 这样的分布式搜索引擎中,数据是 分片(shard)存储 的,每个分片可能会独立返回自己的部分数据。这就导致:

  1. 分页偏移(offset)问题:使用传统 from + size 方式时,数据可能在不同分片间重新分配,导致数据不一致或遗漏。
  2. 性能问题:深度分页会导致 from 值过大,导致查询的计算开销巨大,影响性能。

search_after 的工作原理

(1)基本思想

  • search_after 不使用偏移量,而是基于 排序字段 进行分页。
  • 每次查询都使用 上一页的最后一条数据的排序值 作为起点,从而保证数据不会重复或遗漏。

 举例:

假设数据如下

我们仍然按照 时间进行 升序排序,并且 每个分片有 3 条数据

Shard 1: [ A (12:00), D (12:03), G (12:06) ]
Shard 2: [ B (12:01), E (12:04), H (12:07) ]
Shard 3: [ C (12:02), F (12:05), I (12:08) ]

第一次查询

每个分片先执行查询

每个分片会 取出自己的数据 并排序

然后,Elasticsearch 全局排序,取前 5 个:

A (12:00)
B (12:01)
C (12:02)
D (12:03)
E (12:04)

最后一个数据 E (12:04)这次查询的最后一条数据。 

为了获取 下一页数据,我们使用 search_after,以 E (12:04) 作为 分页锚点

然后,每个分片查找 timestamp > 12:04 的数据

Shard 1: [ G (12:06) ]
Shard 2: [ H (12:07) ]
Shard 3: [ F (12:05), I (12:08) ]

全局排序后: 

F (12:05)
G (12:06)
H (12:07)
I (12:08)

第一次查询 时,Elasticsearch 会让每个分片各自执行查询,各自查询 size 条数据然后把每个分片的结果汇总并 进行全局排序,最后取前 size 条数据返回。

 第二次查询 时,每个分片会根据 search_after 提供的 上一次查询的最后一条数据的排序值,查询 比这个值大的数据,然后 再汇总、全局排序、返回前 size

你可以理解为:

  1. ES不会逐个去分片找数据,而是让所有分片同时查找数据。
  2. 所有分片查出来的结果放在一起,进行全局排序,选出最前面的 size 条数据。
  3. 下一次查询时,每个分片都要找到比上次最后一条数据更大/更小的数据,然后再全局排序。
  4. 这个过程会不断重复,直到查不出数据为止。

你可以类比成:

每个分片都是一个独立的小仓库,ES会让它们都拿出一部分数据来,再从所有仓库的数据中挑选出排序最靠前的 size 条。然后下一次查询时,每个仓库都会找比上次最后一个更大/更小的数据,继续挑选、排序、返回。

传统分页:from + size

 查询方式

  • 例如:GET /products/_search?from=10000&size=10
  • 直接跳过 前 10000 条数据,取 第 10001 ~ 10010 条数据

 问题

  • 性能问题
    • ES 仍然需要从索引中取出 10010 条数据,然后 丢弃前 10000 条,只返回最后 10 条。
    • from 越大,查询越慢,因为它必须先加载所有数据,再做排序。

search_after

 查询方式

  • 通过 上一次查询的最后一个排序值 作为起点,查询下一页数据。
  • 例如:
    • 第 1 页返回的最后一个数据的 _sort 值是 [1627890321000, "ABCD"]
    • 第 2 页查询时,使用 search_after: [1627890321000, "ABCD"]
    • 只返回 排序值大于 [1627890321000, "ABCD"] 的数据,不用再扫描前面的数据。

 优势 查询效率更高

  • 不会跳过数据,直接从上次的 最后一条开始,减少 ES 计算负担。
  • 适用于 深度分页(如 from > 10000)
对比项 from + size 传统分页 search_after 深度分页
查询方式 跳过 from 条数据,取 size 直接从上次 最后一个排序值 开始
查询效率 from 越大,查询越慢 速度快,避免跳过数据
排序稳定性 数据更新时可能会重复或漏掉 不会丢数据,排序稳定
适用场景 适合 小数据分页(如前 1000 条) 深度分页(10000+ 条)、流式数据

高亮显示 

总结

数据聚合  

javaRestClient查询API

快速入门 

 @Test
    void testMatchAll() throws IOException {
        // 准备Request
        SearchRequest request = new SearchRequest("items");
        // 准备请求参数
        request.source().query(QueryBuilders.matchAllQuery());
        // 发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
//        System.out.println("response = " + search);
        // 解析结果
        parseResponse(search);
    }

 解析查询结果:

  private static void parseResponse(SearchResponse search) {
        // 获取搜索结果的所有匹配条目
        SearchHits hits = search.getHits();

        // 获取总的匹配条数
        long total = hits.getTotalHits().value;
        System.out.println("total = " + total);

        // 获取每一条搜索结果
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit hit : searchHits) {
            // 获取当前搜索结果的 JSON 字符串
            String json = hit.getSourceAsString();

            // 将 JSON 字符串转化为 Java Bean(假设 ItemDoc 是你的文档类)
            ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
            System.out.println("doc = " + doc); // 打印出文档信息(包含高亮的名称)
        }
  }

叶子查询

全文检索

精确查询 

复合查询(bool查询) 

@Test
    void testSearch() throws IOException {
        // 准备Request
        SearchRequest request = new SearchRequest("items");
        // 准备请求参数
        request.source().query(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("name", "进口牛奶")).filter(QueryBuilders.matchQuery("brand", "德亚")).filter(QueryBuilders.rangeQuery("price").gte(10000).lte(100000)));
        // 发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
//        System.out.println("response = " + search);
        // 解析结果
        parseResponse(search);
    }

排序和分页

    @Test
    void testSortAndPage() throws IOException {
        // 模拟前端传递的数据
        int pageNo = 1, pageSize = 5;
        // 准备Request
        SearchRequest request = new SearchRequest("items");
        // 准备请求参数
        request.source().query(QueryBuilders.matchAllQuery());
        request.source().from((pageNo - 1) * pageSize).size(pageSize);
        request.source().sort("price", SortOrder.ASC);
        request.source().sort("sold", SortOrder.DESC);
        // 发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
//        System.out.println("response = " + search);
        // 解析结果
        parseResponse(search);
    }

高亮显示

@Test
    void testHighLight() throws IOException {

        // 准备Request
        SearchRequest request = new SearchRequest("items");
        // 准备请求参数
        request.source().query(QueryBuilders.matchQuery("name", "拉杆箱旅行"));
        // 高亮条件
        request.source().highlighter(SearchSourceBuilder.highlight().field("name"));
        // 发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
//        System.out.println("response = " + search);
        // 解析结果
        parseResponse(search);

    }

数据聚合 

    @Test
    void testAgg() throws IOException {
        // 准备Request
        SearchRequest request = new SearchRequest("items");
        // 准备请求参数
        // 分页
        request.source().size(0);
        // 聚合条件
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand")).size(5);
        // 发送请求
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
        System.out.println("response = " + search);
        // 解析结果
        Aggregations aggregations = search.getAggregations();
        // 根据聚合名称获取聚合结果
        Terms brandTerms = aggregations.get("brandAgg");
        // 获取buckets
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            // 遍历获取每个bucket
            System.out.println("brand "+bucket.getKeyAsString());
            System.out.println("count "+bucket.getDocCount());
        }
    }

Elasticsearch Java API 操作索引库 & 文档 请求方式总结

操作类型 API 方法 发送请求方法 返回类型
索引库管理 CreateIndexRequest client.indices().create(request, RequestOptions.DEFAULT); CreateIndexResponse
删除索引 DeleteIndexRequest client.indices().delete(request, RequestOptions.DEFAULT); AcknowledgedResponse
检查索引 GetIndexRequest client.indices().exists(request, RequestOptions.DEFAULT); boolean
新增/更新文档 IndexRequest client.index(request, RequestOptions.DEFAULT); IndexResponse
批量新增 BulkRequest + add(IndexRequest) client.bulk(request, RequestOptions.DEFAULT); BulkResponse
查询文档 GetRequest client.get(request, RequestOptions.DEFAULT); GetResponse
删除文档 DeleteRequest client.delete(request, RequestOptions.DEFAULT); DeleteResponse
批量删除 BulkRequest + add(DeleteRequest) client.bulk(request, RequestOptions.DEFAULT); BulkResponse
更新文档 UpdateRequest client.update(request, RequestOptions.DEFAULT); UpdateResponse