ES 索引增删改查
创建索引
PUT /索引名
PUT /product
添加数据
此方式自动创建mapping
PUT /product/_doc/1
{
"name": "xie zi",
"desc": "red xie zi",
"price": 12.12,
"tags":["xiezi","red"]
}
PUT /product/_doc/2
{
"name": "xie zi",
"desc": "red xie zi",
"price": 12.12,
"tags":["xiezi","red"]
}
PUT /product/_doc/3
{
"name": "xie zi",
"desc": "red xie zi",
"price": 14.23,
"tags":["xiezi","red"]
}
获取mapping
GET product/_mapping
获取数据
GET /product/_doc/1
修改数据
#修改所有字段
PUT /product/_doc/3
{
"name": "xie zi",
"desc": "red xie zi",
"price": 189.23,
"tags":["xiezi","red"]
}
#修改所有字段
PUT /product/_doc/3
{
"price": 14.23,
}
#修改指定字段
POST /product/_update/3
{
"doc":{
"price":888
}
}
# 根据条件修改
POST ads_ip_value/_update_by_query?wait_for_completion=false
{
"query": {
"term": {
"supplier": "Oracle"
}
},
"script": {
"source": "ctx._source.supplier ='Or'"
}
}
wait_for_completion=false 查询的结果将在后台异步生成,而不会阻塞当前请求的响应。当查询结果准备好后,您可以使用get任务API来获取结果
GET /_tasks/maiOCBdWQE2AWNyXYNmsYw:65916486
删除索引
DELETE /product
删除数据
DELETE /product/_doc/1
根据条件删除
POST /product/_delete_by_query
{
"query" : {
"term" : {
"name.keyword" : {
"value" : "xie zi 2323",
"boost" : 1.0
}
}
}
}
Mapping
添加mapping
# 创建索引
PUT product/_mapping
{
"properties":{
"id": {
"type": "long"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {
"type": "double"
},
"detail": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "text"
}
}
}
添加字段
# 添加字段
PUT product/_mapping/_doc?include_type_name=true
{
"properties":{
"quantity": {
"type": "integer"
}
}
}
分词器
GET /_analyze
{
"analyzer": "standard",
"text":"if you miss the train I am on,you will know that I am gone"
}
GET /_analyze
{
"analyzer": "standard",
"text":"我爱中华人民共和国"
}
创建数据
PUT product
PUT product/_mapping
{
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {
"type": "double"
},
"number": {
"type": "integer"
},
"detail": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "text"
}
}
}
PUT /product/_doc/1
{
"id":1,
"name": "耐克鞋子",
"desc": "红色的42弹力回升",
"price": 89.9,
"number":200,
"tags":["红色","男的"]
}
PUT /product/_doc/2
{
"id":2,
"name": "回力小白鞋",
"desc": "经典小白鞋,简单就是经典",
"price": 90.9,
"number":100,
"tags":["白色","男的"]
}
PUT /product/_doc/3
{
"id":3,
"name": "回力板鞋",
"desc": "回力",
"price": 100,
"number":80,
"tags":["黑的","黄的","男的"]
}
PUT /product/_doc/4
{
"id":4,
"name": "上衣",
"desc": "男士风衣",
"price": 8888,
"number":60,
"tags":["黑色","男的"]
}
PUT /product/_doc/5
{
"id":5,
"name": "毛衣",
"desc": "男士毛衣",
"price": 555.5,
"number":10,
"tags":["红色","男的"]
}
PUT /product/_doc/9
{
"id":9,
"name": "大衣",
"desc": "棕色大衣",
"price": 564,
"number":63,
"tags":["棕色","女的","38妇女"]
}
PUT /product/_doc/10
{
"id":10,
"name": "裤子",
"desc": "red xie zi",
"price": 1245,
"number":15,
"tags":["红色","女的","38妇女"]
}
PUT /product/_doc/11
{
"id":12,
"name": "耐克鞋",
"desc": "红色的42弹力回升",
"price": 89.9,
"number":200,
"tags":["红色","男的"]
}
PUT /product/_doc/12
{
"id":12,
"name": "耐克",
"desc": "红色的42弹力回升",
"price": 89.9,
"number":200,
"tags":["红色","男的"]
}
查询
查询所有
{
"query": {
"match_all": {
},
"track_total_hits":true
}
}
term
term是代表完全匹配,也就是精确查询,搜索前不会再对搜索词进行分词拆解。
GET product/_doc/_search
{
"query":{
"term":{
"name.keyword":"耐克鞋子"
}
}
}
GET product/_doc/_search
{
"query":{
"term":{
"name":"鞋"
}
}
}
terms
也是精确查询只不过是匹配多个类似于in
# 查找名称里面有(鞋和毛)的字段
GET product/_doc/_search
{
"query":{
"terms":{
"name":["鞋","毛"]
}
}
}
match
模糊匹配会对搜索条件进行分词
GET product/_doc/_search
{
"query":{
"match":{
"name":"我喜欢耐克毛衣"
}
}
}
GET product/_doc/_search
{
"query":{
"match":{
"name.keyword":"耐克鞋子"
}
}
}
#匹配查询(精准查找)
GET product/_doc/_search
{
"query": {
"match": {
"name": {
"query": "耐克",
"operator": "and"
}
}
}
}
match_phrase
match_phrase 称为短语搜索,要求所有的分词必须同时出现在文档中,同时位置必须紧邻一致。
GET product/_doc/_search
{
"query":{
"match_phrase":{
"name":"耐克"
}
}
}
multi_match
多字段查询
# name或者desc有克的
GET product/_doc/_search
{
"query": {
"multi_match": {
"query": "克子",
"fields": ["name","desc"]
}
}
}
bool 查询
#must查询
GET product/_doc/_search
{
"query": {
"bool": {
"must": [
{"match": {"name": "耐克"}},
{"term": {"price": "89.9"}}
]
}
}
}
# should
GET product/_doc/_search
{
"query": {
"bool": {
"should": [
{"match": {"name": "耐克"}},
{"term": {"price": "100"}}
]
}
}
}
指定返回字段和不返回字段
GET product/_doc/_search
{
"_source":{
"exclude": ["tags","number"],
"include": ["name"]
},
"query": {
"match_all": {
}
}
}
返回数据版本
GET product/_doc/_search
{
"version":true,
"query": {
"match_all": {
}
}
}
排序
GET product/_doc/_search
{
"query": {
"match_all": {
}
},
"sort":[
{"number":"desc"},{"price":"asc"}
]
}
范围查询(range)
gt
:>
大于lt
:<
小于gte
:>=
大于或等于lte
:<=
小于或等于
GET product/_doc/_search
{
"query": {
"range":{
"number":{
"gt":"0",
"lt":"100"
}
}
}
}
GET product/_doc/_search
{
"query": {
"range": {
"date": {
"gt":"now-1h" //查询距离现在一小时之内的文档,直接使用now减去一小时即可
}
}
}
}
GET product/_doc/_search
{
"query": {
"range": {
"date": {
"gt": "2010-11-11 00:00:00", //指定时分秒查询
"lt": "2012-12-31 00:00:00"
}
}
}
}
短语模糊查询(wildcard)
- 允许使用通配符*和?进行查询
*
: 代表一个或者多个字符?
:代表任意一个字符
GET product/_doc/_search
{
"query": {
"wildcard": {
"name":"回*"
}
}
}
GET product/_doc/_search
{
"query": {
"wildcard": {
"name.keyword":"回?小白鞋"
}
}
}
模糊查询 fuzzy
搜索的时候,可能输入的搜索文本会出现误拼写的情况
doc1: hello world
doc2: hello java
搜索: hallo world
fuzzy搜索以后,会自动尝试将你的搜索文本进行纠错,然后去跟文本进行匹配
fuzziness,你的搜索文本最多可以纠正几个字母去跟你的数据进行匹配,默认如果不设置,就是2
GET product/_doc/_search
{
"query": {
"fuzzy": {
"name":"灰"
}
}
}
GET product/_doc/_search
{
"query": {
"match": {
"name": {
"query": "灰力",
"fuzziness": "AUTO",
"operator": "or"
}
}
}
}
null值的查询
exists
这个语句用来查询存在值的信息,如果和must结合表示查询不为null的数据,如果must_not集合表示查询为null
的数据,如下:
#查询name=null的数据
GET product/_doc/_search
{
"query": {
"bool": {
"must_not":{
"exists":{
"field":"name"
}
}
}
}
}
#查询name!=null的数据
GET product/_doc/_search
{
"query": {
"bool": {
"must":{
"exists":{
"field":"name"
}
}
}
}
}
filter查询
- 缓存,不返回相关性,速度比query快
- 注意:filter过滤查询必须要配合bool查询使用,执行时,先执行filter 过滤,然后在执行query,且ES会自动缓存经常使用的过滤器,以加快性能
GET product/_doc/_search
{
"query":{
"bool":{
"must":[
{"term":{
"name":{
"value":"回"
}
}
}
],
"filter":[
{
"range":{
"number":{
"gte":0,
"lte":100
}
}
}
]
}
}
}
bool过滤查询
- 语法如下:
must
:所有的语句都 必须(must) 匹配,与AND
等价。must_not
:所有的语句都 不能(must not) 匹配,与NOT
等价。should
:至少有一个语句要匹配,与OR
等价。
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
}
}
sql 查询
POST /_sql?format=txt
{
"query": "SELECT avg(valueMicros) as valueMicros , ip,country FROM adsipvalue group by ip,country order by valueMicros asc limit 1000"
}
sql 转 dsl
POST /_sql/translate
{"query": "SELECT avg(valueMicros) as valueMicros , ip,country FROM ads_ip_value group by ip,country order by valueMicros desc limit 1000"}
简单计算查询
GET metric/_search
{
"from": 0,
"size": 1, //取最新的一条数据,下面按时间排序了
"query": {
"match": { //筛选与cpu相关的指标
"metricset.name":"cpu"
}
},
"sort":{ //按时间排序
"timestamp":"desc"
},
"_source": { //控制显示的字段
"includes": ["cpu_pct","system.cpu.cores","system.cpu.total.pct","timestamp"]
},
"script_fields": {
"cpu_pct": { //自己定义存放结果的名称
"script": {
"lang": "expression", //使用expression脚本
"source": "doc['system.cpu.total.pct'] / doc['system.cpu.cores']"
}
}
}
}
分页查询
ES支持的三种分页查询方式
- From + Size 查询
- Scroll 遍历查询
- Search After 查询
From + Size
es 默认采用的分页方式是 from+ size 的形式,默认返回前10个匹配的匹配项。
其中: from:未指定,默认值是0,注意不是1,代表当前页返回数据的起始值。
size:未指定,默认值是 10,代表当前页返回数据的条数。
优缺点
但是这种分页方式,随着深度分页的递进,对内存和查询效率是不友好的,在深度分页的情况下,这种使用方式效率是非常低的
,除了效率上的问题,还有一个无法解决的问题是,es目前支持最大的 skip 值是 max_result_window,默认为 10000 。
也就是当 from + size > max_result_window 时,es 将返回错误。
解决防范
PUT product/_settings
{
"max_result_window":1000000
}
官方建议:
1、避免过度使用 from 和 size 来分页或一次请求太多结果。
2、不推荐使用 from + size 做深度分页查询的核心原因:
搜索请求通常跨越多个分片,每个分片必须将其请求的命中内容以及任何先前页面的命中内容加载到内存中。
对于翻页较深的页面或大量结果,这些操作会显著增加内存和 CPU 使用率,从而导致性能下降或节点故障。
GET product/_doc/_search
{
"from":0,
"size":2,
"query": {
"match_all": {
}
}
}
## 超过10000会报错
GET product/_doc/_search
{
"from":10000,
"size":2,
"query": {
"match_all": {
}
}
}
Scroll
有一种查询场景,我们需要一次性或者每次查询大量的文档,但是对实时性要求并不高。
ES针对这种场景提供了scroll api的方案。这个方案牺牲了实时性,但是查询效率确实非常高。
不要把scroll用于实时请求,它主要用于大数据量的场景。例如:将一个索引的内容索引到另一个不同配置的新索引中。
GET product/_doc/_search?scroll=1m
{
"size":2,
"query": {
"match_all": {
}
}
}
GET /_search/scroll
{
"scroll":"1m",
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFjJkR0hXSGtBU0oyQlR0MERoWTJBNlEAAAAAAA4YbRZjSktBZnIzUFFLR1JtdGJVMFRyM3pB"
}
Scroll API 原理上是对某次查询生成一个游标 scroll_id , 后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。
所有文档获取完毕之后,需要手动清理掉 scroll_id 。虽然es 会有自动清理机制,但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集映像,并且会占用文件描述符。所以用完之后要及时清理。使用 es 提供的 CLEAR_API 来删除指定的 scroll_id
删除视图快照(DELETE请求)
DELETE /_search/scroll
{ "scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFjJkR0hXSGtBU0oyQlR0MERoWTJBNlEAAAAAAA4ZNhZjSktBZnIzUFFLR1JtdGJVMFRyM3pB"
}
优缺点
scroll查询的相应数据是非实时的,scoll滚动搜索技术,一批一批查询。scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是查询不到的,并且保留视图快照需要足够的堆内存空间。
官方文档强调:不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000+ 结果时,推荐使用:PIT + search_after。
search after
当使用search_after时,from值必须设置为0或者-1
官方的建议scroll 并不适用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。
那么在实时情况下如果处理深度分页的问题呢?
es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。search after利用实时有游标来帮我们解决实时滚动的问题,简单来说前一次查询的结果会返回一个唯一的字符串,下次查询带上这个字符串,进行`下一页`的查询,一看觉得和Scroll差不多,search_after有点类似scroll,但是和scroll又不一样,它提供一个活动的游标,通过上一次查询最后一条数据来进行下一次查询。
基本思想:searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
GET product/_doc/_search
{
"size":2,
"query": {
"match_all": {
}
},
"sort":{
"id":"desc"
}
}
GET product/_doc/_search
{
"size":2,
"query": {
"match_all": {
}
},
"sort":{
"id":"desc"
},
"search_after": [9] //这个值与上次查询最后一条数据的sort值一致,支持多个
}
GET product/_doc/_search
{
"size":2,
"query": {
"match_all": {
}
},
"sort":[{
"id":"desc"
},{
"price":"ASC"
}
]
}
GET product/_doc/_search
{
"size":2,
"query": {
"match_all": {
}
},
"sort":[{
"id":"desc"
},{
"price":"ASC"
}
],
"search_after": [11,89.9]
}
优缺点
Search_after的缺点就是不能自由跳页,search_after 查询仅支持向后翻页。 不严格受制于
max_result_window,可以无限制往后翻页,单次请求值不能超过 max_result_window,
但总翻页结果集可以超过,那自然就无法应用到业务中的分页查询了。
聚合查询
官方案例参考
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html
分桶聚合
就是sql的group by
# 设置size=0的意思就是,仅返回聚合查询结果,不返回普通query查询结果
GET product/_search
{
"size" : 0,
"aggs": {
"group":{
"terms":{
"field": "name.keyword"
}
}
}
}
对应的Java代码
SearchRequest searchRequest = new SearchRequest("index");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.aggregation(AggregationBuilders.terms("group").field("name.keyword"));
多字段groupBy**(composite复合聚合)**
GET product/_search
{
"size" : 0,
"aggs": {
"groupby" : {
"composite" : {
"size" : 1000,
"sources" : [
{
"name" : {
"terms" : {
"field" : "name.keyword"
}
}
},
{
"number" : {
"terms" : {
"field" : "number"
}
}
}
]
}
}
}
}
对应的Java代码
SearchRequest searchRequest = new SearchRequest("索引名字");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//多字段分组的list
List<CompositeValuesSourceBuilder<?>> listValuesSource = new ArrayList<>();
TermsValuesSourceBuilder valuesSourceIp = new TermsValuesSourceBuilder("ip");
valuesSourceIp.field("ip");
listValuesSource.add(valuesSourceIp);
TermsValuesSourceBuilder valuesSourceProject = new TermsValuesSourceBuilder("country");
valuesSourceProject.field("country");
listValuesSource.add(valuesSourceProject);
// 设置复合桶将多字段分组list放进去
CompositeAggregationBuilder compositeAggregationBuilder = AggregationBuilders
.composite("agg_composite_project_ip",listValuesSource)
.subAggregation(AggregationBuilders.avg("valueMicrosAvg").field("valueMicros"));
compositeAggregationBuilder.size(100);
//主桶:设置要聚合的字段,sql
searchSourceBuilder.aggregation(compositeAggregationBuilder);
searchSourceBuilder.size(0);
searchRequest.source(searchSourceBuilder);
SearchResponse search = adsIpValueEsMapper.search(searchRequest, RequestOptions.DEFAULT);
search.getAggregations().get("agg_composite_project_ip");
指标聚合
#求和
GET product/_search
{
"size" : 0,
"aggs": {
"number-sum":{
"sum":{
"field": "number"
}
}
}
}
# 求平均值
GET product/_search
{
"size" : 0,
"aggs": {
"number-sum":{
"avg":{
"field": "number"
}
}
}
}
# max
GET product/_search
{
"size" : 0,
"aggs": {
"number-sum":{
"max":{
"field": "number"
}
}
}
}
# min
GET product/_search
{
"size" : 0,
"aggs": {
"number-sum":{
"min":{
"field": "number"
}
}
}
}
多字段指标聚合
stats 会计算出 count,min,avg,sum
GET product/_search
{
"size": 0,
"aggregations": {
"number_avg": {
"avg": {
"field": "number"
}
},
"price_stats": {
"stats": {
"field": "price"
}
}
}
}
分桶加指标聚合
{
"size" : 0,
"_source" : false,
"aggregations" : {
"groupby" : {
"composite" : {
"size" : 1000,
"sources" : [
{
"name" : {
"terms" : {
"field" : "name.keyword",
"missing_bucket" : true,
"order" : "asc"
}
}
},
{
"desc" : {
"terms" : {
"field" : "desc.keyword",
"missing_bucket" : true,
"order" : "asc"
}
}
}
]
},
"aggregations" : {
"avg" : {
"avg" : {
"field" : "number"
}
},
"stats" : {
"stats" : {
"field" : "price"
}
}
}
}
}
}
Date histogram聚合
{
"aggs" : {
"sales_over_time" : { // 聚合查询名字,随便取一个
"date_histogram" : { // 聚合类型为: date_histogram
"field" : "date", // 根据date字段分组
"calendar_interval" : "month", // 分组间隔:month代表每月、支持minute(每分钟)、hour(每小时)、day(每天)、week(每周)、year(每年)
"format" : "yyyy-MM-dd" // 设置返回结果中桶key的时间格式
}
}
}
}
POST /ads_analyse_day_s1/_search
{
"size": 0,
"aggs" : {
"sales_over_time" : {
"date_histogram" : {
"field" : "createTime",
"calendar_interval" : "hour",
"format" : "yyyy-MM-dd HH"
}
}
}
}
Range聚合
GET /_search
{
"aggs" : {
"price_ranges" : { // 聚合查询名字,随便取一个
"range" : { // 聚合类型为: range
"field" : "price", // 根据price字段分桶
"ranges" : [ // 范围配置
{ "to" : 100.0 }, // 意思就是 price <= 100的文档归类到一个桶
{ "from" : 100.0, "to" : 200.0 }, // price>100 and price<200的文档归类到一个桶
{ "from" : 200.0 } // price>200的文档归类到一个桶
]
}
}
}
}
默认key的值不太友好,尤其开发的时候,不知道key长什么样子,处理起来比较麻烦,我们可以为每一个分桶指定一个有意义的名字。
GET /_search
{
"aggs" : {
"price_ranges" : {
"range" : {
"field" : "price",
"keyed" : true,
"ranges" : [
// 通过key参数,配置每一个分桶的名字
{ "key" : "cheap", "to" : 100 },
{ "key" : "average", "from" : 100, "to" : 200 },
{ "key" : "expensive", "from" : 200 }
]
}
}
}
}
管道聚合
管道聚合 : 对桶聚合和管道聚合的计算结果进行聚合计算、排序、截取等操作。
eg:根据名称分组取组内sum价格最高的
POST product/_search
{
"size": 0,
"aggs": {
"groupName": {
"terms": {
"field": "name.keyword"
},
"aggs": {
"sales": {
"sum": {
"field": "price"
}
}
}
},
"max_monthly_sales": {
"max_bucket": {
"buckets_path": "groupName>sales"
}
}
}
}
buckets_path:指定聚合的名称,支持多级嵌套聚合。 其他参数:
gap_policy 当管道聚合遇到不存在的值,有点类似于term等聚合的(missing)时所采取的策略,可选择值为:skip、insert_zeros。
skip:此选项将丢失的数据视为bucket不存在。它将跳过桶并使用下一个可用值继续计算。
insert_zeros:默认使用0代替。
format 用于格式化聚合桶的输出(key)。
- 桶脚本 bucket_script
通过bucket_script可以使用脚本进行更复杂的数据提取和计算
下面是官方例子
GET /_search
{
"size": 0,
"runtime_mappings": {
"normalized_genre": {
"type": "keyword",
"script": """
String genre = doc['genre'].value;
if (doc['product'].value.startsWith('Anthology')) {
emit(genre + ' anthology');
} else {
emit(genre);
}
"""
}
},
"aggs": {
"genres": {
"terms": {
"field": "normalized_genre"
}
}
}
}
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_per_month": {
"date_histogram": {
"field": "date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": {
"field": "price"
}
},
"t-shirts": {
"filter": {
"term": {
"type": "t-shirt"
}
},
"aggs": {
"sales": {
"sum": {
"field": "price"
}
}
}
},
"t-shirt-percentage": {
"bucket_script": {
"buckets_path": {
"tShirtSales": "t-shirts>sales",
"totalSales": "total_sales"
},
"script": "params.tShirtSales / params.totalSales * 100"
}
}
}
}
}
}