1 概念
Elasticsearch的映射用来定义一个索引中的文档如何被存储,定义一个映射类似于定义一个Mongo DB集合,在Elasticsearch 7.x中,映射和索引是一对一的关系。映射分为静态映射和动态映射,前者需要用户手动定义,后者则直接在向一个未生成的索引添加文档的时候自动生成。
1.1 动态映射
用户尝试向一个不存在的索引添加文档时,Elasticsearch会新建该索引并根据该文档字段的特性生成一个动态映射,其推断的字段映射类型和JSON文档字段类型的关系如下:
JSON类型 | 动态映射推断的映射类型 |
---|---|
null |
没有字段被添加,即不填加该映射 |
布尔型(true 或者false ) |
boolean 类型 |
浮点类型数字 | 单精度浮点型(float 类型) |
数字 | 长整型(long 类型) |
内嵌对象 | object 类型 |
数组 | 由数组的第一个非null 值决定 |
字符串类型 | 根据字符串内容特征而定,有可能为text /keyword /double /long /date 类型等 |
例如向Elasticsearch索引test-index
添加以下文档:
{
"id": 1,
"name": "Trump",
"height": 180.52,
"man": true,
"country": "USA",
"born": "1946-6-14",
"child": ["Donald John Trump", "Ivana Marie Trump", "Eric Frederick Trump"]
}
此时查询动态映射信息:
GET /test-index/_mapping
{
"test-index" : {
"mappings" : {
"properties" : {
"born" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"child" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"country" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"height" : {
"type" : "float"
},
"id" : {
"type" : "long"
},
"man" : {
"type" : "boolean"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
其中id
被推测为long
类型,height
被推测为float
类型,man
推测为boolean
类型,其余字段为text
类型。
1.2 静态映射
静态映射是用户在创建索引时手动指定映射,定义各个字段的属性,类似于MySQL的建表操作。
用户可以通过Rest API的PUT方法定义一个索引的映射,例如:
PUT test-index
{
"mappings": {
"properties": {
"born": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"child": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"country": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"height": {
"type": "float"
},
"id": {
"type": "long"
},
"man": {
"type": "boolean"
},
"name": {
"_all": {
"enabled": false
},
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
2 字段类型
Elasticsearch字段类型类似于MySQL中的字段类型。Elasticsearch字段类型主要有:核心类型、复合类型、地理类型、特殊类型。
2.1 核心类型
核心类型可以划分为字符串类型、数字类型、日期类型、布尔类型、基于BASE64的二进制类型、范围类型:
类型 | 具体类型 |
---|---|
字符串类型 | text 、keyword |
数字类型 | long 、integer 、short 、byte 、double 、float 、half_float 、scaled_float |
日期类型 | date 、date_nanos |
布尔类型 | boolean |
二进制类型 | binary |
范围类型 | range |
字符串类型
Elasticsearch 7.x有两种字符串类型:text
和keyword
,在Elasticsearch 5.x之后string
类型已经不再支持。
text
text
类型适用于需要被全文检索的字段,例如新闻正文、邮件内容等比较长的文字。text
类型会被Lucene分词器(Analyzer
)处理为一个个词项,并使用Lucene倒排索引存储。text
字段不能被用于排序。如果需要使用该类型的字段只需要在定义映射时指定JSON中对应字段的type
为text
。keyword
keyword
适合简短、结构化字符串,例如主机名、姓名、商品名称等,可以用于过滤、排序、聚合检索,也可以用于精确查询。
数字类型
数字类型分为long
、integer
、short
、byte
、double
、float
、half_float
、scaled_float
,其范围如下:
long
, 到integer
, 到short
, 到byte
, 到double
,IEEE 754标准双精度浮点类型,8字节float
,IEEE 754标准单精度浮点类型,4字节half_float
,IEEE 754标准半精度浮点类型,2字节scaled_float
,缩放类型浮点类型
数字类型的字段在满足需求的前提下应当尽量选择范围较小的数据类型,字段长度越短,搜索效率越高。对于浮点数,可以优先考虑使用scaled_float
类型,该类型可以通过缩放因子来精确浮点数,12.34
可以转换为1234
来存储。
日期类型
在Elasticsearch中日期可以为以下形式:
- 格式化的日期字符串,例如
2019-01-01 00:00
、2019/01/01
- 时间戳(和
1970-01-01 00:00:00 UTC
的差值),单位毫秒或者秒
即使是格式化的日期字符串,Elasticsearch底层依然采用的是时间戳的形式存储。
布尔类型
JSON文档中同样存在布尔类型,不过JSON字符串类型也可以被Elasticsearch转换为布尔类型存储,前提是字符串的取值为"true"
或者"false"
。布尔类型常用于检索中的过滤条件。
二进制类型
二进制类型binary
接受BASE64编码的字符串,默认store
属性为false
,并且不可以被搜索。
范围类型
范围类型可以用来表达一个数据的区间,可以分为5种:
integer_range
,可以表示最大的范围为float_range
,可以表达IEEE754单精度浮点数范围long_range
,可以表示最大的范围为double_range
,可以表达IEEE754双精度浮点数范围date_range
,可以表达64位时间戳(单位毫秒)范围。
如果需要使用范围类型,同样需要手动定义映射,例如定义一个date_range
的映射:
{
"mappings": {
"properties": {
"time_area": {
"type": "date_range",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
然后添加一份文档:
{
"time_area": {
"gte": "2019-11-10 00:00:00",
"lte": "2019-11-11 15:00:00"
}
}
gte
为区间最小值,lte
为区间最大值。
2.2 复合类型
复合类型主要有array
、object
、nested
数组类型
因为Lucene底层并不真正支持数组类型,所以自然Elasticsearch也没有专用的数组类型。对于文档中的数组而言,每个元素必须是同一种类型。例如:[1,2,3]
、["a","b","c"]
、[{"name":"Trump"},{"name":"Smith"}]
是合法的。
之前提到动态映射会以数组的第一个元素的类型来决定整个数组的类型,如果是空数组,那么会当成null
来处理,即忽略该数组字段,不会创建该字段的映射。
object类型
JSON字符串允许嵌套对象,一个文档可以嵌套多个、多层对象。可以通过object
类型来存储二级文档,不过由于Lucene并没有内部对象的概念,Elasticsearch会将原JSON文档扁平化,例如文档:
{
"name": {
"first": "Donald",
"last": "Trump"
}
}
实际上Elasticsearch会将其转换为以下格式,并通过Lucene存储,即使name
是object
类型:
{
"name.first": "Donald",
"name.last": "Trump"
}
对应的动态映射为:
{
"mappings": {
"properties": {
"name": {
"properties": {
"first": {"type":"text"},
"last": {"type":"text"}
}
}
}
}
}
nested类型
nested
类型可以看成是一个特殊的object
类型,可以让对象数组独立检索。例如文档:
{
"group": "a group",
"member": [
{ "first": "Donald", "last": "Trump"},
{ "first": "Barack", "last": "Obama"},
{ "first": "William", "last": "Clinton"}
]
}
member
字段是一个JSON数组,并且每个数组对象都是一个JSON对象。如果将member
设置为object
类型,那么Elasticsearch会将其转换为:
{
"group": "a group",
"member.first": ["Donald", "Barack", "William"],
"member.last": ["Trump", "Obama", "Clinton"]
}
可以看出转换后的JSON文档中first
和last
的关联丢失了,如果尝试搜索first
为Donald,last
为Obama的文档,那么成功会检索出上述文档,但是Donald和Obama在原JSON文档中并不属于同一个JSON对象,应当是不匹配的,即检索不出任何结果。nested
类型就是为了解决这种问题的。nested
类型将数组中的每个JSON对象作为独立的隐藏文档来存储,每个嵌套的对象都能够独立地被搜索,所以上述案例中虽然表面上只有1个文档,但实际上是存储了4个文档。
将member
字段改为nested
后,检索first
为Donald,last
为Obama的文档是就会提示不存在。
2.3 地理类型
地理类型字段分为两种:经纬度类型和地理区域类型。
经纬度类型
经纬度类型字段(geo_point
)可以存储经纬度相关信息。通过地理类型的字段,可以用来实现诸如查找在指定地理区域内相关的文档、根据距离排序、根据地理位置修改评分规则等需求。
如果需要使用到经纬度类型,需要手动定义映射:
{
"mappings": {
"properties": {
"location": {
"type": "geo_point"
}
}
}
}
geo_point
接收以下地理位置数据:
- 经纬度键值对,
lat
为纬度,lon
为经度{ "location": { "lat": 30.0, "lon": -50.0 } }
- 用逗号分割的字符串,前者为纬度,后者为经度
{ "location": "30.0,-50.0" }
- 数组形式坐标
{ "location": [30.0,-50.0] }
地理区域类型
经纬度类型可以表达一个点,而geo_shape
类型可以表达一块地理区域,区域的形状可以是任意多边形,也可以是点、线、面、多点、多线、多面等几何类型。GeoJSON一种表达地理区域的编码规范,具体可参考文档 《Geo格式规范说明》。
2.4 特殊类型
IP类型
IP类型的字段可以用来存储IPv4或者IPv6地址,如果需要存储IP类型的字段,需要手动定义映射:
{
"mappings": {
"properties": {
"my_ip": {
"type": "ip"
}
}
}
}
然后添加文档:
{
"my_ip": "10.0.0.1"
}
join类型
join
类型是Elasticsearch 6.x引入的类型,以取代淘汰的_parent
元字段。用来实现文档的一对一、一对多的关系。
join
类型的Mapping如下:
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
my_join_field
为join
类型字段的名称relations
指定关系:question
是answer
的父类
例如定义一个ID为1父文档:
PUT my_join_index/1?refresh
{
"text": "This is a question",
"my_join_field": "question"
}
接下来定义一个子文档:
PUT my_join_index/_doc/3?routing=1&refresh
{
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
该文档指定了父文档ID为1。
更多特殊类型
参考官方文档
3 元字段
元字段是映射中描述文档本身的字段,所有的元字段类型都以下划线开头,元字段可以分为:
-
文档属性的元字段
字段名 作用 _index
文档所属的索引 _type
文档类型,在ES 7以上版本中类型已被废弃,所以该值一律为 _doc
_id
文档唯一ID -
源文档的元字段
字段名 作用 _source
文档原始JSON字符串 _size
_source
字段的大小 -
索引的元字段
字段名 作用 _all
包含索引全部字段的超级字段 _field_names
文档中包含非空值的所有字段 -
路由的元字段
字段名 作用 _routing
将文档路由到特定分片的自定义路由值 -
自定义元字段:
meta
,用于自定义元数据。
3.1 _index字段
_index
字段为文档所在的索引名称,它是一个虚拟字段,并不会实际存储在Lucene索引中。利用该字段可以用来执行多索引查询,可以通过_index
进行term查询、聚合查询、terms查询等,但不支持prefix、regexp、fuzzy、wildcard查询。
terms查询示例如下:
GET index1,index2/_serach
{
"query": {
"terms": {
"_index": ["index1", "index2"]
}
},
"aggs": {
"indices": {
"terms": {
"field": "_index",
"size": 10
}
}
},
"sort": [{"_index": {"order": "asc"}}]
}
3.2 _id和_type字段
每条被索引的文档都有一个_type
字段和_id
字段。前者为类型字段,后者为ID字段,用来标识唯一的在Elasticsearch 5.X版本以前,每个索引可以建立多个类型,到Elasticsearch 6.X后,只能建立一个类型,到7.X之后类型概念被取消,_type
字段统一为_doc
。
_id
字段用于标识索引中唯一的文档,可以用于term、terms、match、query_string、simple_query_string查询,不能用于聚合、脚本和排序。
3.3 _source字段
_source
存储文档的原始JSON字符串,update、update_by_query、重建索引、高亮关键字、索引数据备份、修改mapping等一系列操作都需要用到_source
。虽然可以在定义mapping时不存储_source
,不过最好不要这么做。
3.4 _all字段
_all
字段是一个把文档中其它字段拼接在一起之后的字段,使用空格分隔,_all
字段会被解析和索引但是不会存储。_all
字段典型的用途就是在不知道具体字段的前提下,查找包含某个关键字的文档,例如:
GET index1/_search
{
"query": {
"match": {
"_all": "apple"
}
}
}
3.5 _field_names字段
_field_names
存储文档中所有非空字段的名字,常用于检索存在某个指定字段的文档:
GET index1/_search
{
"query": {
"match": {
"_field_names": "name"
}
}
}
上述示例是查询出具有name
字段的文档。
3.6 _routing
Elasticsearch通过以下公式来计算文档应该分配到哪个分片上:
shard_num = hash(_routing) % num_primary_shards
默认的_routing
值是文档的_id
字段,指定_routing
参数可以设置自定义路由,例如:
PUT my_index/_doc/1?routing=user1&refresh=true
{
"title": "This is a document"
}
上述示例中指定routing
为user1