DSL查询
快速入门
GET /items/_search
{
"query":{
"match_all":{
}
}
}
叶子查询
match_query和multi_match_query的区别
match
:精准定位——只在 一个字段 里搜索(比如只在“书名”里找关键词)。multi_match
:广撒网——同时在 多个字段 里搜索(比如同时在“书名”和“简介”里找关键词)。
生活化例子:
假设你有一堆书籍信息(书名、简介、作者),现在想找和“冒险”相关的书:
用
match
:你明确知道“冒险”这个词只会在 书名 里出现,于是直接搜索书名字段。
结果:只返回书名中包含“冒险”的书籍(比如《冒险岛》)。
用
multi_match
:你不确定“冒险”出现在书名还是简介里,于是同时搜索书名和简介两个字段。
结果:返回书名或简介中包含“冒险”的书籍(比如《神秘岛》的书名没有“冒险”,但简介里有“这是一场惊险的冒险旅程”)。
精确查询
ES当中term查询的注意事项:
在 Elasticsearch(ES)中,term
查询是一种精确匹配查询,主要用于匹配不分词的字段或字段中的确切值。使用 term
查询时需要注意以下几点:
1. term
查询不会进行分词
term
查询适用于keyword
类型的字段,而不适用于text
类型的字段。text
类型的字段会在索引时被分词,而term
查询不会对查询值进行分词,因此可能得不到预期的结果。如果
title
是text
类型,ES 可能存储的是["elasticsearch"]
,但term
查询不会对"Elasticsearch"
进行分词,所以可能查不到结果。解决方案:
对于text
类型字段,使用match
查询
查询值必须与索引中的值完全匹配
term
查询不会进行模糊匹配,查询的值必须与存储的值完全一致,包括大小写和空格。
适用于 keyword
、numeric
、boolean
、date
类型的字段
term
查询不仅适用于 keyword
类型字段,还适用于 integer
、boolean
、date
等非分词字段。
# 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 的 AND
、OR
、NOT
逻辑操作。它允许你灵活地构造复杂的查询,以实现更精确的搜索结果。
bool
查询的基本结构
bool
查询由多个子查询组成,每个子查询有不同的作用:
must
:必须匹配(类似 SQLAND
)should
:可以匹配(类似 SQLOR
,但影响相关性评分)must_not
:不能匹配(类似 SQLNOT
)filter
:必须匹配,但不会影响相关性评分(更快)
基本结构当中例如must里面需要和叶子查询进行组合
bool 子句 |
作用 | 影响相关性 |
---|---|---|
must |
必须匹配,相当于 SQL AND |
影响 |
should |
可以匹配,相当于 SQL OR ,但提高相关性 |
影响 |
must_not |
不能匹配,相当于 SQL NOT |
不影响 |
filter |
必须匹配,但不影响评分,性能更好 | 不影响 |
排序和分页
在 Elasticsearch 这样的分布式搜索引擎中,数据是 分片(shard)存储 的,每个分片可能会独立返回自己的部分数据。这就导致:
- 分页偏移(offset)问题:使用传统
from + size
方式时,数据可能在不同分片间重新分配,导致数据不一致或遗漏。 - 性能问题:深度分页会导致
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
条。
你可以理解为:
- ES不会逐个去分片找数据,而是让所有分片同时查找数据。
- 所有分片查出来的结果放在一起,进行全局排序,选出最前面的
size
条数据。 - 下一次查询时,每个分片都要找到比上次最后一条数据更大/更小的数据,然后再全局排序。
- 这个过程会不断重复,直到查不出数据为止。
你可以类比成:
每个分片都是一个独立的小仓库,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"]
的数据,不用再扫描前面的数据。
- 第 1 页返回的最后一个数据的
优势 查询效率更高
- 不会跳过数据,直接从上次的 最后一条开始,减少 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 |