ElasticSearch 是一款强大的、开源的、分布式的搜索与分析引擎,简称 ES,它提供了实时搜索聚合分析两大功能。

0,ES 与关系型数据库类比

ElasticSearch 关系型数据库
索引
文档
字段
Mapping / 映射 表定义
DSL SQL

ES 与关系型数据库的区别:

区别 ElasticSearch 关系型数据库
索引方式 倒排索引 B-Tree 结构
事务支持 不支持 支持
扩展方式 天生支持分片,易扩展 需借助第三方组件进行分库分表,不易使用
查询速度 为搜索而生,具有实时性 随着数据量的增大,搜索变慢

Mapping 的特点:

  • 用于定义文档中字段的类型
  • 文档中某字段的类型一旦确定就不能更改,因为 ES 会根据相应的类型,去建立索引信息
  • 字段的类型虽然不能更改,但是 Mapping 中可以增加字段

1,查看集群信息

#  查看 ES 集群信息
> curl http://localhost:9200/ 

# 查看索引 mappings 
> curl -XGET host:port/$index_name/_mappings?pretty

# 查看索引 settings
> curl -XGET host:port/$index_name/_settings?pretty 

# 查看 ES 中安装的插件
> elasticsearch-plugin list

# 安装插件
> elasticsearch-plugin install analysis-icu
  • GET /_cat/plugins?v 查看 ES 中的插件
  • GET /_cluster/health 查看集群的健康状态
  • GET /_cat/nodes?v 查看所有的节点

2,查看索引信息

  • GET /_cat/indices?v 查看所有的索引信息
  • GET /_cat/indices?v&health=green 查看状态为 green 的索引
  • GET /_cat/indices?v&s=docs.count:desc 按照文档个数排序
  • GET /_cat/indices?v&h=i,tm&s=tm:desc 查看索引使用的内存大小
  • GET /_cat/indices/index_prefix*?v&s=index 查看所有的索引名以 index_prefix 为前缀的索引
  • GET /_cat/indices/index_prefix*?pri&v&h=health,index,pri,rep,docs.count,mt 查看指定索引的指定信息

3,创建索引

每个索引都可以定义自己的 Mappings 和 Settings:

  • Mappings:用于设置文档字段的类型。
  • Settings:用于设置不同的数据分布。

1,创建索引

索引操作:

  • PUT $index_name 创建索引
    • 可加入 Mappings 和 Settings 信息(也可不加,采用默认设置)
    • 不可重复创建,否则会报错
    • PUT $index_name/_mapping 创建索引,只有 mapping 信息
    • PUT $index_name/_settings 创建索引,只有 settings 信息
  • HEAD $index_name:查看索引是否存在,通过 http 响应的状态码查看
    • 状态码 200:表示索引存在
    • 状态码 404:表示索引不存在
  • GET $index_name 查看指定索引相关信息(Mappings 和 Settings)
    • GET $index_name/_mapping 只查看 mapping
    • GET $index_name/_settings 只查看 settings
  • DELETE $index_name 删除整个索引,要谨慎使用!
    • 当删除的 index_name 存在时,会删除整个索引内容
    • 当删除的 index_name 不存在时,ES 会返回 404 错误
  • POST $index_name/_close 关闭索引
    • 索引被关闭后,将不再能被使用,直到被打开
  • POST $index_name/_open 打开索引

创建索引:

PUT $index_name
{
  "mappings":{
    "properties":{
      "col_1":{
        "type":"text"
      },
      "col_2":{
        "type":"keyword"
      },
      ...
    }
  },
  "settings":{
    "number_of_shards":5,	# 主分片数,不可更改
    "number_of_replicas":2	# 副本分片数,默认为 0
  }
}

虽然 ES 可以自动推断字段的数据类型,但最好还是自定义 mapping,因为,ES 自推断的类型,有可能并不是你想要的。

增加索引字段

虽然索引中的字段的类型是不能改变的,但是可以为索引增加字段。

# 为索引增加字段 col_3,类型为 keyword
POST $index_name/_mapping 
{
  "properties": {
    "col_3": {
      "type":"keyword"
    }
  }
}

其实字段类型是否能够修改,分两种情况:

  • 对于新增字段
    • 如果 mappings._doc.dynamicture,当有新字段写入时,Mappings 会自动更新。
    • 如果 mappings._doc.dynamicfalse,当有新字段写入时,Mappings 不会更新;新增字段不会建立倒排索引,但是信息会出现在 _source 中。
    • 如果 mappings._doc.dynamicstrict,当有新字段写入时,写入失败。
  • 对于已有字段
    • 字段的类型不允许再修改。因为如果修改了,会导致已有的信息无法被搜索
    • 如果希望修改字段类型,需要 Reindex 重建索引

dynamic 有 3 种取值,使用下面 API 可以修改 dynamic 的值:

PUT $index_name/_mapping
{
  "dynamic": false/true/strict
}

2,重建索引

有时候我们需要重建索引,比如以下情况:

  • 索引的 mappings 发生改变:比如字段类型或者分词器等发生更改。
  • 索引的 settings 发生改变:比如索引的主分片数发生更改。
  • 集群内或集群间需要做数据迁移。

ES 中提供两种重建 API:

  • Update by query:在现有索引上重建索引
    • 改变现有 mappings 时,使用该操作
  • Reindex:在其它索引上重建索引
    • 先创建一个新的索引 mappings
    • 再使用 Reindex 迁移数据
# Reindex API
POST _reindex
{
  "source": { # 指定原有索引
    "index": "blogs"
  },
  "dest": {   # 指定目标索引
    "index": "blogs_new"
  }
}

4,文档相关操作

文档的删除与更新:

  • ES 中文档的删除操作不会马上将其删除,而是会将其标记到 del 文件中,在后期合适的时候(比如 Merge 阶段)会真正的删除。
  • ES 中的文档是不可变更的,更新操作会将旧的文档标记为删除,同时增加一个新的字段,并且文档的 version 加 1。

1,查看文档

  • GET $index_name/_count 查看索引中的文档数量
  • POST $index_name/_search 查看指定索引的前10条文档
    • POST Body 是 {}
  • GET $index_name/_doc/$id 获取指定索引中的指定文档

查询性能分析

GET /$index_name/_search
{
  "profile":"true",	# 开启性能分析
  "query": {
    ...
  }
}

评分分析(对某个文档的匹配详情分析):

GET /$index_name/_explain/$doc_id
{
  "query": {
    ...
  }
}

2,写入文档

  • POST $index_name/_doc 不指定 ID 写入文档
    • 总是会插入新的文档,文档数加 1
  • PUT $index_name/_doc/$id?op_type=XXX 指定 ID 写入文档
    • op_type=create
      • 相当于 PUT $index_name/_create/$id
      • 当 id 不存在时,会插入新的文档,文档数加 1
      • 当 id 存在时,报错,不会插入新文档
    • op_type=index
      • 相当于 POST/PUT $index_name/_doc/$id
      • 当 id 不存在时,会插入新的文档,文档数加 1
      • 当 id 存在时,会覆盖之前的,并且 version 会加 1,文档数不增加

3,更新文档

  • POST $index_name/_update/$id 更新指定文档的内容。更新的内容要放在 doc 字段中,否则会报错

    • 当 id 不存在时:报错,不更新任何内容
    • 当 id 存在时:
      • 如果更新的字段与原来的相同,则不做任何操作
      • 如果更新的字段与原来的不同,则更新原有内容,并且 version 会加 1
    • 实际上 ES 中的文档是不可变更的,更新操作会将旧的文档标记为删除,同时增加一个新的字段,并且文档的 version 加 1
  • POST $index_name/_update_by_query 根据条件更新文档

条件更新的语法:

POST $index_name/_update_by_query
{
  "query":{	# 更新条件
    ...
  },
  "script":{	# 更新操作
    ...
  }
}

upsert 操作,表示 update 与 insert

  • 当目标文件存在时,则进行更新操作,否则进行插入操作。

格式如下:

POST /$index_name/_update/$id
{
  "doc":{		# $id 存在时,进行更新操作
    "col_1":"xxx",
    "col_2":"yyy"
  },
  "upsert" {		# $id 不存在时,进行插入操作
    "col_1":"mmm",
    "col_2":"nnn"
  }
}

4,删除文档

  • DELETE $index_name/_doc/$id 删除指定文档
    • 当删除的 id 存在时,会删除该文档
    • 当删除的 id 不存在时,ES 会返回 not_found
  • POST $index_name/_delete_by_query 根据条件删除文档

条件删除格式如下:

POST $index_name/_delete_by_query
{
  "query":{	# 更新条件
    ...
  }
}

5,并发控制

同一个资源在多并发处理的时候,会发生冲突的问题。

传统数据库(比如 MySQL)会采用锁的方式,在更新数据的时候对数据进行加锁,来防止冲突。

而 ES 并没有锁,而是将并发问题交给了用户处理。

在 ES 中可以采用两种方式:

  • 内部版本控制(ES 自带的 version):在 URI 中使用 if_seq_noif_primary_term
  • 外部版本控制(由用户指定 version):在 URI 中使用 versionversion_type=external

5,批量操作

  • Bulk 批量操作
  • Mget 批量读取
  • Msearch 批量查询

注意在使用批量操作时,数据量不宜过大,避免出现性能问题,建议在 5~15M

1,Bulk 操作

批量操作指的是,在一次 API 调用中,对不同的索引进行多次操作

  • 每次操作互不影响,即使某个操作出错,也不影响其他操作。

  • 返回的结果中包含了所有操作的执行结果。

Bulk 操作支持 Index,Create,Update,Delete,使用 POST 方法:

POST /_bulk

一般请求体的内容写在文件中,文件名以 .json 结尾。

批量写入的格式

{"index":{"_index":"$index_name", "_id":"1"}}	# 索引名称及 id
{...}				                # 索引内容
{"index":{"_index":"$index_name", "_id":"2"}}	# 索引名称及 id
{...}						# 索引内容

如果没有写 _id 字段,则 _id 由 ES 自动生成。

批量更新的格式

{"update":{"_index":"$index_name", "_id":"1"}}	# 索引名称及 id
{"doc":{...}}				        # 索引内容
{"update":{"_index":"$index_name", "_id":"2"}}	# 索引名称及 id
{"doc":{...}}					# 索引内容

批量删除的格式

{"delete":{"_index":"$index_name", "_id":"1"}}	# 索引名称及 id
{"delete":{"_index":"$index_name", "_id":"2"}}	# 索引名称及 id

注意,批量 updatedelete 时,必须指定 _id 字段。

一般使用 curl 命令写入:

# movies.json 是文件名
curl -s -XPOST "host:port/_bulk?pretty" -H "Content-Type: application/json" --data-binary "@movies.json"

2,Mget 读取

Mget 一次读取多个文档的内容,设计思想类似 Bulk 操作。

语法格式如下:

GET _mget
{
    "docs" : [
        {"_index" : "$index_name1", "_id" : "1"},
        {"_index" : "$index_name2", "_id" : "2"}
    ]
}

# 也可以在 URI 中指定索引
GET $index_name/_mget
{
    "docs" : [
        {"_id" : "1"},
        {"_id" : "2"}
    ]
}

# 用 _source 字段设置返回内容
GET _mget
{
    "docs" : [
        {"_index" : "$index_name1", "_id" : "1"},
        {"_index" : "$index_name2", "_id" : "2", "_source" : ["f1", "f2"]}
    ]
}

3,Msearch 查询

格式如下:

POST $index_name1/_msearch
{} # 索引名称,不写的话就是 URI 中的索引
{"query" : {"match_all" : {}},"size":1}
{"index" : "index_name2"} # 改变了索引名称
{"query" : {"match_all" : {}},"size":2}

# URI 中也可以不写索引名称,此时请求体里必须写索引名称
POST _msearch
{"index" : "index_name1"} # 索引名称
{"query" : {"match_all" : {}},"size":1}
{"index" : "index_name2"} # 索引名称
{"query" : {"match_all" : {}},"size":2}

6,常见错误码

当我们的请求发生错误的时候,ES 会返回相应的错误码,常见的错误码如下:

错误码 含义
429 集群过于繁忙
4XX 请求格式错误
500 集群内部错误

7,数据类型

1,简单类型

  • keyword:是不进行切分的字符串类型,主要用于对文档的过滤,排序,聚合
    • 经常用于描述姓名,产品类型,用户 ID,URL,状态码等
    • 查询这种类型,一般使用 term 查询
    • 注意:对 keyword 类型使用 match 查询,是不会命中文档的
  • text:可进行分割的字符串类型
    • 如果希望某字段可以进行模糊查询(match),则可使用该类型
    • 对该类型的查询,一般使用 match 查询(先进行分词,再匹配)
  • 数值类型:一般用于对文档进行过滤,排序,聚合
    • long,interger,short,byte
    • double,float
    • half_float,scaled_float,unsigned_long
    • 对于这类数据类型,一般使用 term,range 查询
  • boolean:有 true 和 false 两种值
    • 该类型一般使用 term 查询
  • date:日期类型
    • ES 中可用如下三种形式表示 date 类型
      • 格式化的日期字符串
      • 秒级别的整型(从 1970.1.1 到现在)
      • 毫秒级的长整型(从 1970.1.1 到现在)
    • ES 中 date 的默认格式是 strict_date_option_time || epoch_millis
      • strict_date_option_time 支持的格式有:
        • yyyy-MM-dd,yyyyMMdd
        • yyyyMMddHHmmss,yyyy-MM-ddTHH:mm:ss
        • yyyy-MM-ddTHH:mm:ss.SSS,yyyy-MM-ddTHH:mm:ss.SSSZ
      • epoch_millis 表示 1970.1.1 到现在的毫秒数
    • 在写入数据时用什么格式,在查询时就用什么格式
    • 一般使用 range 查询对 date 类型进行搜索

注意,date 类型默认不支持我们常用的 yyyy-MM-dd HH:mm:ss 格式,如果需要使用该格式,则需要在 mapping 中定义:

{
  "mappings": {
    "properties": {
      "create_time": {
        "type":"date",	                # 日期类型
        "format": "yyyy-MM-dd HH:mm:ss"	# 指定格式
      }
    }
  }
}

对 date 类型定义好了格式,就需要使用相应的格式进行写入和查询。

精确值与全文本

ES 中有精确值(Exact Values)与全文本(Full Text)之分,上面说到的数据类型,text 类型属于全文本类型,除了 text 之外,其它都是精确值。

  • 对于精确值,ES 在建立索引和进行搜索时,都不会对文本进行分词分析
    • term,terms,range,exists,bool 查询都属于精确值匹配
  • 对于全文本,ES 在建立索引和进行搜索时,都会对文本进行分词分析
    • 全文本类型适用于 match 查询

2,复杂类型

  • 数组类型:没有特殊的定义方式
    • 在写入时用中括号 [] 括起来就行,它在 mapping 中显示的还是元素的类型,而没有数组类型
    • ES 中的空数组称为 missing field
    • 写入空数组时,将字段值置为 [] 就行,例如 "col_name":[]
    • 对于数组类型的查询规则,遵循其元素的查询规则
  • 对象类型:和数组一样,对象类型也没有特殊的定义方式
    • 在写入文档时,直接写入对象格式就行,ES 会自动将其转化为对象类型
    • 对于对象类型的查询规则,遵循其元素的查询规则
  • geo_point:地理类型

3,多字段

有时,对于同一个字段,需要有不同的索引方式,这时候就需要用到多字段

比如下面的 mapping:

{
  "mappings": {
    "properties": {
      "col_name": {		# 自定义字段名称
        "type": "text",         # 字段类型
        "fields": {		# ES 关键字,用于定义多字段
          "col_name_keyword": {	# 自定义多字段名称
            "type": "keyword"	# 字段类型
          }
        }
      }
    }
  }
}

对于上面的 mapping,ES 在建立索引时,会将 col_name 字段建立两份索引,分别是 text 类型和 keyword 类型。

4,nested 嵌套对象

直接上案例。

一个 movies 索引,其中 actors 是一个对象格式:

DELETE my_movies

PUT my_movies
{
   "mappings" : {
      "properties" : {
        "actors" : {
          "properties" : {
            "first_name" : { "type" : "keyword" },
            "last_name" : { "type" : "keyword" }
          }
        },
        "title" : {
          "type" : "text",
          "fields" : {
            "keyword" : { "type" : "keyword", "ignore_above" : 256 }
          }
        }
      }
   }
}

写入一条电影信息,注意 actors 写入的是数组:

POST my_movies/_doc/1
{
  "title":"Speed",
  "actors":[
    { "first_name":"Keanu", "last_name":"Reeves" },
    { "first_name":"Dennis", "last_name":"Hopper" }
  ]
}

查询电影:

POST my_movies/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"actors.first_name": "Keanu"}},
        {"match": {"actors.last_name": "Hopper"}}
      ]
    }
  }
}

这条查询语句居然查出了 _id 为 1 的文档。

那么问题出现了,我们查询的条件是 first_nameKeanu,并且 last_nameHopper;显然 _id 为 1 的文档,并不满足这样的条件,那问什么能将其查询出来呢?

实际上 ES 在存储数组的时候,会将 _id 为 1 的文档,存成下面的样子:

{
  "title": "Speed",
  "actors.first_name": ["Keanu", "Dennis"],
  "actors.last_name": ["Reeves", "Hopper"]
}

因此,我们的查询语句就搜索出了 _id 为 1 的文档。

那么如何解决这个问题呢(让那个查询语句查不出 _id 为 1 的文档)?

这时可以使用 nested 嵌套对象

  • nested 数据类型,使得对象数组中的对象是一个整体。

使用方式如下:

DELETE my_movies

# 创建 Nested 对象 Mapping
PUT my_movies
{
   "mappings" : {
      "properties" : {
        "actors" : {
          "type": "nested",	# nested 类型
          "properties" : {
            "first_name" : {"type" : "keyword"},
            "last_name" : {"type" : "keyword"}
          }},
        "title" : {
          "type" : "text",
          "fields" : {"keyword":{"type":"keyword","ignore_above":256}}
        }
      }
   }
}

然后写入文档,之后用下面的查询方式查询:

# Nested 查询
POST my_movies/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": {"title": "Speed"}},
        { "nested": {		# 指定 nested 查询
            "path": "actors",	# 指定 nested 类型的字段名称
            "query": {		# 普通的 query 语句
              "bool": {
                "must": [
                  {"match": { "actors.first_name": "Keanu" }},
                  {"match": { "actors.last_name": "Hopper" }}
                ]
              }
            }
          }
        }
      ]
    }
  }
}

这样的查询语句时查不出我们刚写入的文档的(要的就是这个效果)。

nested 嵌套对象的聚合分析

格式如下:

# Nested Aggregation
POST my_movies/_search
{
  "size": 0,
  "aggs": {
    "actors_aggs": {		# 自定义聚合名称
      "nested": {		# 指定是 nested 类型聚合
        "path": "actors"	# 指定 nested 类型的字段名
      },
      "aggs": {			# es 关键字,指定具体的聚合
        "actor_name": {		# 自定义聚合名称
          "terms": {		# 指定 terms 聚合
            "field": "actors.first_name"	# 指定聚合的字段
          }
        }
      }
    }
  }
}

注意,对 nested 嵌套对象进行普通的聚合是不起作用的:

# 普通 aggregation 不工作
POST my_movies/_search
{
  "size": 0,
  "aggs": {
    "actors_aggs": {
      "terms": {
        "field": "actors.first_name"
      }
    }
  }
}

8,文档的高级查询

指定查询范围

通过 URI 可以指定在哪些索引中进行查询,有下面几种格式:

  • /_search:在所有的索引中进行搜索。
  • /$index_name/_search:在 index_name 索引中进行搜索。
  • /$index1,$index2/_search:在 index1 和 index2 索引中进行搜索。
  • /$index*/_search:在所有的以 index 为前缀的索引中进行搜索。

ES 提供了丰富的查询功能:

  • 进行完全匹配的 term 查询(不进行分词)
  • 按照范围匹配的 range 查询(不进行分词)
  • 进行分词匹配的 match 查询(进行分词)

一个查询模板:

GET /$index_name/_search
{	
  "query": {		# 查询条件
  ...
  },
  "_source": [...],	# 指定返回的字段
  "sort":[		# 排序方式
    {"col_name": {"order": "desc"}},
    ...
  ],
  "from": 0,		# 起始 id
  "size": 10		# 页大小
}

为了不给 ES 造成较大负担,ES 使用 max_result_window 规定 from + size 的最大值,默认情况下为 10000。

修改该属性的方法(一般无需修改):

PUT /$index_name/_settings
{
  "index": {
    "max_result_window": 20000
  }
}

为了解决深度分页问题,ES 有两种解决方案:Search AfterScroll

不同分页方式的使用场景:

  • 普通查询(不使用分页):需要实时获取顶部的部分文档。
  • From-Size(普通分页):适用于非深度分页。
  • Search After:需要深度分页时使用。
  • Scroll:需要全部文档,比如导出全部数据。

1,match_all 查询

查看索引的所有文档:

# 默认返回前 10 行
GET $index_name/_search
{    
    "query":{
        "match_all":{}
    }
}

match_all 查询不会对文档进行打分,每个文档均为 1.0 分。

2,term 查询

term 查询是结构化精准查询的主要查询方式,用于查询待查字段和查询值是否完全匹配,格式如下:

GET $index_name/_search
{
  "query": {
    "term": {		# term 查询
      "col_name": {	# 字段名称
        "value": "$xxx"	# 要匹配的值
      }
    }
  }
}

term 查询还有一种简写形式:

GET $index_name/_search
{
  "query": {
    "term": {		  # term 查询
      "col_name": "$xxx"   # key:value 形式
    }
  }
}

term 查询一般针对的数据类型有:

  • keyword 类型
  • 数值类型
  • 布尔类型
  • 日期类型:要使用 mapping 定义的时间格式
  • 数组类型

3,terms 查询

terms 是 term 的扩展形式,用于查询一个或多个值与待查字段是否完全匹配,格式如下:

GET $index_name/_search
{
  "query": {
    "terms": {		# terms 查询
      "col_name": [	# 字段名称,是一个数组
        "$xxx",		# 要匹配的值
        "$yyy",
        "$zzz",
        ...
      ]
    }
  }
}

terms 查询一般针对的数据类型有:

  • keyword 类型
  • 数值类型
  • 布尔类型
  • 日期类型:要使用 mapping 定义的时间格式
  • 数组类型

4,range 查询

range 表示范围查询,比较符号有:

  • gt:大于
  • lt:小于
  • gte:大于等于
  • lte:小于等于

格式如下:

GET $index_name/_search
{
  "query":{
    "range": {
      "$col_name": {
          # 一个或多个比较符号
          "gte": 300,   # 这里 300 加与不加双引号是一样的
          "lte": 500
      }
    }
  }
}

range 查询一般针对的数据类型有:

  • 数值类型
  • 日期类型:要使用 mapping 定义的时间格式

对于日期类型,ES 内置了几个常用的符号:

符号 含义
y
M
w
d
H / h
m
s
now 现在

示例:

POST $index_name/_search
{
  "query" : {
    "range" : {
      "$col_name" : {      # 字段名称
        "gte" : "now-10y"  # 10年之前
      }
    }
  }
}

5,exists 查询

exists 用于查找某个字段不为空的文档,下列情况都表示不为空

  • 值存在,且不是 null
  • 值不是空数组 []
  • 值是数组,但不是 [null]

格式如下:

GET $index_name/_search
{
  "query":{
    "exists": {     # 查询 col_name 字段不为空的文档
      "field": "$col_name"
    }
  }
}

搜索不存在某个字段的文档,需要使用布尔查询。

如下:

POST index_name/_search
{
  "query": {
    "bool": {       # 布尔查询
      "must_not": { # 不存在
        "exists": { # 不存在 $field_name 字段的文档
          "field": "$field_name"
        }
      }
    }
  }
}

6,bool 查询

布尔类型支持四种子查询:

  • must:必须匹配,相当于逻辑与(and)
  • should:可以匹配,相当于逻辑或(or)
  • must not:必须不匹配,相当于逻辑非(not)
  • filter:必须匹配过滤条件,不进行打分计算
    • 相当于不打分的 must

这四种子查询都包含一个数组,可以把其他的 term 级别的查询及 bool 查询放入其中。

格式如下:

GET $index_name/_search
{
  "query": {
    "bool": {
      "must/should/must_not/filter": [
        {"term":{}},
        {"terms":{}},
        {"range":{}},
        {"exists":{}},
        {"bool":{}},
        ...
      ]
    }
  }
}

7,match 查询

match 查询表示全文搜索,主要针对 text 类型,比如一段对话,商品介绍等。

不同于结构化查询,全文搜索会对查询词进行分析(分词),然后再进行查询。

  • 结构化查询关注的是数据是否匹配,一般用于精确匹配
  • 全文搜索关注的是匹配的程度(算分),用于模糊匹配

格式如下:

GET $index_name/_search
{
  "query": {
    "match":{		# 先对 $xxx 进行分词,在去字段中匹配
      "col_name": "$xxx"
    }
  }
}

operator 属性

  • 该属性决定文档按照分词后的词集合进行“与”还是“或”匹配
  • 比如分词时分成了 A 和 B
  • operator 用于控制查询时 A 和 B之间的关系
  • 其取值有 and(与关系) 和 or(或关系)
    • A and B 表示包含 A 和 B,它们之间没有先后顺序
    • A or B 表示包含 A 或包含 B,它们之间没有先后顺序
  • 默认为 or

minimum_should_match 属性

  • operator 与关系对于搜索要求过于苛刻,或关系又比较宽松
  • 此时可以使用 minimum_should_match 来控制最小匹配度

语法如下:

POST index_name/_search
{
  "query": {
    "match": {
      "$field_name": {
        "query": "$xxx",
        "operator": "or",
        "minimum_should_match": 80%
      }
    }
  }
}

8,multi_match 查询

multi_match 表示在多个字段中进行 match。

格式如下:

GET $index_name/_search
{
  "query": {
    "multi_match":{
      "query": "$xxx",
      "fields": [		# 在哪些字段中进行匹配
        "col_1",
        "col_2",
        ...
      ]
    }
  }
}

9,match_phrase 查询

match_phrase 用于搜索确切的短语或邻近的词语。

# 此时表示的含义是
# one 与 love 紧邻,且 one 在 love 前面
# slop 表示 one 与 love 之间可以有几个字符,默认为 1
POST $index_name/_search
{
  "query": {
    "match_phrase": {
      "$field_name":{
        "query": "one love",
        "slop" : 1
      }
    }
  }
}

10,constant score 查询

默认的查询都会计算算分,constant score 称为不计分查询,所有分数都是 1。boots 属性用于改变分值。

语法如下:

POST $index_name/_search
{
  "query": {
    "constant_score": {        # constant_score 查询,固定写法
      "boost": 2.0,	       # 改变分值,默认为 1
      "filter": {              # 固定写法
        "term": {              # constant_score 包装一个 term 查询,就没有了算分
          "avaliable": true
        }
      }
    }
  }
}

11,function score 查询

function score 暂略。

9,聚合操作

聚合操作是 ES 提供的第二项重要功能,其使用 aggs 子句。

1,统计指标

常见的统计指标包含以下:

  • avg:平均值
  • max:最大值
  • min:最小值
  • sum:求和
  • stats:同时计算上面 4 种指标
    • 另外还会返回 count,表示参与计算的文档数量
    • 值为空值的字段不会参与计算
  • value_count:专门用于统计字段非空值的数量
    • 注意:如果进行聚合的字段是数组类型,则 value_count 统计的是符合条件的所有文档中该字段数组中非空元素个数的总和,而不是数组的个数总和
  • cardinality:去重统计,类似 MySQL 中的 distinct

格式如下:

GET $index_name/_search 
{
  "size": 0,	# 聚合操作,size 一般设为 0,表示不需要返回文档信息
  "aggs": {	# 聚合操作
    "my_agg": {	# 自定义聚合名称
      "avg/max/min/sum/stats/value_count": {
        "field": "$col_name",	# 要进行聚合的字段
        "missing": 100		# 当字段为空时,用该值替代,不写该属性时,默认为 0
      }
    }
  }
}

对于空值的处理

  • 索引中,会有部分文档,其某些字段可能缺失,或为空值
  • 可以使用 missing 来补充空值

多个统计指标示例:

# 多个 Metric 聚合,找到最低最高和平均工资
POST $index_name/_search
{
  "size": 0,               # size 为 0
  "aggs": {
    "max_salary": {        # 自定义聚合名称
      "max": {             # 聚合类型
        "field": "$salary" # 聚合字段
      }
    },
    "min_salary": {        # 自定义聚合名称
      "min": {             # 聚合类型
        "field": "$salary" # 聚合字段
      }
    },
    "avg_salary": {        # 自定义聚合名称
      "avg": {             # 聚合类型
        "field": "$salary" # 聚合字段
      }
    }
  }
}

2,桶聚合

ES 中的桶聚合相当于 MySQL 中的 Group By 语句,将同一类的数据放入同一个“桶”中。

桶聚合可分为:

  • 单维度桶聚合
  • 多维度桶聚合

单维度桶聚合

单维度桶聚合有:

  • terms 桶聚合:按照字段的实际完整值进行匹配分组,适用于能够精确匹配的数据类型,比如:
    • keyword 类型
    • bool 类型
    • keyword 数组类型
    • text 类型不适合 terms 桶聚合
  • ranges 桶聚合:针对的是数值类型,表示按照数值范围进行分组,使用到两个关键字:
    • from:起始数值
    • to:终止数值
    • 范围是 [from, to),包含 from,不包含 to
  • histogram 直方图桶聚合 :用于区间分桶
  • date_histogram 直方图桶聚合 :用于时间区间分桶

terms 聚合示例:

GET $index_name/_search
{
  "size": 0,
  "aggs": {
    "my_agg": {	        # 自定义聚合名称
      "terms": {
        "field":"$col_name"	# 按照 $col_name 字段进行聚合
				# 相当于 MySQL 中的以字段进行 group by 分组
      }
    }
  }
}

默认情况下,进行桶聚合时如果不指定统计指标,则聚合的是文档计数,该值以 doc_count 为 key 存储在每一个 bucket 返回值中。

ranges 聚合示例:

GET $index_name/_search
{
  "size": 0,
  "aggs": {
    "my_agg": {                 # 自定义聚合名称
      "range": {
        "field":"$col_name",	# 聚合的字段
        "ranges":[		# 指定多个范围
          { "to": 200	},  	# from 默认为 0 
          { "from": 200, "to": 500 },
          { 
            "from": 500, 	# to 默认为该字段最大值
            "key":"可以使用 key 自定义名称", 	# 如果没有定义 key,ES 会自动生成
          }			
        ]
      }
    }
  }
}

histogram 桶聚合示例:

# Salary Histogram
POST $index_name/_search
{
  "size": 0,
  "aggs": {
    "salary_histrogram": {   # 自定义聚合名称
      "histogram": {         # histogram 聚合
        "field":"$salary",   # 聚合的字段
        "interval":5000,     # 区间值
        "extended_bounds":{  # 最大最小范围
          "min":0,
          "max":100000
        }
      }
    }
  }
}

date_histogram 桶聚合示例:

POST $movies/_search
{
  "size": 0,
  "query": {
    "range": {
      "$publish_time": {
        "gte": "2009-06-01",
        "lte": "2010-01-01"
      }
    }
  }, 
  "aggs": {
    "$publish_time_histrogram": {   	# 自定义聚合名称
      "date_histogram": {        	# date_histogram
        "field":"$publish_time",    	# 聚合字段
        "calendar_interval": "year",    # 聚合间隔 
        "format": "yyyy-MM-dd",		# 时间格式
        "min_doc_count": 3,		# 最小数量
        "extended_bounds":{		# 最大最小范围
          "min":"2009-01-01",
          "max":"2010-01-01"
        }
      },
      "aggs": {				# 在 date_histogram 的基础上再进行指标聚合
        "$total_time": {
          "sum": {
            "field": "$movie_time"
          }
        }
      }
    }
  }
}

指定统计指标:在单维度桶聚合的基础上,指定统计指标

# 先以 $col_name1 字段进行桶聚合
# 在其基础上,再对 $col_name2 字段进行聚合
GET $index_name/_search
{
  "size": 0,
  "aggs": {
    "my_agg1": {
      "terms": {	# 先进行桶聚合
        "field": "$col_name1"
      },
      "aggs": {	        # 在桶聚合的基础上,再进行聚合
        "my_agg2": {
          "sum" : {	# 求和
            "field": "$col_name2"
          }
        }
      }
    }
  }
}

多维度桶聚合

ES 支持多维度(多层)桶聚合,一层一层的嵌套,如果有统计指标的话,统计指标放在最内层。

例如:

GET $index_name/_search
{
  "size": 0,
  "aggs": {
    "my_level_1": {		# 第一层桶聚合
      "terms": {
        "field": "$col_name1"
      },
      "aggs": {	
        "my_level_2": { 	# 第二次桶聚合,在第一层的基础上,再进行桶聚合
          "terms": {
            "field":"$col_name2"
          },
          "aggs": {		# 最内层,指定统计指标
            "my_sum" : {
              "sum" : { 	# 求和
                "field": "$col_name"
              }
            }
          }
        }
      }
    }
  }
}

3,先查询再聚合

上面介绍到的都是直接聚合的方式,也就是没有指定 query 子句,是对索引内的所有文档进行聚合。

在加上 query 子句后,参加聚合的文档,必须先匹配 query 查询,才会进行聚合。

GET $index_name/_search
{
  "size": 0,
  "query": { 	# query 子句
    ...
  },
  "aggs": {	# 聚合子句
    ...
  }
}

4,前过滤器

只有符合过滤器条件的文档才会进行聚合。

GET $index_name/_search
{
  "size": 0,
  "query": {        # query 子句
    ...
  },
  "aggs": {         # 聚合子句
    "my_agg": {
      "filter": {   # 前过滤器,与 query 子句语法一样
        ...
      },
      "aggs": {	    # 真正进行的聚合操作
        ...
      }
    }
  }
}

5,聚合排序

可以使用 order 子句自定义排序方式,可选的排序方式有:

  • 按照文档数排序,使用 _count 关键字(默认排序方式)
  • 按照聚合指标排序,使用相应聚合名称
  • 按照分组名称排序,使用 _key 关键字

_count 排序:

GET $index_name/_search
{
  "size": 0,
  "aggs": {
    "my_agg1": {
      "terms": {		# 先进行桶聚合
        "field": "$col_name1",
        "order": {	        # order 子句
          "_count": "asc"	# 以 my_agg2 的结果进行正序排序
        }
      },
      "aggs": {		        # 在桶聚合的基础上,再进行聚合
        "my_agg2": {
          "sum" : {	        # 求和
            "field": "$col_name2"
          }
        }
      }
    }
  }
}

order 中可以有多个指标:

"order":[
    {"_count":"asc"}, # 先按照文档数排序
    {"_key":"desc"}   # 如果文档数相同,再按照 key 排序
]

以聚合名称排序:

"order": {	    # order 子句
  "my_aggs": "asc"  # my_aggs 是自定义聚合名称
}

以分组的组名排序: 也就是引用 buckets 结果中的 key

"order": {	# order 子句
  "_key": "asc"
}

6,其它属性

桶聚合的其它常用属性还有:

  • size:返回的桶的数量,默认是 10
  • min_doc_count:返回的桶的最小文档数,默认是 1
"aggs": {
  "my_aggs": {	
    "terms": {
      "field":"$col_name",
      "size": 1000, 
      "min_doc_count": 10
    }
  }
}

10,搜索建议

搜索建议就是在用户输入搜索关键词的过程中,系统进行自动补全提示。

在搜索时,用户每输入一个字符,前端就需要向后端发送一次查询请求对匹配项进行查询,因此对后端的响应速度要求很高。

如下是百度的搜索建议:

在这里插入图片描述

ES 中提供的搜索建议功能,使用的索引结构不是倒排索引,而是在内存中构建的 FST,这种数据结构对内存的使用较多,一定要注意。

在 ES 中,要想使用搜索建议,对应的字段类型需要定义为 completion 类型。

PUT $index_name
{
  "mappings": {
    "properties": {
      "$field_name": {          # 字段名称
        "type": "completion"    # 字段类型
      }
    }
  }
}

使用时,是这样使用的:

GET $index_name/_search
{
  "suggest": {
    "my_suggest": {		# 自定义建议名称
      "prefix": "$用户的输入词",
      "completion": {
        "field":"$field_name"
      }
    }
  }
}