本篇文章会深入 ElasticSearch 中的查询功能,包括 Term 查询、全文查询、搜索相关性和多字段查询等,都含有 Demo 方便大家测试。
Term 搜索与全文搜索
对比维度 | Term 搜索 | 全文搜索 |
---|---|---|
查询类型 | 精确匹配 | 模糊匹配 |
分析过程 | 不分词,直接匹配索引中的词项(Term) | 分词处理,匹配词条(Token) |
适用字段类型 | keyword、数字、日期等精确值字段 | text 类型字段 |
使用场景 | 过滤(Filter)、聚合(Aggregation) | 自由文本搜索(如搜索框输入) |
性能特点 | 高效,适合大数据集和实时过滤 | 相对耗时,依赖分词和相关性计算 |
查询语法 | {"term": {"field": "value"}} | {"match": {"field": "user_input"}} |
倒排索引使用 | 直接定位词项的文档列表 | 通过词条组合计算相关性得分 |
Term 搜索
示例
1、插入数据
POST /products/_bulk
{ "index": { "_id": 1 }}
{ "productID" : "XHDK-A-1293-#fJ3","desc":"iPhone" }
{ "index": { "_id": 2 }}
{ "productID" : "KDKE-B-9947-#kL5","desc":"iPad" }
{ "index": { "_id": 3 }}
{ "productID" : "JODL-X-1937-#pV7","desc":"MBP" }
2、Term 查询
POST /products/_search
{
"query": {
"term": {
"desc": {
// "value": "iPhone"
// "value": "iphone"
}
}
}
}
使用 iPhone
不能搜索出结果,而小写的 iphone
可以。因为是精确查询,而原始数据在分词后的结果为 iphone
。
POST /products/_search
{
"query": {
"term": {
"desc.keyword": {
// "value": "iPhone"
// "value": "iphone"
}
}
}
}
此时使用 keyword 类型能成功获取数据。
3、Constant Score 转为 Filter
POST /products/_search
{
"explain": true,
"query": {
"constant_score": {
"filter": {
"term": {
"productID.keyword": "XHDK-A-1293-#fJ3"
}
}
}
}
}
- 将 Query 转为 Filter,忽略 TF-IDF 计算,避免相关性算分的开销。
- Filter 可以有效利用缓存。
全文搜索
Match / Match Phrase / Query String
- 索引和搜索时都会进行分词,查询字符串先传递到一个合适的分词器,然后生成一个供查询的列表。
- 查询会对每个词项逐个查询再将结果进行合并,并为每个文档生成一个算分。
场景 | 推荐查询类型 |
---|---|
普通全文搜索 | match |
精确短语匹配 | match_phrase |
高级搜索(用户输入带逻辑) | query_string |
用户输入框 + 安全性优先 | match / multi_match ,避免 query_string |
结构化搜索
结构化数据是指具有固定格式和明确字段的数据,每个字段都有特定的类型(如字符串、数字、日期等),并且数据是可预测、易于解析的。结构化搜索即对结构化数据进行搜索。
示例
1、插入数据
DELETE products
POST /products/_bulk
{ "index": { "_id": 1 }}
{ "price" : 10,"avaliable":true,"date":"2018-01-01", "productID" : "XHDK-A-1293-#fJ3" }
{ "index": { "_id": 2 }}
{ "price" : 20,"avaliable":true,"date":"2019-01-01", "productID" : "KDKE-B-9947-#kL5" }
{ "index": { "_id": 3 }}
{ "price" : 30,"avaliable":true, "productID" : "JODL-X-1937-#pV7" }
{ "index": { "_id": 4 }}
{ "price" : 30,"avaliable":false, "productID" : "QQPX-R-3956-#aD8" }
2、Bool
POST products/_search
{
"query": {
"constant_score": {
"filter": {
"term": {
"avaliable": true
}
}
}
}
}
3、数字
POST products/_search
{
"query": {
"term": {
"price": 30
}
}
}
4、Range
POST products/_search
{
"query" : {
"constant_score" : {
"filter" : {
"range" : {
"price" : {
"gte" : 20,
"lte" : 30
}
}
}
}
}
}
搜索相关性
- 搜索的相关性算分,描述了一个文档和查询语句匹配的程度。ES 会对每个匹配查询条件的结果进行算分 _score。
- 打分的本质是排序,需要把最符合用户需求的文档排在前面。ES5 之前,默认的相关性算分采用 TF-IDF,现在采用 BM 25。
词频 TF
- TF 即是词在一篇文档中出现的频率。
- 度量一条查询和结果文档相关性的简单方法:将搜索中的每一个词 TF 相加。
- Stop Word 不应考虑,类似 「the」、「的」。
逆文档频率 IDF
- DF:检索词在所有文档中出现的评率。
- Inverse Document Frequency:
- TF-IDF 的本质就是将 TF 求和变成了加权求和:
BM 25
与 TF-IDF 相比,当一个词的 TF 无限增加时,BM 25 算分会趋于一个稳定值。
Boosting
Boosting 是控制相关度的一种手段。
- 当 boost > 1 时打分的相关度相对性提升。
- 当 0 < boost < 1 时打分的权重相对性降低。
- 当 boost < 0 时,贡献负分。
多字段多字符串查询
bool 查询
一个 bool 查询是一个或者多个查询子句的组合。
子句 | 描述 |
---|---|
must | 必须匹配,贡献算分。 |
should | 选择性匹配,贡献算分。 |
must_not | Filter Context 查询字句,必须不能匹配。 |
filter | Filter Context 必须匹配,不贡献算分。 |
示例 |
1、bool 查询
POST /products/_search
{
"query": {
"bool" : {
"must" : {
"term" : { "price" : "30" }
},
"filter": {
"term" : { "avaliable" : "true" }
},
"must_not" : {
"range" : {
"price" : { "lte" : 10 }
}
},
"should" : [
{ "term" : { "productID.keyword" : "JODL-X-1937-#pV7" } },
{ "term" : { "productID.keyword" : "XHDK-A-1293-#fJ3" } }
],
"minimum_should_match" :1
}
}
}
- 子查询可以任意顺序出现。
- 可以嵌套多个查询。
- 如果 bool 查询中没有 must 条件,那么 should 中必须至少满足一条查询。
2、boost 控制查询分数
POST /news/_bulk
{ "index": { "_id": 1 }}
{ "content":"Apple Mac" }
{ "index": { "_id": 2 }}
{ "content":"Apple iPad" }
{ "index": { "_id": 3 }}
{ "content":"Apple employee like Apple Pie and Apple Juice" }
POST news/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"content": "apple"
}
},
"negative": {
"match": {
"content": "pie"
}
},
"negative_boost": 0.5
}
}
}
此时会将苹果产品放前边,而 id 为 3 的显示在最后。
多字段单字符串查询
Disjunction Max Query
将任何与任一查询匹配的文档作为结果返回。采用字段上最匹配的评分最终评分返回。
示例
1、插入数据
PUT /blogs/_doc/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /blogs/_doc/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
2、bool 测试
POST /blogs/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
结果可以看到 1 号文档在前,是因为 bool 会对两个进行加和平均,不符合直觉。
2、dis_max 测试
POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
此时 2 号文档在前,符合直觉。
POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
]
}
}
}
结果还是 1 号文档在前,同时分数相同。因为最高分数的 quick 都只出现一次。但 2 号文档中有 pet ,直觉上应该 2 号文档更高,但 dis_max 只取最大的一条。
POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.2
}
}
}
加入 tie_breaker 后会把最匹配一条之外的分数与其值相乘,这样 2 号文档就能更高,符合直觉。
MultiMatch
multi_match
是 Elasticsearch 中一种用于在多个字段中执行全文搜索的查询方式。它扩展了 match
查询,允许你在多个字段上同时进行匹配。
类型 | 描述 | 使用场景 |
---|---|---|
best_fields | 匹配最佳字段(默认) | 标题或正文关键词搜索 |
most_fields | 多字段尽量都匹配 | 多语言字段匹配 |
cross_fields | 字段合并为整体匹配 | 姓名拆分(first_name + last_name) |
phrase | 短语匹配 | 查找完整短语 |
phrase_prefix | 短语前缀匹配 | 自动补全 |
bool_prefix | 前缀词项布尔匹配 | 高效前缀搜索 |
示例
1、插入数据
PUT /titles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "english",
"fields": {"std": {"type": "text","analyzer": "standard"}}
}
}
}
}
POST titles/_bulk
{ "index": { "_id": 1 }}
{ "title": "My dog barks" }
{ "index": { "_id": 2 }}
{ "title": "I see a lot of barking dogs on the road " }
2、使用 most_fields
GET /titles/_search
{
"query": {
"multi_match": {
"query": "barking dogs",
"type": "most_fields",
"fields": [ "title", "title.std" ]
}
}
}
结果显示 2 号文档在前,符合直觉。若不使用 MultiMatch 则会 1 号文档在前,因为 English 分词器会把 barking dogs 分为 bark 和 dog,1 号文档分数更高。
写在最后
这是该系列的第五篇,主要讲解 ElasticSearch 中的查询功能,包括 Term 查询、全文查询、搜索相关性和多字段查询等,可以自己去到 Kibana 的 Dev Tool 实战操作,未来会持续更新该系列,欢迎关注👏🏻。
同时欢迎关注公众号:LanTech指南。不定时分享职场思考、独立开发日志、大厂方法论和后端经验❤️