前言
Elasticsearch
是一个底层基于Lucene
的全文搜索和分析引擎,支持近乎实时地存储、搜索和分析大量数据的能力,最常用于网站搜索、日志搜索、数据分析等场景。
本文主要针对日常工作中Elasticsearch
使用的一些基础概念、使用规范、注意事项、常见优化以及工具使用进行总结,如有不当的地方,欢迎指正。
一、Elasticsearch建索引规范
索引建立时需搞清楚每个字段存在的用途(这里用途不仅仅是业务上的定义,还需关心该字段是会做索引,还是会聚合计算,还是会有排序,或者仅仅只是文档),在建立mapping
时应当根据字段的不同用途,不同数据类型来匹配合适的Elasticsearch
中的数据类型。
搞清楚中Elasticsearch中的数据类型
1. string类型
Keyword
和Text
都属于string
类的基本数据类型,但使用场景完全不同。
Keyword
与Text
差异对比
Keyword
(不分词):如果文本上有精确搜索、排序、聚合查询的需求时,可以使用,大文本用不要用keyword
类型。
Text
(分词):按分词器进行分词,用于全文检索,不能排序,也禁止用来聚合。
以下测试案例,具体说明了两种类型分别在match
和term
查询时反映出来的差异性。
# 建立名为emp的索引
PUT /emp/
{
"mappings": {
"_doc": {
"properties": {
"nickname": {
"type": "text"
},
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
}
}
}
}
}
# 插入测试数据
POST /emp/_doc/_bulk
{
"index":{
"_id":"1"}}
{
"nickname":"zhang san","name":"zhang san","age":"28"}
{
"index":{
"_id":"2"}}
{
"nickname":"zhang san feng","name":"zhang san feng","age":"88"}
{
"index":{
"_id":"3"}}
{
"nickname":"zhang san bu feng","name":"zhang san bu feng","age":"8"}
# 3条全部能够查到
GET /emp/_search
{
"query":{
"match":{
"nickname":"zhang san"}}}
# 3条全都查不到
GET /emp/_search
{
"query":{
"term":{
"nickname":"zhang san"}}}
# 换成查name,match和term都只能查到id为1的这条数据
GET /emp/_search
{
"query":{
"match":{
"name":"zhang san"}}}
GET /emp/_search
{
"query":{
"term":{
"name":"zhang san"}}}
# match和term都是3条全部能够查到
GET /emp/_search
{
"query":{
"match":{
"nickname":"zhang"}}}
GET /emp/_search
{
"query":{
"term":{
"nickname":"zhang"}}}
# match和term都是一条也查不到
GET /emp/_search
{
"query":{
"match":{
"name":"zhang"}}}
GET /emp/_search
{
"query":{
"term":{
"name":"zhang"}}}
# 可以排序
GET /emp/_search
{
"sort":[{
"name":{
"order":"desc"}}]}
# 报错
GET /emp/_search
{
"sort":[{
"nickname":{
"order":"desc"}}]}
2. 数值类型
数组类型包括:long, integer, short, byte, double, float, half_float, scaled_float
请按实际需求选择,因为这不仅仅是能够节省空间,同时索引和搜索都将变的更有效率,不过Elasticsearch
会根据存储的实际情况进行优化。
以下案例具体说明了,当实际存储类型与数据类型精度不一致时,所导致的“怪异”现象,主要原因就是因为Elasticsearch会根据存储的实际情况进行优化。
# 注意age字段指定的是integer类型
PUT /emp/
{
"mappings": {
"_doc": {
"properties": {
"nickname": {
"type": "text"
},
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
}
}
}
}
}
# 插入的测试数据,age字段实际上保存的都是字符串,但完全不影响插入
POST /emp/_doc/_bulk
{
"index":{
"_id":"1"}}
{
"nickname":"zhang san","name":"zhang san","age":"28"}
{
"index":{
"_id":"2"}}
{
"nickname":"zhang san feng","name":"zhang san feng","age":"88"}
{
"index":{
"_id":"3"}}
{
"nickname":"zhang san bu feng","name":"zhang san bu feng","age":"8"}
# 同样,保存小数也没问题
POST /emp/_doc/_bulk
{
"index":{
"_id":"6"}}
{
"nickname":"li gang","name":"li gang","age":16.9}
不过问题会出在查询时,直接查16.9
是查不到的。
# 查不出结果
GET /emp/_search
{
"query":{
"match":{
"age":16.9}}}
而查16
却可以查到结果。
# 能查出结果
GET /emp/_search
{
"query":{
"match":{
"age":16}}}
当然如果改为使用double
类型存储小数后,则可正常查询,以下是测试案例:
# 新建一个double类型的字段
PUT /emp_double/
{
"mappings":{
"_doc":{
"properties":{
"salary":{
"type":"double"}}}}}
# 构建数据
POST /emp_double/_doc/_bulk
{
"index":{
"_id":"1"}}
{
"salary":1000.5}
{
"index":{
"_id":"2"}}
{
"salary":2000.25}
{
"index":{
"_id":"3"}}
{
"salary":3000}
{
"index":{
"_id":"4"}}
{
"salary":4000.1234}
# 4条数据查1000.5,2000.25,3000,4000.1234都没问题
GET /emp_double/_search
{
"query":{
"match":{
"salary":4000.1234}}}
所以,实际生产环境不要存储比字段类型精度更高的数据。
当然数值类型也是支持聚合和排序的。同样,注意字段类型与实际存储类型产生的差异即可。
并没有按照实际存储的数值进行排序,因为实际精度并没有保留到小数。
这里还需要注意的是,在Elasticsearch
中存储时并不是所有数值数据都一定要被映射为数值类型的,通常情况是当数值需要进行范围查询时,则建议使用数值类型,而术语级别的查询,则使用keyword
更合适。
例如像ID、编号等这样的数据通常不会被用来做范围查询,而是经常用来做术语查询,因此应当建立为keyword
类型,尽管它本身是一串数字。
3. 日期类型
Elasticsearch
中日期类型既可以是格式化后的字符串,也可以是时间戳,Elasticsearch
内部统一会将其转换为UTC,并按照long number
类型进行存储。
多种时间格式的测试案例:
POST /my_date/_doc/_bulk
{
"index":{
"_id":"1"}}
{
"date":"2015-01-01"}
{
"index":{
"_id":"2"}}
{
"date":"2015-01-01T12:10:30Z"}
{
"index":{
"_id":"3"}}
{
"date":1420070400001}
GET my_date/_search
{
"sort":{
"date":"desc"}}
也支持指定的日期格式:
PUT my_date
{
"mappings": {
"_doc": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
}
}
}
}
}
# 此时插入第3条时间戳的格式将会报错
POST /my_date/_doc/_bulk
{
"index":{
"_id":"1"}}
{
"date":"2015-01-01"}
{
"index"