elasticsearch2

映射 mapping

映射示例
#Dynamic mapping
DELETE product_mapping
GET product_mapping/_mapping
PUT /product_mapping/_doc/1
{
  "name": "xiaomi phone",
  "desc": "shouji zhong de zhandouji",
  "count": 123456,
  "price": 123.123,
  "date": "2020-05-20",
  "isdel": false,
  "tags": [
    "xingjiabi",
    "fashao",
    "buka"
  ]
}

#手工创建mapping(fields的mapping只能创建,无法修改)
#语法
GET product/_mapping
PUT /product
{
  "mappings": {
    "properties": {
      "date": {
        "type": "text"
      }
    }
  }
}

GET product/_mapping
#1 index

#案例
PUT /product
{
  "mappings": {
    "properties": {
      "date": {
        "type": "text"
      },
      "desc": {
        "type": "text",
        "analyzer": "english"
      },
      "name": {
        "type": "text",
        "index": "false"
      },
      "price": {
        "type": "long"
      },
      "tags": {
        "type": "text",
        "index": "true"
      },
      "parts": {
        "type": "object"
      },
      "partlist": {
        "type": "nested"
      }
    }
  }
}
#插入数据
GET product/_mapping
PUT /product/_doc/1
{
  "name": "xiaomi phone",
  "desc": "shouji zhong de zhandouji",
  "count": 123456,
  "price": 3999,
  "date": "2020-05-20",
  "isdel": false,
  "tags": [
    "xingjiabi",
    "fashao",
    "buka"
  ],
  "parts": {
    "name": "adapter",
    "desc": "5V 2A"
  },
  "partlist": [
    {
      "name": "adapter",
      "desc": "5V 2A"
    },
    {
      "name": "USB-C",
      "desc": "5V 2A 1.5m"
    },
    {
      "name": "erji",
      "desc": "boom"
    }
  ]
}
#查看
GET /product/_search
{
  "query": {
    "match_all": {}
  }
}
#验证
GET /product/_search
{
  "query": {
    "match": {
      "name": "xiaomi"
    }
  }
}

#copy_to
PUT copy_to
{
  "mappings": {
    "properties": {
      "field1": {
        "type": "text",
        "copy_to": "field_all" 
      },
      "field2": {
        "type": "text",
        "copy_to": "field_all" 
      },
      "field_all": {
        "type": "text"
      }
    }
  }
}

PUT copy_to/_doc/1
{
  "field1": "field1",
  "field2": "field2"
}
GET copy_to/_search
GET copy_to/_search
{
  "query": {
    "match": {
      "field_all": { 
        "query": "field1 field2"
      }
    }
  }
}

#coerce:是否允许强制类型转换
PUT coerce
{
  "mappings": {
    "properties": {
      "number_one": {
        "type": "integer"
      },
      "number_two": {
        "type": "integer",
        "coerce": false
      }
    }
  }
}
PUT coerce/_doc/1
{
  "number_one": "10" 
}
#//拒绝,因为设置了false
PUT coerce/_doc/2
{
  "number_two": "10" 
}  

DELETE coerce
PUT coerce
{
  "settings": {
    "index.mapping.coerce": false
  },
  "mappings": {
    "properties": {
      "number_one": {
        "type": "integer",
        "coerce": true
      },
      "number_two": {
        "type": "integer"
      }
    }
  }
}
PUT coerce/_doc/1
{ 
  "number_one": "10" 
} 
#拒绝,因为设置了false
PUT coerce/_doc/2
{
  "number_two": "10" 
  
} 

PUT /product/_mapping
{
  "properties": {
    "date": {
      "type": "text"
    }
  }
}

#7- 7 
PUT dynamic
{
  "mappings": {
    "dynamic": false,
    "properties": {
      "user": {
        "properties": {
          "date": {
            "type": "text"
          },
          "desc": {
            "type": "text",
            "analyzer": "english"
          },
          "name": {
            "type": "text",
            "index": "false"
          },
          "price": {
            "type": "long"
          }
        }
      }
    }
  }
}
PUT /product/_mapping
{
  "properties": {
    "date": {
      "type": "text"
    }
  }
}

#7-11 
GET /product/_mapping
#给city创建一个keyword
PUT fields_test
{
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "fields": {
          "raw": { 
            "type":  "keyword"
          }
        }
      }
    }
  }
}

PUT fields_test/_doc/1
{
  "city": "New York"
}

PUT fields_test/_doc/2
{
  "city": "York"
}
GET fields_test/_mapping
GET fields_test/_search
{
  "query": {
    "match": {
      "city": "york" 
    }
  },
  "sort": {
    "city.raw": "asc" 
  },
  "aggs": {
    "cities": {
      "terms": {
        "field": "city.raw" 
      }
    }
  }
}

#忽略类型错误-常用于数据同步
PUT ignore_malformed
{
  "mappings": {
    "properties": {
      "number_one": {
        "type": "integer",
        "ignore_malformed": true
      },
      "number_two": {
        "type": "integer"
      }
    }
  }}
PUT ignore_malformed/_doc/1
{
  "text":       "Some text value",
  "number_one": "foo" 
  
}   
#//虽然有异常 但是不抛出
PUT ignore_malformed/_doc/2
{
  "text":       "Some text value",
  "number_two": "foo" 
  
}  
GET my_index/_search
#//数据格式不对


#fielddata
#每个tag产品的数量   "size":0, 不显示原始结果
GET /product/_search
{
  "aggs": {
    "tag_agg_group": {
      "terms": {
        "field": "tags"
      }
    }
  },
  "size":0
}
GET /product/_mapping
#将文本field的fielddata属性设置为true
PUT /product/_mapping
{
  "properties": {
    "tags": {
      "type": "text",
      "fielddata": true
    }
  }
}
映射的基本概念
Mapping 也称之为映射,定义了 ES 的索引结构、字段类型、分词器等属性,是索引必不可少的组成部分。

ES 中的 mapping 有点类似与RDB中“表结构”的概念,在 MySQL 中,表结构里包含了字段名称,字段的类型还有索引信息等。在 Mapping 里也包含了一些属性,比如字段名称、类型、字段使用的分词器、是否评分、是否创建索引等属性,并且在ES中一个字段可以有对个类型。分词器、评分等概念在后面的课程讲解。


查看完整 mapping
GET /index/_mappings

查看指定字段 mapping
GET /index/_mappings/field/<field_name>

数字类型
long:64 位有符号整形
integer:32 位有符号整形
short:16 位有符号整形
byte:8位有符号整形
double:双精度 64位浮点类型
float:单精度 64位浮点类型
half_float:半精度 64位浮点类型
scaled_float:缩放类型浮点数,按固定 double 比例因子缩放
unsigned_long:无符号 64 位整数。

基本数据类型
binary:Base64 字符串二进制值
boolean:布尔类型,接收 ture 和 false 两个值
alias:字段别名

Keywords类型
keyword:适用于索引结构化的字段,可以用于过滤、排序、聚合。keyword类型的字段只能通过精确值搜索到。如 Id、姓名这类字段应使用 keyword
constant_keyword:始终包含相同值的关键字字段
wildcard:可针对类似 grep 的

Dates(时间类型)
date:JSON 没有日期数据类型,例如 "2015-01-01"、 "2015/01/01 12:10:30"
date_nanos:此数据类型是对 date 类型的补充。但是有一个重要区别。date 类型存储最高精度为毫秒,而date_nanos 类型存储日期最高精度是纳秒,但是高精度意味着可存储的日期范围小,即:从大约 1970 到 2262,因为日期仍存储为表示纳秒的长 自时代以来。

对象类型
object:非基本数据类型之外,默认的 json 对象为 object 类型。
flattened:单映射对象类型,其值为 json 对象。
nested ★:嵌套类型。
join:父子级关系类型。

空间数据类型
geo_point:纬度和经度点。
geo_shape:复杂的形状,例如多边形。
point:任意笛卡尔点。
shape:任意笛卡尔几何。

文档排名类型
dense_vector:记录浮点值的密集向量。
rank_feature:记录数字特征以提高查询时的命中率。
rank_features:记录数字特征以提高查询时的命中率。

文本搜索类型
text:文本类型
annotated-text:包含特殊文本 标记。用于标识命名实体。
completion ★:用于自动补全,即搜索推荐
search_as_you_type: 类似文本的字段,经过优化 为提供按类型完成的查询提供现成支持 用例
token_count:文本中的标记计数。

自动映射:Dynamic field mapping
field type      dynamic
true/false      boolean
小数             float
数字             long
object           object
数组             取决于数组中的第一个非空元素的类型
日期格式字符串    date
数字类型字符串    float/long
其他字符串        text + keyword
除了上述字段类型之外,其他类型都必须显示映射,也就是必须手工指定,因为其他类型ES无法自动识别。

显示映射 Expllcit field mapping
PUT /product
{
  "mappings": {
    "properties": {
      "field": {
        "mapping_parameter": "parameter_value",
        ...
      },
      ...
    }
  }
}

映射参数
index:是否对创建对当前字段创建倒排索引,默认 true,如果不创建索引,该字段不会通过索引被搜索到,但是仍然会在 source 元数据中展示

analyzer:指定分析器(character filter、tokenizer、Token filters)。

boost:对当前字段相关度的评分权重,默认1

coerce:是否允许强制类型转换 true “1”=> 1 false “1”=< 1

copy_to:该参数允许将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询

doc_values:为了提升排序和聚合效率,默认true,如果确定不需要对字段进行排序或聚合,也不需要通过脚本访问字段值,则可以禁用doc值以节省磁盘空间(不支持text和annotated_text)

dynamic:控制是否可以动态添加新字段
    true 新检测到的字段将添加到映射中。(默认)
    false 新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍会出现在_source返回的匹配项中。这些字段不会添加到映射中,必须显式添加新字段。

strict 如果检测到新字段,则会引发异常并拒绝文档。必须将新字段显式添加到映

eager_global_ordinals:用于聚合的字段上,优化聚合性能,但不适用于 Frozen indices。
    Frozen indices(冻结索引):有些索引使用率很高,会被保存在内存中,有些使用率特别低,宁愿在使用的时候重新创建,在使用完毕后丢弃数据,Frozen indices 的数据命中频率小,不适用于高搜索负载,数据不会被保存在内存中,堆空间占用比普通索引少得多,Frozen indices是只读的,请求可能是秒级或者分钟级。

enable:是否创建倒排索引,可以对字段操作,也可以对索引操作,如果不创建索引,让然可以检索并在_source元数据中展示,谨慎使用,该状态无法修改。

fielddata:查询时内存数据结构,在首次用当前字段聚合、排序或者在脚本中使用时,需要字段为fielddata数据结构,并且创建倒排索引保存到堆中

fields:给field创建多字段,用于不同目的(全文检索或者聚合分析排序)

format:格式化

ignore_above:超过长度将被忽略

ignore_malformed:忽略类型错误

index_options:控制将哪些信息添加到反向索引中以进行搜索和突出显示。仅用于text字段

Index_phrases:提升 exact_value 查询速度,但是要消耗更多磁盘空间

Index_prefixes:前缀搜索
    min_chars:前缀最小长度,> 0,默认 2(包含)
    max_chars:前缀最大长度,< 20,默认 5(包含)

meta:附加元数据

normalizer:

norms:是否禁用评分(在 filter 和聚合字段上应该禁用)。

null_value:为 null 值设置默认值

position_increment_gap:参考 match_phrase 跨值查询中 position_increment_gap 参数用法

proterties:除了mapping还可用于object的属性设置

search_analyzer:设置单独的查询时分析器:

similarity:为字段设置相关度算法,支持BM25、classic(TF-IDF)、boolean

store:设置字段是否仅查询

term_vector:运维参数,会在进阶课程中深入讲解

#Text 类型 概述
当一个字段是要被全文搜索的,比如 Email 内容、产品描述,这些字段应该使用 text 类型。设置 text 类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。

Text 类型 注意事项
适用于全文检索:如 match 查询
文本字段会被分词
默认情况下,会创建倒排索引
自动映射器会为 Text 类型创建 Keyword 字段

#Keyword 类型 概述
Keyword 类型适用于不分词的字段,如姓名、Id、数字等。如果数字类型不用于范围查找,用 Keyword 的性能要高于数值类型。

Keyword 类型 语法和语义
如当使用 keyword 类型查询时,其字段值会被作为一个整体,并保留字段值的原始属性。
GET test_index/_search
{
  "query": {
    "match": {
      "title.keyword": "测试文本值"
    }
  }
}

Keyword 类型 注意事项
Keyword 不会对文本分词,会保留字段的原有属性,包括大小写等。
Keyword 仅仅是字段类型,而不会对搜索词产生任何影响
Keyword 一般用于需要精确查找的字段,或者聚合排序字段
Keyword 通常和 Term 搜索一起用(会在 DSL 中提到)
Keyword 字段的 ignore_above 参数代表其截断长度,默认 256,如果超出长度,字段值会被忽略,而不是截断。

#映射模板 简介
之前讲过的映射类型或者字段参数,都是为确定的某个字段而声明的,如果希望对符合某类要求的特定字段制定映射,就需要用到映射模板:Dynamic templates。

映射模板有时候也被称作:自动映射模板、动态模板等。

#基本语法
"dynamic_templates": [
    {
      "my_template_name": { 
        ... match conditions ... 
        "mapping": { ... } 
      }
    },
    ...
]

#conditions参数
match_mapping_type :主要用于对数据类型的匹配
match 和 unmatch   :用于对字段名称的匹配

#示例
PUT test_dynamic_template
{
  "mappings": {
    "dynamic_templates": [
      {
        "integers": {
          "match_mapping_type": "long",
          "mapping": {
            "type": "integer"
          }
        }
      },
      {
        "longs_as_strings": {
          "match_mapping_type": "string",
          "match": "num_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}
#以上代码会产生以下效果:
所有long类型字段会默认映射为integer
所有文本字段,如果是以 num_ 开头,并且不以 _text 结尾,会自动映射为 keyword 类型

字符过滤器(character filter):分词之前的预处理,过滤无用字符

1.字符html标签过滤器:html_strip
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-
htmlstrip-charfilter.html
#参数:escaped_tags 需要保留的html标签

2.字符映射过滤器:type mapping
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-mapping-charfilter.html

3.字符正则替换过滤器:type pattern_replace
https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-pattern-replace-charfilter.html

字符过滤器

#分词器
GET _analyze
{
  "text": "Mr. Ma is an excellent teacher",
  "analyzer": "english"
}

#字符标签过滤器索引
PUT my_index
{
  "settings": {
    #指定分析器
    "analysis": {
      #设置字符串过滤器
      "char_filter": {
        "my_char_filter":{
          #字符过滤器类型:标签过滤器
          "type":"html_strip",
          #需要保留的标签
          "escaped_tags":["a"]
        }
      },
      #指定分词器
      "analyzer": {
        "my_analyzer":{
          "tokenizer":"keyword",
          #指定字符串过滤器
          "char_filter":["my_char_filter"]
        }
      }
    }
  }
}

#测试分词器运行效果:I'm so <a>happy</a>!
GET my_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": "<p>I'm so <a>happy</a>!</p>"
}

#字符映射过滤器
PUT my_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "my_char_filter":{
          #字符过滤器类型:映射过滤器
          "type":"mapping",
          #字符映射列表
          "mappings":["滚 => *", "垃 => *", "圾 => *"]
        }
      },
      "analyzer": {
        "my_analyzer":{
          "tokenizer":"keyword",
          "char_filter":["my_char_filter"]
        }
      }
    }
  }
}

#测试分词器运行效果:你就是个**!*
GET my_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": "你就是个垃圾!滚"
}

#字符正则替换过滤器
PUT my_index
{
  "settings": {
    "analysis": {
      "char_filter": {
        "my_char_filter":{
          #字符过滤器类型:正则替换过滤器
          "type":"pattern_replace",
          "pattern":"(\\d{3})\\d{4}(\\d{4})",
          "replacement":"$1****$2"
        }
      },
      "analyzer": {
        "my_analyzer":{
          "tokenizer":"keyword",
          "char_filter":["my_char_filter"]
        }
      }
    }
  }
}

#测试分词器运行效果:176****1200
GET my_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": "您的手机号是17611001200"
}

令牌过滤器(token filter):停用词、时态转换、大小写转换、同义词转换、语气词处理等

#同义词官方文档地址
https://www.elastic.co/guide/en/elasticsearch/reference/7.10/analysis-synonym-tokenfilter.html

PUT /test_index
{
  "settings": {
      "analysis": {
        "filter": {
          "my_synonym": {
            #设置同义词过滤器
            "type": "synonym_graph",
            #同义词文件内容:
            #大G ==> 本次G级
            #霸道 ==> 普拉多
            "synonyms_path": "analysis/synonym.txt"
          }
        },
        "analyzer": {
          "my_analyzer": {
            "tokenizer": "ik_max_word",
            "filter": [ "my_synonym" ]
          }
        }
      }
  }
}

#测试结果:
GET test_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["大G,霸道"]
}

GET test_index/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["奔驰G级"]
}

PUT /test_index
{
  "settings": {
      "analysis": {
        "filter": {
          "my_synonym": {
            #同义词过滤器
            "type": "synonym",
            #直接指定过滤替换内容
            "synonyms": ["赵,钱,孙,李=>吴","周=>王"]
          }
        },
        "analyzer": {
          "my_analyzer": {
            "tokenizer": "standard",
            "filter": [ "my_synonym" ]
          }
        }
      }
  }
}

GET test_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["赵,钱,孙,李","周"]
}

#大小写
GET test_index/_analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": ["AASD ASDA SDASD ASDASD"]
}

GET test_index/_analyze
{
  "tokenizer": "standard",
  "filter": ["uppercase"],
  "text": ["asdasd asd asg dsfg gfhjsdf asfdg g"]
}

GET test_index/_analyze
{
  "tokenizer": "standard",
  "filter": {
    "type": "condition",
    "filter":"uppercase",
    "script": {
      "source": "token.getTerm().length() < 5"
    }
  },
  "text": ["asdasd asd asg dsfg gfhjsdf asfdg g"]
}

#停用词
DELETE test_index
PUT /test_index
{
  "settings": {
      "analysis": {
        "analyzer": {
          "my_analyzer": {
            "type": "standard",
            #停用词,遇到这些词时忽略
            "stopwords":["me","you"]
          }
        }
      }
  }
}

GET test_index/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["Teacher me and you in the china"]
}

分词器

#官方文档
https://www.elastic.co/guide/en/elasticsearch/reference/7.10/analysis-tokenizers.html

#常见分词器:
standard analyzer:默认分词器,中文支持的不理想,会逐字拆分。
pattern tokenizer:以正则匹配分隔符,把文本拆分成若干词项。
simple pattern tokenizer:以正则匹配词项,速度比pattern tokenizer快。
whitespace analyzer:以空白符分隔 Tim_cookie

GET test_index/_analyze
{
  "tokenizer": "ik_max_word",
  "text": ["我爱中国"]
}

#自定义分词器:
char_filter: 内置或自定义字符过滤器
token filter:内置或自定义token filter
tokenizer:   内置或自定义分词器

#自定义分词器
PUT custom_analysis
{
  "settings": {
    "analysis": {
      #定义字符过滤器
      "char_filter": {
        "my_char_filter": {
          "type": "mapping",
          "mappings": ["& => and","| => or"]
        },
        "html_strip_char_filter":{
          "type":"html_strip",
          "escaped_tags":["a"]
        }
      },
      "filter": {
        "my_stopword": {
          "type": "stop",
          #忽略这些词
          "stopwords": ["is", "in", "the", "a", "at", "for"]
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "pattern",
          #分词的切割点
          "pattern": "[ ,.!?]"
        }
      },
      #自定义分词器
      "analyzer": {
        自定义分词器名称
        "my_analyzer":{
          #类型是自定义
          "type":"custom",
          #过滤字符
          "char_filter":["my_char_filter","html_strip_char_filter"],
          #过滤同义词等
          "filter":["my_stopword","lowercase"],
          #配置切割的逻辑
          "tokenizer":"my_tokenizer"
        }
      }
    }
  }
}

测试自定义的分词器
GET custom_analysis/_analyze
{
  "analyzer": "my_analyzer",
  "text": ["What is ,<a>as.df</a>  ss<p> in ? &</p> | is ! in the a at for "]
}
中文分词器:ik分词
#安装和部署
ik下载地址:https://github.com/medcl/elasticsearch-analysis-ik
Github加速器:https://github.com/fhefh2015/Fast-GitHub
创建插件文件夹 cd your-es-root/plugins/ && mkdir ik
将插件解压缩到文件夹 your-es-root/plugins/ik
重新启动es

#IK文件描述
IKAnalyzer.cfg.xml:IK分词配置文件
主词库:main.dic
英文停用词:stopword.dic,不会建立在倒排索引中

特殊词库:
quantifier.dic:特殊词库:计量单位等
suffix.dic:特殊词库:行政单位
surname.dic:特殊词库:百家姓
preposition:特殊词库:语气词

自定义词库:网络词汇、流行词、自造词等,自定义时可以直接在配置文件中配置词文件的地址,但需要重启es

#ik提供的两种analyzer:
ik_max_word会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询

GET custom_analysis/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["我爱中华人民共和国"]
}

ik分词热更新

#1.远程词库文件
1.1优点:上手简单
1.2缺点:
    词库的管理不方便,要操作直接操作磁盘文件,检索页很麻烦
    文件的读写没有专门的优化性能不好
    多一层接口调用和网络传输

#ik访问数据库
#MySQL驱动版本兼容性
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html
https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-versions.html

驱动下载地址
https://mvnrepository.com/artifact/mysql/mysql-connector-java

索引的批量操作

#批量查询: GET /_mget

#批量写入:
POST /<index>/_bulk
{"action": {"metadata"}}
{"data"}

注意:
1、bulk api对json的语法有严格的要求,除了delete外,每一个操作都要两个json串(metadata2和business data),且每个json串内不能换行,非同一个json串必须换行,否则会报错;
2、bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志

#索引的操作类型
create:如果在PUT数据的时候当前数据已经存在,则数据会被覆盖,如果在PUT的时候加上操作类型create,此时如果数据已存在则会返回失败,因为已经强制指定了操作类型为create,ES就不会再去执行update操作。比如:PUT /pruduct/_create/1/ ( 老版本的语法为 PUT /pruduct/_doc/1/_create )指的就是在索引product中强制创建id为1的数据,如果id为1的数据已存在,则返回失败。
delete:删除文档,ES对文档的删除是懒删除机制,即标记删除。(lazy delete原理)
index:在ES中,写入操作被称为Index,这里Index为动词,即索引数据为将数据创建在ES中的索引,写入数据亦可称之为“索引数据”。可以是创建,也可以是全量替换
update:执行partial update(全量替换,部分替换)

以上四种操作类型均为写操作。ES中的数据写入均发生在Primary Shard,当数据在Primary写入完成之后会同步到相应的Replica Shard。ES的数据写入有两种方式:单个数据写入和批量写入,ES为批量写入数据提供了特有的API:_bulk。

优缺点
    优点:相较于普通的Json格式的数据操作,不会产生额外的内存消耗,性能更好,常用于大数据量的批量写入
    缺点:可读性差,可能会没有智能提示。

使用场景:大数据量的批量操作,比如数据从MySQL中一次性写入ES,批量写入减少了对es的请求次数,降低了内存开销以及对线程的占用。

批量查询
GET product/_mget
{
  "ids": [2,3,4]
}

GET product/_mget
{
  "docs": [{
      "_id": 2,
      "_source": ["name","price"]
    },
    {
      "_id": 3,
      "_source": {
        "include": ["name","price"],
        "exclude": ["price","type"]
      }
    }]
}

创建数据
PUT test_index/_doc/1
{
  "test_field":"test",
  "test_title":"title"
}

删除
DELETE test_index/_doc/3

更新
POST /test_index/_update/11
{
  "doc": {
    "test_title": "test 3"
  }
}

重命名索引
POST _reindex
{
  "source": {"index": "product"},
  "dest": {"index": "product2"}
}

批量新增、修改处理数据
#加?filter_path=items.*.error  只显示失败的
POST /_bulk?filter_path=items.*.error
{ "create": { "_index": "product2",  "_id": "2" }}
{ "name":    "_bulk create 2" }
{ "create": { "_index": "product2",  "_id": "12" }}
{ "name":    "_bulk create 12" }
{ "index":  { "_index": "product2",  "_id": "3" }}
{ "name":    "index product2 "}
{ "index":  { "_index": "product2",  "_id": "13" }}
{ "name":    "index product2" }
{ "update": { "_index": "product2",  "_id": "4","retry_on_conflict" : "3"} }
{ "doc" : {"test_field2" : "bulk test1"} }

模糊查询

#前缀搜索:prefix

概念:以xx开头的搜索,不计算相关度评分。

注意:
    前缀搜索匹配的是term,而不是field。即匹配的是字段分词后的单个term的前缀匹配
    前缀搜索的性能很差,尽量不用
    前缀搜索没有缓存
    前缀搜索尽可能把前缀长度设置的更长

语法:
GET <index>/_search
{
  "query": {
    "prefix": {
      "<field>": {
        "value": "<word_prefix>"
      }
    }
  }
}
默认的前缀匹配长度范围
index_prefixes: "min_chars" : 2,   "max_chars" : 5

GET my_index/_search
{
  "query": {
    "prefix": {
      "text": {
        "value": "城市"
      }
    }
  }
}

索引实例
PUT my_index
{
  "mappings": {
    "properties": {
      "text": {
        "analyzer": "ik_max_word",
        "type": "text",
        "index_prefixes":{
          "min_chars":2,
          "max_chars":4
        },
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      }
    }
  }
}


#通配符:wildcard

概念:通配符运算符是匹配一个或多个字符的占位符。例如,*通配符运算符匹配零个或多个字符。您可以将通配符运算符与其他字符结合使用以创建通配符模式。

注意:通配符匹配的也是term,而不是field

语法:
GET <index>/_search
{
  "query": {
    "wildcard": {
      "<field>": {
        "value": "<word_with_wildcard>"
      }
    }
  }
}

GET my_index/_search
{
  "query": {
    "wildcard": {
      "text.keyword": {
        "value": "my eng*ish"
      }
    }
  }
}

#正则:regexp

概念:regexp查询的性能可以根据提供的正则表达式而有所不同。为了提高性能,应避免使用通配符模式,如.或 .?+未经前缀或后缀

语法:
GET <index>/_search
{
  "query": {
    "regexp": {
      "<field>": {
        "value": "<regex>",
        "flags": "ALL",
      }
    }
  }
}

GET product_en/_search
{
  "query": {
    "regexp": {
      "desc": {
        "value":"[\\s\\S]*nfc[\\s\\S]*",
        "flags": "COMPLEMENT"
      }
    }
  }
}

#模糊查询:fuzzy
混淆字符 (box → fox)
缺少字符 (black → lack)
多出字符 (sic → sick)
颠倒次序 (act → cat)

注意:
    fuzzy和match的区别:fuzzy的搜索字符是不分词的,match的搜索字符是分词的
    大数据量的不建议使用

语法
GET <index>/_search
{
  "query": {
    "fuzzy": {
      "<field>": {
        "value": "<keyword>"
      }
    }
  }
}

# fuzzy:模糊查询
GET product_en/_search
{
  "query": {
    "fuzzy": {
      "desc": {
        "value": "quangongneng nfc",
        "fuzziness": "2"
      }
    }
  }
}


#短语前缀:match_phrase_prefix

match_phrase:
    match_phrase会分词
    被检索字段必须包含match_phrase中的所有词项并且顺序必须是相同的
    被检索字段包含的match_phrase中的词项之间不能有其他词项

match_phrase_prefix:对性能有影响

概念:
match_phrase_prefix与match_phrase相同,但是它多了一个特性,就是它允许在文本的最后一个词项(term)上的前缀匹配,如果 是一个单词,比如a,它会匹配文档字段所有以a开头的文档,如果是一个短语,比如 "this is ma" ,他会先在倒排索引中做以ma做前缀搜索,然后在匹配到的doc中做match_phrase查询,(网上有的说是先match_phrase,然后再进行前缀搜索, 是不对的)

参数
    analyzer 指定何种分析器来对该短语进行分词处理
    max_expansions 限制前缀匹配的最大词项
    boost 用于设置该查询的权重
    slop 允许短语间的词项(term)间隔:slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 什么是相隔多远? 意思是说为了让查询和文档匹配你需要移动词条多少次?

GET product_en/_search
{
  "query": {
    "match_phrase_prefix": {
      "desc": {
        "query": "de zhong shouji hongzhaji",
        "max_expansions": 50,
        "slop":3
      }
    }
  }
}

# N-gram和edge ngram

搜索推荐

示例
#term suggest

DELETE news
POST _bulk
{ "index" : { "_index" : "news","_id":1 } }
{ "title": "baoqiang bought a new hat with the same color of this font, which is very beautiful baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":2 } }
{ "title": "baoqiangge gave birth to two children, one is upstairs, one is downstairs baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":3} }
{ "title": "baoqiangge 's money was rolled away baoqiangba baoqiangda baoqiangdada baoqian baoqia"}
{ "index" : { "_index" : "news","_id":4} }
{ "title": "baoqiangda baoqiangda baoqiangda baoqiangda baoqiangda baoqian baoqia"}

GET news/_mapping

POST _analyze
{
  "text": [
    "BaoQiang bought a new hat with the same color of this font, which is very beautiful",
    "BaoQiangGe gave birth to two children, one is upstairs, one is downstairs",
    "BaoQiangGe 's money was rolled away"
  ]
}

POST /news/_search
{
  "suggest": {
    "my-suggestion": {
      "text": "baoqing baoqiang",
      "term": {
        "suggest_mode":"always",
        "field": "title",
        "min_doc_freq": 3
      }
    }
  }
}


GET /news/_search
{ 
  "suggest": {
    "my-suggestion": {
      "text": "baoqing baoqiang",
      "term": {
        "suggest_mode": "popular",
        "field": "title"
      }
    }
  }
}

GET /news/_search
{ 
  "suggest": {
    "my-suggestion": {
      "text": "baoqing baoqiang",
      "term": {
        "suggest_mode": "popular",
        "field": "title",
        "max_edits":2,
        "max_term_freq":1
      }
    }
  }
}

GET /news/_search
{ 
  "suggest": {
    "my-suggestion": {
      "text": "baoqing baoqiang",
      "term": {
        "suggest_mode": "always",
        "field": "title",
        "max_edits":2
      }
    }
  }
}

DELETE news2
POST _bulk
{ "index" : { "_index" : "news2","_id":1 } }
{ "title": "baoqiang4"}
{ "index" : { "_index" : "news2","_id":2 } }
{ "title": "baoqiang4 baoqiang3"}
{ "index" : { "_index" : "news2","_id":3 } }
{ "title": "baoqiang4 baoqiang3 baoqiang2"}
{ "index" : { "_index" : "news2","_id":4 } }
{ "title": "baoqiang4 baoqiang3 baoqiang2  baoqiang"}
POST /news2/_search
{ 
  "suggest": {
    "second-suggestion": {
      "text": "baoqian baoqiang baoqiang2 baoqiang3",
      "term": {
        "suggest_mode": "popular",
        "field": "title"
      }
    }
  }
}



#phrase suggester
DELETE test
PUT test
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 0,
      "analysis": {
        "analyzer": {
          "trigram": {
            "type": "custom",
            "tokenizer": "standard",
            "filter": [
              "lowercase",
              "shingle"
            ]
          }
        },
        "filter": {
          "shingle": {
            "type": "shingle",
            "min_shingle_size": 2,
            "max_shingle_size": 3
          }
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "trigram": {
            "type": "text",
            "analyzer": "trigram"
          }
        }
      }
    }
  }
}

GET /_analyze
{
  "tokenizer": "standard",
  "filter": [
    {
      "type": "shingle",
      "min_shingle_size": 2,
      "max_shingle_size": 3
    }
  ],
  "text": "lucene and elasticsearch"
}


# "min_shingle_size": 2,
# "max_shingle_size": 3
GET test/_analyze
{
  "analyzer": "trigram", 
  "text" : "lucene and elasticsearch"
}
DELETE test
POST test/_bulk
{ "index" : { "_id":1} }
{"title": "lucene and elasticsearch"}
{ "index" : {"_id":2} }
{"title": "lucene and elasticsearhc"}
{ "index" : { "_id":3} }
{"title": "luceen and elasticsearch"}

POST test/_search
GET test/_mapping
POST test/_search
{
  "suggest": {
    "text": "Luceen and elasticsearhc",
    "simple_phrase": {
      "phrase": {
        "field": "title.trigram",
        "max_errors": 2,
        "gram_size": 1,
        "confidence":0,
        "direct_generator": [
          {
            "field": "title.trigram",
            "suggest_mode": "always"
          }
        ],
        "highlight": {
          "pre_tag": "<em>",
          "post_tag": "</em>"
        }
      }
    }
  }
}

#complate suggester
DELETE suggest_carinfo
PUT suggest_carinfo
{
  "mappings": {
    "properties": {
        "title": {
          "type": "text",
          "analyzer": "ik_max_word",
          "fields": {
            "suggest": {
              "type": "completion",
              "analyzer": "ik_max_word"
            }
          }
        },
        "content": {
          "type": "text",
          "analyzer": "ik_max_word"
        }
      }
  }
}



POST _bulk
{"index":{"_index":"suggest_carinfo","_id":1}}
{"title":"宝马X5 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":2}}
{"title":"宝马5系","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":3}}
{"title":"宝马3系","content":"这里是奔驰图文描述"}
{"index":{"_index":"suggest_carinfo","_id":4}}
{"title":"奥迪Q5 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":5}}
{"title":"奥迪A6 无敌车况","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":6}}
{"title":"奥迪双钻","content":"这里是奔驰图文描述"}
{"index":{"_index":"suggest_carinfo","_id":7}}
{"title":"奔驰AMG 两万公里准新车","content":"这里是宝马X5图文描述"}
{"index":{"_index":"suggest_carinfo","_id":8}}
{"title":"奔驰大G 无敌车况","content":"这里是奥迪A6图文描述"}
{"index":{"_index":"suggest_carinfo","_id":9}}
{"title":"奔驰C260","content":"这里是奔驰图文描述"}
{"index":{"_index":"suggest_carinfo","_id":10}}
{"title":"nir奔驰C260","content":"这里是奔驰图文描述"}


GET suggest_carinfo/_search?pretty
{
  "suggest": {
    "car_suggest": {
      "prefix": "奥迪",
      "completion": {
        "field": "title.suggest"
      }
    }
  }
}

#1:内存代价太大,原话是:性能高是通过大量的内存换来的
#2:只能前缀搜索,假如用户输入的不是前缀 召回率可能很低

POST suggest_carinfo/_search
{
  "suggest": {
    "car_suggest": {
      "prefix": "宝马5系",
      "completion": {
        "field": "title.suggest",
        "skip_duplicates":true,
        "fuzzy": {
          "fuzziness": 2
        }
      }
    }
  }
}
GET suggest_carinfo/_doc/10
GET _analyze
{
  "analyzer": "ik_max_word",
  "text": ["奔驰AMG 两万公里准新车"]
}

POST suggest_carinfo/_search
{
  "suggest": {
    "car_suggest": {
      "regex": "nir",
      "completion": {
        "field": "title.suggest",
        "size": 10
      }
    }
  }
}

# context suggester
# 定义一个名为 place_type 的类别上下文,其中类别必须与建议一起发送。
# 定义一个名为 location 的地理上下文,类别必须与建议一起发送
DELETE place
PUT place
{
  "mappings": {
    "properties": {
      "suggest": {
        "type": "completion",
        "contexts": [
          {
            "name": "place_type",
            "type": "category"
          },
          {
            "name": "location",
            "type": "geo",
            "precision": 4
          }
        ]
      }
    }
  }
}

PUT place/_doc/1
{
  "suggest": {
    "input": [ "timmy's", "starbucks", "dunkin donuts" ],
    "contexts": {
      "place_type": [ "cafe", "food" ]
    }
  }
}
PUT place/_doc/2
{
  "suggest": {
    "input": [ "monkey", "timmy's", "Lamborghini" ],
    "contexts": {
      "place_type": [ "money"]
    }
  }
}


GET place/_search
POST place/_search?pretty
{
  "suggest": {
    "place_suggestion": {
      "prefix": "sta",
      "completion": {
        "field": "suggest",
        "size": 10,
        "contexts": {
          "place_type": [ "cafe", "restaurants" ]
        }
      }
    }
  }
}
# 某些类别的建议可以比其他类别提升得更高。以下按类别过滤建议,并额外提升与某些类别相关的建议
GET place/_search
POST place/_search?pretty
{
  "suggest": {
    "place_suggestion": {
      "prefix": "tim",
      "completion": {
        "field": "suggest",
        "contexts": {
          "place_type": [
            { "context": "cafe" },
            { "context": "money", "boost": 2 }
          ]
        }
      }
    }
  }
}

# 地理位置筛选器
PUT place/_doc/3
{
  "suggest": {
    "input": "timmy's",
    "contexts": {
      "location": [
        {
          "lat": 43.6624803,
          "lon": -79.3863353
        },
        {
          "lat": 43.6624718,
          "lon": -79.3873227
        }
      ]
    }
  }
}
POST place/_search
{
  "suggest": {
    "place_suggestion": {
      "prefix": "tim",
      "completion": {
        "field": "suggest",
        "contexts": {
          "location": {
            "lat": 43.662,
            "lon": -79.380
          }
        }
      }
    }
  }
}

# 定义一个名为 place_type 的类别上下文,其中类别是从 cat 字段中读取的。
# 定义一个名为 location 的地理上下文,其中的类别是从 loc 字段中读取的
DELETE place_path_category
PUT place_path_category
{
  "mappings": {
    "properties": {
      "suggest": {
        "type": "completion",
        "contexts": [
          {
            "name": "place_type",
            "type": "category",
            "path": "cat"
          },
          {
            "name": "location",
            "type": "geo",
            "precision": 4,
            "path": "loc"
          }
        ]
      },
      "loc": {
        "type": "geo_point"
      }
    }
  }
}
# 如果映射有路径,那么以下索引请求就足以添加类别
# 这些建议将与咖啡馆和食品类别相关联
# 如果上下文映射引用另一个字段并且类别被明确索引,则建议将使用两组类别进行索引
PUT place_path_category/_doc/1
{
  "suggest": ["timmy's", "starbucks", "dunkin donuts"],
  "cat": ["cafe", "food"] 
}
POST place_path_category/_search?pretty
{
  "suggest": {
    "place_suggestion": {
      "prefix": "tim",
      "completion": {
        "field": "suggest",
        "contexts": {
          "place_type": [
            { "context": "cafe" }
          ]
        }
      }
    }
  }
}
概述:
搜索一般都会要求具有“搜索推荐”或者叫“搜索补全”的功能,即在用户输入搜索的过程中,进行自动补全或者纠错。以此来提高搜索文档的匹配精准度,进而提升用户的搜索体验,这就是Suggest。

四种 Suggester

1、term suggester
term suggester正如其名,只基于tokenizer之后的单个term去匹配建议词,并不会考虑多个term之间的关系

POST <index>/_search
{
  "suggest": {
    "<suggest_name>": {
      "text": "<search_content>",
      "term": {
        "suggest_mode": "<suggest_mode>",
        "field": "<field_name>"
      }
    }
  }
}
Options:
text:用户搜索的文本
field:要从哪个字段选取推荐数据
analyzer:使用哪种分词器
size:每个建议返回的最大结果数
sort:如何按照提示词项排序,参数值只可以是以下两个枚举:
    score:分数>词频>词项本身
    frequency:词频>分数>词项本身
suggest_mode:搜索推荐的推荐模式,参数值亦是枚举:
    missing:默认值,仅为不在索引中的词项生成建议词
    popular:仅返回与搜索词文档词频或文档词频更高的建议词
    always:根据 建议文本中的词项 推荐 任何匹配的建议词
max_edits:可以具有最大偏移距离候选建议以便被认为是建议。只能是1到2之间的值。任何其他值都将导致引发错误的请求错误。默认为2
prefix_length:前缀匹配的时候,必须满足的最少字符
min_word_length:最少包含的单词数量
min_doc_freq:最少的文档频率
max_term_freq:最大的词频

2、phrase suggester
phrase suggester和term suggester相比,对建议的文本会参考上下文,也就是一个句子的其他token,不只是单纯的token距离匹配,它可以基于共生和频率选出更好的建议。

Options:
real_word_error_likelihood: 此选项的默认值为 0.95。此选项告诉 Elasticsearch 索引中 5% 的术语拼写错误。这意味着随着这个参数的值越来越低,Elasticsearch 会将越来越多存在于索引中的术语视为拼写错误,即使它们是正确的
max_errors:为了形成更正,最多被认为是拼写错误的术语的最大百分比。默认值为 1
confidence:默认值为 1.0,最大值也是。该值充当与建议分数相关的阈值。只有得分超过此值的建议才会显示。例如,置信度为 1.0 只会返回得分高于输入短语的建议
collate:告诉 Elasticsearch 根据指定的查询检查每个建议,以修剪索引中不存在匹配文档的建议。在这种情况下,它是一个匹配查询。由于此查询是模板查询,因此搜索查询是当前建议,位于查询中的参数下。可以在查询下的“params”对象中添加更多字段。同样,当参数“prune”设置为true时,我们将在响应中增加一个字段“collate_match”,指示建议结果中是否存在所有更正关键字的匹配
direct_generator:phrase suggester使用候选生成器生成给定文本中每个项可能的项的列表。单个候选生成器类似于为文本中的每个单独的调用term suggester。生成器的输出随后与建议候选项中的候选项结合打分。目前只支持一种候选生成器,即direct_generator。建议API接受密钥直接生成器下的生成器列表;列表中的每个生成器都按原始文本中的每个项调用。

3、completion suggester
自动补全,自动完成,支持三种查询【前缀查询(prefix)模糊查询(fuzzy)正则表达式查询(regex)】 ,主要针对的应用场景就是"Auto Completion"。 此场景下用户每输入一个字符的时候,就需要即时发送一次查询请求到后端查找匹配项,在用户输入速度较高的情况下对后端响应速度要求比较苛刻。因此实现上它和前面两个Suggester采用了不同的数据结构,索引并非通过倒排来完成,而是将analyze过的数据编码成FST和索引一起存放。对于一个open状态的索引,FST会被ES整个装载到内存里的,进行前缀查找速度极快。但是FST只能用于前缀查找,这也是Completion Suggester的局限所在。

completion:es的一种特有类型,专门为suggest提供,基于内存,性能很高。
prefix query:基于前缀查询的搜索提示,是最常用的一种搜索推荐查询。
    prefix:客户端搜索词
    field:建议词字段
    size:需要返回的建议词数量(默认5)
    skip_duplicates:是否过滤掉重复建议,默认false
fuzzy query
    fuzziness:允许的偏移量,默认auto
    transpositions:如果设置为true,则换位计为一次更改而不是两次更改,默认为true。
    min_length:返回模糊建议之前的最小输入长度,默认 3
    prefix_length:输入的最小长度(不检查模糊替代项)默认为 1
    unicode_aware:如果为true,则所有度量(如模糊编辑距离,换位和长度)均以Unicode代码点而不是以字节为单位。这比原始字节略慢,因此默认情况下将其设置为false。
regex query:可以用正则表示前缀,不建议使用

4、context suggester
完成建议者会考虑索引中的所有文档,但是通常来说,我们在进行智能推荐的时候最好通过某些条件过滤,并且有可能会针对某些特性提升权重。

contexts:上下文对象,可以定义多个
    name:context的名字,用于区分同一个索引中不同的 context对象。需要在查询的时候指定当前name
    type:context对象的类型,目前支持两种:category和geo,分别用于对suggest item分类和指定地理位置。
    boost:权重值,用于提升排名
path:如果没有path,相当于在PUT数据的时候需要指定context.name字段,如果在Mapping中指定了path,在PUT数据的时候就不需要了,因为 Mapping是一次性的,而PUT数据是频繁操作,这样就简化了代码。这段解释有木有很牛逼,网上搜到的都是官方文档的翻译,觉悟雷同。
聚合查询(aggs)
用于进行聚合的字段必须是exact value,分词字段不可进行聚合,对于text字段如果需要使用聚合,需要开启fielddata,但是通常不建议;

#聚合查询分类
1.分桶聚合(Bucket agregations):类比SQL中的group by的作用,主要用于统计不同类型数据的数量
2.指标聚合(Metrics agregations):主要用于最大值、最小值、平均值、字段之和等指标的统计
3.管道聚合(Pipeline agregations):用于对聚合的结果进行二次聚合,如要统计绑定数量最多的标签bucket,就是要先按照标签进行分桶,再在分桶的结果上计算最大值。

语法
GET product/_search
{
  "aggs": {
    聚合函数的名称
    "<aggs_name>": {
      聚合种类,比如是桶聚合(terms)或者是指标聚合(avg、sum、min、max等)
      "<agg_type>": {
        字段名称或者叫域名
        "field": "<field_name>"
      }
    }
  }
}
桶聚合测试数据
## 数据
PUT product
{
  "mappings" : {
      "properties" : {
        "createtime" : {
          "type" : "date"
        },
        "date" : {
          "type" : "date"
        },
        "desc" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          },
          "analyzer":"ik_max_word"
        },
        "lv" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "name" : {
          "type" : "text",
          "analyzer":"ik_max_word",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "price" : {
          "type" : "long"
        },
        "tags" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "type" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
}
PUT /product/_doc/1
{
    "name" : "小米手机",
    "desc" :  "手机中的战斗机",
    "price" :  3999,
    "lv":"旗舰机",
    "type":"手机",
    "createtime":"2020-10-01T08:00:00Z",
    "tags": [ "性价比", "发烧", "不卡顿" ]
}
PUT /product/_doc/2
{
    "name" : "小米NFC手机",
    "desc" :  "支持全功能NFC,手机中的滑翔机",
    "price" :  4999,
        "lv":"旗舰机",
    "type":"手机",
    "createtime":"2020-05-21T08:00:00Z",
    "tags": [ "性价比", "发烧", "公交卡" ]
}
PUT /product/_doc/3
{
    "name" : "NFC手机",
    "desc" :  "手机中的轰炸机",
    "price" :  2999,
        "lv":"高端机",
    "type":"手机",
    "createtime":"2020-06-20",
    "tags": [ "性价比", "快充", "门禁卡" ]
}
PUT /product/_doc/4
{
    "name" : "小米耳机",
    "desc" :  "耳机中的黄焖鸡",
    "price" :  999,
        "lv":"百元机",
    "type":"耳机",
    "createtime":"2020-06-23",
    "tags": [ "降噪", "防水", "蓝牙" ]
}
PUT /product/_doc/5
{
    "name" : "红米耳机",
    "desc" :  "耳机中的肯德基",
    "price" :  399,
    "type":"耳机",
        "lv":"百元机",
    "createtime":"2020-07-20",
    "tags": [ "防火", "低音炮", "听声辨位" ]
}
PUT /product/_doc/6
{
    "name" : "小米手机10",
    "desc" :  "充电贼快掉电更快,超级无敌望远镜,高刷电竞屏",
    "price" :  "",
        "lv":"旗舰机",
    "type":"手机",
    "createtime":"2020-07-27",
    "tags": [ "120HZ刷新率", "120W快充", "120倍变焦" ]
}
PUT /product/_doc/7
{
    "name" : "挨炮 SE2",
    "desc" :  "除了CPU,一无是处",
    "price" :  "3299",
        "lv":"旗舰机",
    "type":"手机",
    "createtime":"2020-07-21",
    "tags": [ "割韭菜", "割韭菜", "割新韭菜" ]
}
PUT /product/_doc/8
{
    "name" : "XS Max",
    "desc" :  "听说要出新款12手机了,终于可以换掉手中的4S了",
    "price" :  4399,
        "lv":"旗舰机",
    "type":"手机",
    "createtime":"2020-08-19",
    "tags": [ "5V1A", "4G全网通", "大" ]
}
PUT /product/_doc/9
{
    "name" : "小米电视",
    "desc" :  "70寸性价比只选,不要一万八,要不要八千八,只要两千九百九十八",
    "price" :  2998,
        "lv":"高端机",
    "type":"耳机",
    "createtime":"2020-08-16",
    "tags": [ "巨馍", "家庭影院", "游戏" ]
}
PUT /product/_doc/10
{
    "name" : "红米电视",
    "desc" :  "我比上边那个更划算,我也2998,我也70寸,但是我更好看",
    "price" :  2999,
    "type":"电视",
        "lv":"高端机",
    "createtime":"2020-08-28",
    "tags": [ "大片", "蓝光8K", "超薄" ]
}
PUT /product/_doc/11
{
  "name": "红米电视",
  "desc": "我比上边那个更划算,我也2998,我也70寸,但是我更好看",
  "price": 2998,
  "type": "电视",
  "lv": "高端机",
  "createtime": "2020-08-28",
  "tags": [
    "大片",
    "蓝光8K",
    "超薄"
  ]
}
分桶聚合查询
桶聚合 例:统计不同标签的商品数量
GET product/_search
{
  "aggs": {
    "tag_bucket": {
      "terms": {
        "field": "tags.keyword"
      }
    }
  }
}

不显示hits数据:size:0
GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_bucket": {
      "terms": {
        #查询不会被分词
        "field": "tags.keyword"
      }
    }
  }
}

排序
GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_bucket": {
      "terms": {
        "field": "tags.keyword",
        "size": 3,
        "order": {
          "_count": "desc"
        }
      }
    }
  }
}

## doc_values和field_data
GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_bucket": {
      "terms": {
        "field": "name"
      }
    }
  }
}

GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_bucket": {
      "terms": {
        "field": "name.keyword"
      }
    }
  }
}

POST product/_mapping
{
  "properties": {
    "name": {
      "type": "text",
      "analyzer": "ik_max_word",
      #通过修改fielddata为true来支持文本字段桶聚合查询
      "fielddata": true
    }
  }
}

GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_bucket": {
      "terms": {
        "size": 20,
        "field": "name"
      }
    }
  }
}
指标聚合查询
指标聚合,例:最贵、最便宜和平均价格三个指标
GET product/_search
{
  "size": 0,
  "aggs": {
    查询最大的价格
    "max_price": {
      "max": {
        "field": "price"
      }
    },
    查询最小的价格
    "min_price": {
      "min": {
        "field": "price"
      }
    },
    查询平均价格
    "avg_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

单个聚合查询所有指标
GET product/_search
{
  "size": 0,
  "aggs": {
    "price_stats": {
      查询所有指标
      "stats": {
        "field": "price"
      }
    }
  }
}

按照name去重的数量
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_count": {
      "cardinality": {
         #使用name时会进行分词后计算
        "field": "name"
      }
    }
  }
}

GET product/_search
{
  "size": 0,
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "name.keyword"
      }
    }
  }
}

对type计算去重后数量
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "lv.keyword"
      }
    }
  }
}

管道聚合 二次聚合,例:统计平均价格最低的商品分类
GET product/_search
{
  "size": 0,
  "aggs": {
     根据商品分类分桶聚合
    "type_bucket": {
      "terms": {
        "field": "type.keyword"
      },
      "aggs": {
        根据分桶查询结果再进行平均价格计算->管道聚合
        "price_bucket": {
          "avg": {
            "field": "price"
          }
        }
      }
    },
    查询最小的桶
    "min_bucket":{
      "min_bucket": {
        "buckets_path": "type_bucket>price_bucket"
      }
    }
  }
}

嵌套聚合
语法
GET product/_search
{
  "size": 0,
  "aggs": {
    "<agg_name>": {
      "<agg_type>": {
        "field": "<field_name>"
      },
      "aggs": {
        "<agg_name_child>": {
          "<agg_type>": {
            "field": "<field_name>"
          }
        }
      }
    }
  }
}

例:统计不同类型商品的不同级别的数量
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_lv": {
      "terms": {
        "field": "type.keyword"
      },
      "aggs": {
        "lv": {
          "terms": {
            "field": "lv.keyword"
          }
        }
      }
    }
  }
}

按照lv分桶 输出每个桶的具体价格信息
GET product/_search
{
  "size": 0, 
  "aggs": {
    "lv_price": {
      "terms": {
        "field": "lv.keyword"
      },
      "aggs": {
        "price": {
          "stats": {
            "field": "price"
          }
        }
      }
    }
  }
}

结合了上面两个例子,统计不同类型商品 不同档次的 价格信息 标签信息
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_agg": {
      "terms": {
        "field": "type.keyword"
      },
      "aggs": {
        "lv_agg": {
          "terms": {
            "field": "lv.keyword"
          },
          "aggs": {
            "price_stats": {
              "stats": {
                "field": "price"
              }
            },
            "tags_buckets": {
              "terms": {
                "field": "tags.keyword"
              }
            }
          }
        }
      }
    }
  }
}

统计每个商品类型中 不同档次分类商品中 平均价格最低的档次
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_bucket": {
      "terms": {
        "field": "type.keyword"
      },
      "aggs": {
        "lv_bucket": {
          "terms": {
            "field": "lv.keyword"
          },
          "aggs": {
            "price_avg": {
              "avg": {
                "field": "price"
              }
            }
          }
        },
        "min_bucket": {
          "min_bucket": {
            "buckets_path": "lv_bucket>price_avg"
          }
        }
      }
    }
  }
}

基于查询结果的聚合
GET product/_search
{
  "size": 0, 
  "query": {
    "range": {
      "price": {
        "gte": 5000
      }
    }
  }, 
  "aggs": {
    "tags_bucket": {
      "terms": {
        "field": "tags.keyword"
      }
    }
  }
}

基于filter的aggs
GET product/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "price": {
            "gte": 5000
          }
        }
      }
    }
  },
  "aggs": {
    "tags_bucket": {
      "terms": {
        "field": "tags.keyword"
      }
    }
  }
}

GET product/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "price": {
            "gte": 5000
          }
        }
      }
    }
  },
  "aggs": {
    "tags_bucket": {
      "terms": {
        "field": "tags.keyword"
      }
    }
  }
}

基于聚合的查询
GET product/_search
{
  "aggs": {
    "tags_bucket": {
      "terms": {
        "field": "tags.keyword"
      }
    }
  },
  "post_filter": {
    "term": {
      "tags.keyword": "性价比"
    }
  }
}

取消查询条件&&查询条件嵌套,例:最贵、最便宜和平均价格三个指标
GET product/_search
{
  "size": 10,
  "query": {
    "range": {
      "price": {
        "gte": 4000
      }
    }
  },
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
      }
    },
    "min_price": {
      "min": {
        "field": "price"
      }
    },
    "avg_price": {
      "avg": {
        "field": "price"
      }
    },
    "all_avg_price": {
      "global": {},
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    },
    "muti_avg_price": {
      "filter": {
        "range": {
          "price": {
            "lte": 4500
          }
        }
      }, 
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

聚合排序_count _key _term
GET product/_search
{
  "size": 0,
  "aggs": {
    "type_agg": {
      "terms": {
        "field": "tags",
        "order": {
          "_count": "desc"
        },
        "size": 10
      }
    }
  }
}

多级排序
GET product/_search?size=0
{
  "aggs": {
    "first_sort": {
      "terms": {
        "field": "type.keyword",
        "order": {
          "_count": "desc"
        }
      },
      "aggs": {
        "second_sort": {
          "terms": {
            "field": "lv.keyword",
            "order": {
              "_count": "asc"
            }
          }
        }
      }
    }
  }
}

多层排序
GET product/_search
{
  "size": 0,
  "aggs": {
    "tag_avg_price": {
      "terms": {
        "field": "type.keyword",
        "order": {
          "agg_stats>stats.sum": "desc"
        }
      },
      "aggs": {
        "agg_stats": {
          "filter": {
            "terms": {
              "type.keyword": ["耳机","手机","电视"]
            }
          },
          "aggs": {
            "stats": {
              "extended_stats": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

常用的查询函数,histogram 直方图 或者 柱状图
GET product/_search
{
  "aggs": {
    "price_range": {
      "range": {
        "field": "price",
        "ranges": [
          {
            "from": 0,
            "to": 1000
          },
          {
            "from": 1000,
            "to": 2000
          },
          {
            "from": 3000,
            "to": 4000
          },
          {
            "from": 4000,
            "to": 5000
          }
        ]
      }
    }
  }
}

GET product/_search?size=0
{
  "aggs": {
    "price_range": {
      "range": {
        "field": "createtime",
        "ranges": [
          {
            "from": "2020-05-01",
            "to": "2020-05-31"
          },
          {
            "from": "2020-06-01",
            "to": "2020-06-30"
          },
          {
            "from": "2020-07-01",
            "to": "2020-07-31"
          },
          {
            "from": "2020-08-01"
          }
        ]
      }
    }
  }
}

空值的处理逻辑 对字段的空值赋予默认值
GET product/_search?size=0
{
  "aggs": {
    "price_histogram": {
      "histogram": {
        "field": "price",
        "interval": 1000,
        "keyed": true,
        "min_doc_count": 0,
        "missing": 1999
      }
    }
  }
}

date-histogram,ms s m h d
GET product/_search?size=0
{
  "aggs": {
    "my_date_histogram": {
      "date_histogram": {
        "field": "createtime",
        "calendar_interval": "month",
        "min_doc_count": 0,
        "format": "yyyy-MM",
        "extended_bounds": {
          "min": "2020-01",
          "max": "2020-12"
        },
        "order": {
          "_count": "desc"
        }
      }
    }
  }
}

GET product/_search?size=0
{
  "aggs": {
    "my_auto_histogram": {
      "auto_date_histogram": {
        "field": "createtime",
        "format": "yyyy-MM-dd",
        "buckets": 180
      }
    }
  }
}

cumulative_sum
GET product/_search?size=0
{
  "aggs": {
    "my_date_histogram": {
      "date_histogram": {
        "field": "createtime",
        "calendar_interval": "month",
        "min_doc_count": 0,
        "format": "yyyy-MM", 
        "extended_bounds": {
          "min": "2020-01",
          "max": "2020-12"
        }
      },
      "aggs": {
        "sum_agg": {
          "sum": {
            "field": "price"
          }
        },
        "my_cumulative_sum":{
          "cumulative_sum": {
            "buckets_path": "sum_agg"
          }
        }
      }
    }
  }
}

## percentile 百分位统计 或者 饼状图
## https://www.elastic.co/guide/en/elasticsearch/reference/7.10/search-aggregations-metrics-percentile-aggregation.html

GET product/_search?size=0
{
  "aggs": {
    "price_percentiles": {
      "percentiles": {
        "field": "price",
        "percents": [
          1,
          5,
          25,
          50,
          75,
          95,
          99
        ]
      }
    }
  }
}
#percentile_ranks
#TDigest
GET product/_search?size=0
{
  "aggs": {
    "price_percentiles": {
      "percentile_ranks": {
        "field": "price",
        "values": [
          1000,
          2000,
          3000,
          4000,
          5000,
          6000
        ]
      }
    }
  }
}

深度分页问题

1、什么是深度分页(Deep paging)?

1.1 ES中 from+size分页

分页问题是Elasticsearch中最常见的查询场景之一,正常情况下分页代码如实下面这样的:

GET order_2290w/_search
{
  "from": 0,
  "size": 5
}

输出结果如下图:
在这里插入图片描述
很好理解,即查询第一页的 5条数据。图中数字2即返回的五条文档数据。但是如果我们查询的数据页数特别大,达到什么程度呢?当 from + size大于 10000的时候,就会出现问题,如下图报错信息所示:
在这里插入图片描述
报错信息的解释为当前查询的结果超过了 10000的最大值。那么疑问就来了,明明只查询了5条数据,为什么它计算最大值要加上我from的数量呢?而且Elasticsearch不是号称PB及数据秒级查询,几十亿的数据都没问题,怎么还限制最大查询前10000条数据呢?这里有一个字很关键:“”,前10000条意味着什么?意味着数据肯定是按照某种顺序排列的,ES中如果不人工指定排序字段,那么最终结果将按照相关度评分排序。

分布式系统都面临着同一个问题,数据的排序不可能在同一个节点完成。一个简单的需求,比如:

1.2 案例解释什么是深分页

10万名高考生中查询成绩为的 10001-10100位的 100名考生的信息。
看似简单的查询其实并不简单,我们来画图解释一下:
在这里插入图片描述
假设10万名考生的考试信息被存放在一个 exam_info索引中,由于索引数据在写入是并无法判断在执行业务查询时的具体排序规则,因此排序是随机的。而由于ES的分片和数据分配策略为了提高数据在检索时的准确度,会把数据尽可能均匀的分布在不同的分片。假设此时我们有五个分片,每个分片中承载 2万条有效数据。按照需求我们需要去除成绩在 1000110100的一百名考生的信息,就要先按照成绩进行倒序排列。然后按照 page_size: 100&page_index: 101进行查询。即查询按照成绩排序,第 101页的100位学员信息。

单机数据库的查询逻辑很简单,先按照把10万学生成绩排序,然后从 前10100条数据数据中取出第 10001-10100条。即按照100为一页的第101页数据。

但是分布式数据库不同于单机数据库,学员成绩是被分散保存在每个分片中的,你无法保证要查询的这一百位学员的成绩一定都在某一个分片中,结果很有可能是存在于每个分片。换句话说,你从任意一个分片中取出的 前10100位学员的成绩,都不一定是总成绩的 前10100。更不幸的是,唯一的解决办法是从每个分片中取出当前分片的 前10100名学员成绩,然后汇总成 50500条数据再次排序,然后从排序后的这 50500个成绩中查询前 10100的成绩,此时才能保证一定是整个索引中的成绩的前 10100名。

如果还不理解,我再举个例子用来类比:从保存了世界所有国家短跑运动员成绩的索引中查询短跑世界前三,每个国家类比为一个分片的数据,每个国家都会从国家内选出成绩最好的前三位参加最后的竞争,从每个国家选出的前三名放在一起再次选出前三名,此时才能保证是世界的前三名。

2、深度分页会带来什么问题?

从上面案例中不难看出,每次有序的查询都会在每个分片中执行单独的查询,然后进行数据的二次排序,而这个二次排序的过程是发生在heap中的,也就是说当你单次查询的数量越大,那么堆内存中汇总的数据也就越多,对内存的压力也就越大。这里的单次查询的数据量取决于你查询的是第几条数据而不是查询了几条数据,比如你希望查询的是第 10001-10100这一百条数据,但是ES必须将前 10100全部取出进行二次查询。因此,如果查询的数据排序越靠后,就越容易导致OOM(Out Of Memory)情况的发生,频繁的深分页查询会导致频繁的FGC。
ES为了避免用户在不了解其内部原理的情况下而做出错误的操作,设置了一个阈值,即 max_result_window,其默认值为 10000,其作用是为了保护堆内存不被错误操作导致溢出。因此也就出现了文章一开始所演示的问题。

3、max_result_window参数

max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。

在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大约好。

4、深度分页问题的常见解决方案?

4.1 尝试避免深度分页

目前人类对抗疾病最有效的手段:打疫苗。没错,能防止其发生的问题总比发生之后再治理来的强。同样,解决深度分页问题最好的办法也是预防,也就是能避免最好是避免使用深度分页。我相信不服气的小伙儿伴已经满嘴质疑了,我们怎么能要求用户去做什么、不做什么呢?用户想深度分页检索你凭什么不让呢?技术要服务于业务!不能用妥协用户体验来解决技术问题…

带着这些质疑,我们先来看一看众多大型搜索引擎面对深度分页问题是如何处理的:
首先是以 百度谷歌为代表的全文搜索引擎:
在这里插入图片描述
在这里插入图片描述
谷歌、百度目前作为全球和国内做大的搜索引擎(不加之一应该没人反对吧。O(∩_∩)O~)。不约而同的在分页条中删除了“跳页”功能,其目的就是为了避免用户使用深度分页检索。

这里也许又双叒叕会有人不禁发问:难道删除“跳页”就能阻止用户查询很多页以后的数据了吗?我直接狂点下一页不也是深度分页?好我暂时先不反驳这里的提问,但是我也发出一个反问,至少删除跳页,可以阻挡哪些刻意去尝试深度分页的“恶意用户”,真正想通过搜索引擎来完成自己检索需求的用户,通常来说都会首先查看第一页数据,因为搜索引擎是按照“相关度评分”进行排名的,也就是说,第一页的数据很往往是最符合用户预期结果的(暂时不考虑广告、置顶等商业排序情况)。

下面我们再看一下以中国最大电商平台“淘宝”为代表的垂直搜索引擎是怎么解决的:
在这里插入图片描述
在这里插入图片描述
我们分别尝试搜索较大较为宽泛的商品种类,以使其召回结果足够多(这里以 手机衣服为例,已屏蔽掉了商品品牌和型号,以避免广告嫌疑(#.#))。

虽然这里没有删除“跳页”功能,但这里可以看到一个有趣的现象,不管我们搜索什么内容,只要商品结果足够多,返回的商品列表都是仅展示前100页的数据,我们不难发现,其实召回的商品被“截断”了,不管你有多少,我都只允许你查询前100页,其实这本质和ES中的 max_result_window作用是一样的,都是限制你去搜索更深页数的数据。

手机端APP就更不用说了,直接是下拉加载更多,连分页条都没有,相当于你只能点击“下一页”。

那么回到当初的问题,我们牺牲了用户体验了吗?

不仅没有,而且用户体验大大提升了!

  • 首先那些直接输入很大的页码,直接点击跳页的用户,本身就是恶意用户,组织其行为是理所应当,因此删除“跳页”,功能并无不妥!
  • 其次,真正的通过搜索引擎来检索其意向数据的用户,只关心前几页数据,即便他通过分页条跳了几页,但这种搜索并不涉及深度分页,即便它不停的点下去,我们也有其它方案解决此问题。
  • 类似淘宝这种直接截断前100页数据的做法,看似暴力,其实是在补习生用户体验的前提下,极大的提升了搜索的性能,这也变相的为哪些“正常用户”,提升了搜索体验,何乐不为?

官方已不推荐使用滚动查询进行深度分页查询,因为无法保存索引状态。

4.2.1 适合场景

单个滚动搜索请求中检索大量结果,即非“C端业务”场景

4.2.2 使用
GET <index>/_search?scroll=1m
{
  "size": 100
}

时间单位:

d Days
h Hours
m Minutes
s Seconds
ms Milliseconds
micros Microseconds
nanos Nanoseconds

为了使用滚动,初始搜索请求应该 scroll在查询字符串中指定参数,该 参数告诉 Elasticsearch 应该保持“搜索上下文”多长时间,例如 ?scroll=1m。结果如下:

{
  "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABVWsWN3Q4dDJjcVVRQ0NBbllGMmFqN0ZVZw==",  
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 21921750,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      ...
    ]
  }
}

上述请求的结果包含一个 _scroll_id,应将其传递给 scrollAPI 以检索下一批结果。

滚动返回在初始搜索请求时与搜索匹配的所有文档。它会忽略对这些文档的任何后续更改。该 scroll_id标识一个搜索上下文它记录身边的一切Elasticsearch需要返回正确的文件。搜索上下文由初始请求创建,并由后续请求保持活动状态。

4.2.3 注意
  • Scroll上下文的存活时间是滚动的,下次执行查询会刷新,也就是说,不需要足够长来处理所有数据,它只需要足够长来处理前一批结果。保持旧段处于活动状态意味着需要更多的磁盘空间和文件句柄。确保您已将节点配置为具有充足的空闲文件句柄。
  • 为防止因打开过多Scrolls而导致的问题,不允许用户打开超过一定限制的Scrolls。默认情况下,打开Scrolls的最大数量为 500。此限制可以通过 search.max_open_scroll_context集群设置进行更新 。
4.2.4 清除滚动上下文

scroll超过超时后,搜索上下文会自动删除。然而,保持Scrolls打开是有代价的,因此一旦不再使用该 clear-scrollAPI ,就应明确清除Scroll上下文

#清除单个
DELETE /_search/scroll
{
  "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
#清除多个
DELETE /_search/scroll
{
  "scroll_id" : [
    "scroll_id1",
    "scroll_id2"
  ]
}
#清除所有
DELETE /_search/scroll/_all

4.3 Search After

  • 不支持向前搜索
  • 每次只能向后搜索1页数据
  • 适用于C端业务
posted @ 2023-07-04 23:05  rbcd  阅读(52)  评论(0)    收藏  举报