本篇接着前一篇内容,继续介绍mapping信息,重点倾向于自定义mapping、自定义对象以及数组集合类的底层结构。
自定义mapping
上一篇文章介绍的都是Elasticsearch的自动mapping,我们在创建索引时,可以先指定好mapping的信息,还是以music索引为例:
PUT /music
{
"mappings": {
"children": {
"properties": {
"content": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"language": {
"type": "keyword"
},
"length": {
"type": "long"
},
"likes": {
"type": "long"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
共包含name,content,language,length,likes 5个field,这里我们把language field的type指定为keyword,这个field无需分词,精确匹配即可。
修改mapping
我们可以在创建索引时指定mapping信息,也可以为索引增加新的field时指定mapping信息,但是已经存在的field,我们不能修改其mapping,否则会报错,例如:
我们为music索引增加一个author field,并指定其type为text,analyzer、search_analyzer均为english
PUT /music/_mapping/children
{
"properties" : {
"author" : {
"type" : "text",
"index": true,
"analyzer": "english",
"search_analyzer": "english"
}
}
}
如果尝试修改一下已经存在的field,如name字段,则会提示报错:
请求:
PUT /music/_mapping/children
{
"properties" : {
"name" : {
"type" : "text",
"index": true,
"analyzer": "english",
"search_analyzer": "english"
}
}
}
报错信息:
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Mapper for [name] conflicts with existing mapping in other types:\n[mapper [name] has different [analyzer]]"
}
],
"type": "illegal_argument_exception",
"reason": "Mapper for [name] conflicts with existing mapping in other types:\n[mapper [name] has different [analyzer]]"
},
"status": 400
}
复杂数据类型底层结构
前面提及的mapping信息,都是针对基础数据类型的,我们知道Elasticsearch是面向对象的分布式存储文档系统,肯定会遇到各种各样的自定义对象、集合数组、嵌套对象等数据结构,ES是如何支持、处理这些复杂对象类型的呢?
- 数组、集合类
集合在JSON串中与数组等同,均可以认为是数组类型,在Elasticsearch中对数组没有特殊的映射需求,建立索引时与text是一样的,也是分词处理后得到多个词条,建立倒排索引。
有一点要注意的是数组内的元素,数据类型要一致,比如都是字符器,都是日期,或都是数值,不能混搭,自动mapping是以第1位元素来决定type的,如果混搭,数组内与第1位元素类型不一致的元素,索引时会报错。
-
Null类型
底层的Lucene不能存储null值,null类型的不会被索引:"null_value": null, "empty_array": [], "array_with_null_value": [ null ]
- 复杂对象
复杂对象一般有一层或多层嵌套,即对象中的属性,类型也是对象,有时候还混搭着数组一类的,举个例子:{ "address": { "country": "CN", "province": "GD", "city": "SZ" }, "name": "Herry", "age": 28, "birth_date": "1992-04-29" }
人员信息除了姓名年龄外,还有地址信息address,而address属性本身也是个对象,里面包含了country、province、city三个属性,查看它的mapping信息,也是呈嵌套结构(内容有删除,只保留体现层级的属性):
{
"person": {
"mappings": {
"info": {
"properties": {
"address": {
"properties": {
"city": {
"type": "text"
},
"country": {
"type": "text"
},
"province": {
"type": "text"
}
}
}
}
}
}
}
}
Elasticsearch在存储层级结构的数据对象时,会做一些扁平化处理,Luence文档是一组KV结构的列表,把上述的数据存储成这样:
{
"address.country": "CN",
"address.province": "GD",
"address.city": "SZ",
"name": "Herry",
"age": 28,
"birth_date": "1992-04-29"
}
所以最终想要根据address下的country作为条件进行查询,应该这么写:
GET /person/info/_search
{
"query": {
"match": {
"address.country": "CN"
}
}
}
- 数组内的复杂对象
数组内的元素如果是基础数据类型还好说,只要求数组内所有元素类型一致即可,如果里面的元素是一个对象,又是如何索引的呢?
假如一个数组结构是这样的:{ "likes": [ { "name": "Three Zhang", "datetime": "2019-12-01 08:58:12"}, { "name": "Four Lee", "datetime": "2019-12-01 09:12:23"}, { "name": "Five Wang", "datetime": "2019-12-01 09:15:58"} ] }
经过扁平化处理(由列变成行),得到的数据将会是这样:
{
"likes.name": ["Three Zhang","Four Lee","Five Wang"],
"likes.datetime": ["2019-12-01 08:58:12","2019-12-01 09:12:23","2019-12-01 09:15:58"],
}
发现什么问题没?这样处理完了之后,原本对象之间的关联性就没有了,数组内只是一堆无序的元素,如果要查询"Three Zhang在2019-12-01 09:00:00之后有没有给我点过赞"这样的组合条件查询,是会出现一条查询记录的,这跟预期的结果不同,显然是错了。
留意一下这个问题,数组内的元素是对象类型时,需要声明为nested类型,才能得到预期的查询效果,nested类型在后续会有介绍。
小结
本篇主要对mapping的内容进行一些补充,并简单描述了各种复杂对象的底层存储结构,需要注意的是数组内的对象处理,需要声明为nested才能得到正确的结果。
专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术