ES 深入理解搜索
1.基于Term的查询
Term是表达语义的最小单位,搜索和自然语言处理都用Term处理。
- Term级别的查询:Term查询、范围查询、存在性查询、前缀查询、通配符查询。
- 在ES里面对于Term查询,输入不做分词。将输入当成一个整体,在倒排索引里面查找准确的词。然后根据公式对文档进行算分。
- 可以通过Constant Score,避免算分,并利用缓存提高性能。
案例
POST /products/_bulk
{"index":{"_id":1}}
{"product_id":"ABC-DEF","desc":"iPhone"}
{"index":{"_id":2}}
{"product_id":"ABC-bba","desc":"iPad"}
POST /products/_search
{
"query": {
"term": {
"desc": {
"value": "iPhone"//查不到,"iphone"可以,
}
}
}
}
POST /products/_search
{
"query": {
"term": {
"product_id.keyword": {
"value": "ABC-bba"
}
}
}
}
忽略计算评分
{
"query": {
"constant_score": {
"filter": {
"term": {
"product_id.keyword": "ABC-bba"
}
}
}
}
}
2.基于全文的查询
可以通过match、match phrase、query string这些类的查询实现。
特点:
- 索引和搜索时都会进行分词,查询字符串先传递一个合适的分词器,然后生成可供查询的列表。
- 查询,先对输入分词,对每个单词逐个底层查询,最终合并结果,并计算分数。
详细内容见:https://www.jianshu.com/p/8e70f80cca80
3.结构化搜索
3.1 结构化数据
- 布尔类型和数字类型是结构化的
- 文本也可以是结构化的
- 彩色笔可以有离散的颜色结合。
- 博客可能被标记了标签。
- 电商网站上的商品的标识符,严格规定结构化的格式
3.2 ES的结构化搜索
- 布尔、时间、日期或者数字这类结构化数据:有精确的格式,可以进行逻辑操作。包括比较数字或者范围比较,判断两个值大小。
- 结构化的文本可以做精确匹配或者部分匹配:Term查询/Prefix前缀查询。
- 结构化结果只有是或者否两个值,可以根据场景需要决定是否打分。
范围查询案例:
POST products/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"price": {
"gte": 10,
"lte": 20
}
}
},
"boost": 1.2
}
}
}
4.相关性算分
搜索的相关性算法,描述了一个文档和查询语句的匹配程度,ES会对每个匹配的查询条件做score,ES5以前用TF-IDF算法,现在用BM25/
一些基本概念:
词频(IF):检索词在一片文档中出现的频率。检索词出现的次数/文档总字数。可以把各个词频(除去没有用的词)想加得到一个评分。
逆文档频率(IDF):log(全部文档数/检索词出现过的文档总数)
TF-IDF:本质就是将TF加权求和,TF(区块链)IDF(区块链)+TF(的)IDF(的)+TF(应用)*IDF(应用)
4.1通过Boosting控制相关度
- 当boost > 1时,打分的相关度相对性提升
- 当 0 < boost < 1,打分的权重相对性降低
- 当boost <0,贡献负分
POST score_test/_search
{
"query": {
"boosting": {
"positive": {
"term": {
"content": "live"
}
},
"negative":{
"term":{
"content":"he"
}
},
"negative_boost":0.3
}
}
}
匹配带live的,如果content字段里面包含了he,减分0.3
5. Query & Filter 多字段查询
5.1 bool查询
应用于多个字段,多个条件的符合查询。
- 一个bool查询是一个或者多个查询的组合。
- 总共包括4中子句,其中两种会影响算分,两种不影响分数。
- 多个子句被合并为复合语句的时候,比如bool查询,每个语句的评分都会合并到总评分。
- 同一个层级的条件具有相同的权重,可以调整嵌套结构控制算分
字段值 | 说明 |
---|---|
must | 必须匹配,贡献算分 |
should | 选择性匹配,贡献算分 |
must_not | filter context查询子句,必须不能匹配,不算评分 |
filter | filter context 必须匹配,但是不贡献算分。 |
- 子查询可以任意顺序出现
- 可以嵌套多个查询
- 如果没有must条件,should必须至少满足一条查询
案例
GET movies/_search
{
"size": 20,
"query": {
"bool": {
"must": [
{"term": {
"title": {
"value": "bob"
}
}}
],
"filter": [
{
"range": {
"year": {
"gte": 1991,
"lte": 1994
}
}
}
],
"should": [
{"term": {
"id": {
"value": "3809"
}
}},
{"term": {
"id": {
"value": "1994"
}
}}
],
"minimum_should_match": 1
}
}
}
标题里面包含bob,发行时间在1991年到1994年,最好id是3809或者1994,should条件最少满足一个。
嵌套案例,boost会增加。
GET movies/_search
{
"query": {
"bool": {
"should": [
{"bool": {
"must": [
{"term": {
"title": {
"value": "bob",
"boost": 1.1
}
}}
]
}}
]
}
}
}
5.2 单字符串多字段查询
案例,查询title或者year出现2012的电影,should换成must表示且。
GET movies/_search
{
"explain": true,
"query": {
"bool": {
"should": [
{"match": {"title": "2012" }},
{"match": {"year": "2012"}}
]
}
}
}
评分规则:
- 查询should语句的两个查询
- 对两个查询的评分求和
- 乘以匹配语句的总数
- 除以所有的语句的总数
基于这个评分规则,可能评分可能不是自己想要的。可以使用Disjunction Max Query
,这样就会选择分数最佳的查询的结果的值,返回,不再是相加。
5.3 Disjunction Max Query
GET search_test/_search
{
"query": {
"dis_max": {
"should": [
{"match": {"title": "php class" }},
{"match": {"body": "php class"}}
],
"tie_breaker": 0.7
}
}
}
找出最大值,然后把其他的评分乘以0.7,然后想加得到一个新的评分。
5.3 Mulit Match
5.3.1 最佳字段(best field)
当字段之间存在相互竞争,又相互关联。类似title和body,评分来自最匹配的字段。
案例
POST movies/_search
{
"query": {
"multi_match": {
"type": "best_fields",
"query": "1993",
"fields": [
"title",
"id"
],
"minimum_should_match": 1
, "tie_breaker": 0.2
}
}
}
5.3.2 多字段(most field)
- 在主字段(english analyzer),抽取词干,加入同义词,以匹配更多的项。
- 相同的文本,加入子字段(standard analyzer),以提供更加精准的匹配。其他字段作为匹配文档提高相关度的信号,匹配的字段越多越好。
案例
PUT /titles
{
"mappings": {
"properties": {
"title":{
"type": "text",
"analyzer": "english",
"fields": {
"std":{
"type":"text",
"analyzer":"standard"
}
}
}
}
}
}
std字段不会进行分词,是对title的优化
查询
GET /titles/_search
{
"query": {
"multi_match": {
"type": "most_fields",
"query": "1",
"fields": ["title^10","title.std"]
}
}
}
mostfield方式,title设置权重百分10。
5.3.3 混合字段(cross field)
对于某些实体,人名、地址。需要在多个字段中确定信息,单个字段只能作为整体的一部分。希望在列出的字段中尽可能找到多的词。
案例
POST address/_search
{
"query": {
"multi_match": {
"query": "poland street w",
"fields": ["street","city","country","postcode"],
"type": "cross_fields",
"operator": "and"
}
}
}
相对于通过_copyto方式,减少了磁盘空间,同时可以在搜索的时候为单个字段提升权重
6 search template
将搜索定义成模板,定义和使用的案例如下
POST _scripts/tmdb
{
"script":{
"lang":"mustache",
"source":{
"_source":["title","overview"],
"size":20,
"query":{
"multi_match":{
"query":"{{q}}",
"field":["title","overview"]
}
}
}
}
}
POST tmdb/_search/template
{
"id":"tmdb",
"params": {
"q":"basketball with"
}
}
7 index alias
给索引创建一个别名,实现灵停机运维。通过别名,索引的改名和重建程序不用动。
POST _aliases
{
"actions": [
{
"add": {
"index": "movies",
"alias": "moveis-2019",
"filter": {
"range": {
"year": {
"gte": 2012
}
}
}
}
}
]
}