elasticsearch之mappings
一、elasticsearch之mappings是什么鬼东西
前言#
我们已经自由奔放够了!
我们应该知道,在关系型数据库中,必须先定义表结构,才能插入数据,并且,表结构不会轻易改变。而我们呢,我们怎么玩elasticsearch的呢:
PUT t1/doc/1
{
"name": "小黑"
}
PUT t1/doc/2
{
"name": "小白",
"age": 18
}
文档的字段可以是任意的,原本都是name
字段,突然来个age
。还要elasticsearch自动去猜,哦,可能是个long
类型,然后加个映射!之后发什么什么?肯定是:猜猜猜,猜你妹!
难道你不想知道elasticsearch内部是怎么玩的吗?
当我们执行上述第一条PUT
命令后,elasticsearch到底是怎么做的:
GET t1
结果:
{
"t1" : {
"aliases" : { },
"mappings" : {
"doc" : {
"properties" : {
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1553334893136",
"number_of_shards" : "5",
"number_of_replicas" : "1",
"uuid" : "lHfujZBbRA2K7QDdsX4_wA",
"version" : {
"created" : "6050499"
},
"provided_name" : "t1"
}
}
}
}
由返回结果可以看到,分为两大部分,第一部分关于t1
索引类型相关的,包括该索引是否有别名aliases
,然后就是mappings
信息,包括索引类型doc
,各字段的详细映射关系都收集在properties
中。
另一部分是关于索引t1
的settings
设置。包括该索引的创建时间,主副分片的信息,UUID等等。
我们再执行第二条PUT
命令,再查看该索引是否有什么变化,返回结果如下:
{
"t1" : {
"aliases" : { },
"mappings" : {
"doc" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1553334893136",
"number_of_shards" : "5",
"number_of_replicas" : "1",
"uuid" : "lHfujZBbRA2K7QDdsX4_wA",
"version" : {
"created" : "6050499"
},
"provided_name" : "t1"
}
}
}
}
由返回结果可以看到,settings
没有变化,只是mappings
中多了一条关于age
的映射关系,这一切都是elasticsearch自动的,但特定的场景下,需要我们更多的设置。
所以,接下来,我们研究一下mappings
这个小老弟,到底是怎么回事!
映射是什么?#
其实,映射mappings
没那么神秘!说白了,就相当于原来由elasticsearch自动帮我们定义表结构。现在,我们要自己来了,旨在创建索引的时候,有更多定制的内容,更加的贴合业务场景。OK,坐好了,开车!
elasticsearch
中的映射用来定义一个文档及其包含的字段如何存储和索引的过程。例如,我们可以使用映射来定义:
- 哪些字符串应该被视为全文字段。
- 哪些字段包含数字、日期或者地理位置。
- 定义日期的格式。
- 自定义的规则,用来控制动态添加字段的的映射。
身为吃瓜群众的小老弟,不懂没关系,往下走!
映射类型#
每个索引都有一个映射类型(这话必须放在elasticsearch6.x版本后才能说,之前版本一个索引下有多个类型),它决定了文档将如何被索引。
映射类型有:
- 元字段(meta-fields):元字段用于自定义如何处理文档关联的元数据,例如包括文档的
_index
、_type
、_id
和_source
字段。 - 字段或属性(field or properties):映射类型包含与文档相关的字段或者属性的列表。
还不懂,没关系,继续往下走!
字段的数据类型#
- 简单类型,如文本(
text
)、关键字(keyword
)、日期(date
)、整形(long
)、双精度(double
)、布尔(boolean
)或ip
。 - 可以是支持
JSON
的层次结构性质的类型,如对象或嵌套。 - 或者一种特殊类型,如
geo_point
、geo_shape
或completion
。
为了不同的目的,以不同的方式索引相同的字段通常是有用的。例如,字符串字段可以作为全文搜索的文本字段进行索引,也可以作为排序或聚合的关键字字段进行索引。或者,可以使用标准分析器、英语分析器和法语分析器索引字符串字段。
这就是多字段的目的。大多数数据类型通过fields参数支持多字段。
映射约束#
在索引中定义太多的字段有可能导致映射爆炸!因为这可能会导致内存不足以及难以恢复的情况,为此。我们可以手动或动态的创建字段映射的数量:
- index.mapping.total_fields.limit:索引中的最大字段数。字段和对象映射以及字段别名都计入此限制。默认值为1000。
- index.mapping.depth.limit:字段的最大深度,以内部对象的数量来衡量。例如,如果所有字段都在根对象级别定义,则深度为1.如果有一个子对象映射,则深度为2,等等。默认值为20。
- index.mapping.nested_fields.limit:索引中嵌套字段的最大数量,默认为50.索引1个包含100个嵌套字段的文档实际上索引101个文档,因为每个嵌套文档都被索引为单独的隐藏文档。
一个简单的映射示例#
PUT mapping_test1
{
"mappings": {
"test1":{
"properties":{
"name":{"type": "text"},
"age":{"type":"long"}
}
}
}
}
上例中,我们在创建索引PUT mapping_test1
的过程中,为该索引定制化类型(设计表结构),添加一个映射类型test1
;指定字段或者属性都在properties
内完成。
GET mapping_test1
通过GET
来查看。
{
"mapping_test1" : {
"aliases" : { },
"mappings" : {
"test1" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1550469220778",
"number_of_shards" : "5",
"number_of_replicas" : "1",
"uuid" : "7I_m_ULRRXGzWcvhIZoxnQ",
"version" : {
"created" : "6050499"
},
"provided_name" : "mapping_test1"
}
}
}
}
返回的结果中你肯定很熟悉!映射类型是test1
,具体的属性都被封装在properties
中。而关于settings
的配置,我们暂时不管它。
我们为这个索引添加一些数据:
put mapping_test1/test1/1
{
"name":"张开嘴",
"age":16
}
上例中,mapping_test1
是之前创建的索引,test1
为之前自定义的mappings
类型。字段是之前创建好的name
和age
。
GET mapping_test1/test1/_search
{
"query": {
"match": {
"age": 16
}
}
}
上例中,我们通过age
条件查询。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 1.0,
"hits" : [
{
"_index" : "mapping_test1",
"_type" : "test1",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "张开嘴",
"age" : 16
}
}
]
}
}
返回了预期的结果信息。
欢迎斧正,that's all
二、elasticsearch mappings之dynamic的三种状态
前言#
一般的,mapping
则又可以分为动态映射(dynamic mapping)和静态(显式)映射(explicit mapping)和精确(严格)映射(strict mappings),具体由dynamic
属性控制。
动态映射(dynamic:true)#
现在有这样的一个索引:
PUT m1
{
"mappings": {
"doc":{
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
}
通过GET m1/_mapping
看一下mappings
信息:
{
"m1" : {
"mappings" : {
"doc" : {
"dynamic" : "true",
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
}
}
}
}
}
}
添加一些数据,并且新增一个sex
字段:
PUT m1/doc/1
{
"name": "小黑",
"age": 18,
"sex": "不详"
}
当然,新的字段查询也没问题:
GET m1/doc/_search
{
"query": {
"match": {
"sex": "不详"
}
}
}
返回结果:
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "m1",
"_type" : "doc",
"_id" : "1",
"_score" : 0.5753642,
"_source" : {
"name" : "小黑",
"age" : 18,
"sex" : "不详"
}
}
]
}
}
现在,一切都很正常,跟elasticsearch自动创建时一样。那是因为,当 Elasticsearch 遇到文档中以前未遇到的字段,它用动态映射来确定字段的数据类型并自动把新的字段添加到类型映射。我们再来看mappings
你就明白了:
{
"m1" : {
"mappings" : {
"doc" : {
"dynamic" : "true",
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
},
"sex" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
通过上例可以发下,elasticsearch帮我们新增了一个sex
的映射。所以。这一切看起来如此自然。这一切的功劳都要归功于dynamic
属性。我们知道在关系型数据库中,字段创建后除非手动修改,则永远不会更改。但是,elasticsearch默认是允许添加新的字段的,也就是dynamic:true
。
其实创建索引的时候,是这样的:
PUT m1
{
"mappings": {
"doc":{
"dynamic":true,
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
}
上例中,当dynamic
设置为true
的时候,elasticsearch
就会帮我们动态的添加映射属性。也就是等于啥都没做!
这里有一点需要注意的是:mappings
一旦创建,则无法修改。因为Lucene生成倒排索引后就不能改了。
静态映射(dynamic:false)#
现在,我们将dynamic
值设置为false
:
PUT m2
{
"mappings": {
"doc":{
"dynamic":false,
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
}
现在再来测试一下false
和true
有什么区别:
PUT m2/doc/1
{
"name": "小黑",
"age":18
}
PUT m2/doc/2
{
"name": "小白",
"age": 16,
"sex": "不详"
}
第二条数据相对于第一条数据来说,多了一个sex
属性,我们以sex
为条件来查询一下:
GET m2/doc/_search
{
"query": {
"match": {
"sex": "不详"
}
}
}
结果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : null,
"hits" : [ ]
}
}
结果是空的,也就是什么都没查询到,那是为什呢?来GET m2/_mapping
一下此时m2
的mappings
信息:
{
"m2" : {
"mappings" : {
"doc" : {
"dynamic" : "false",
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
}
}
}
}
}
}
```
可以看到elasticsearch并没有为新增的`sex`建立映射关系。所以查询不到。
当elasticsearch察觉到有新增字段时,因为`dynamic:false`的关系,会忽略该字段,但是仍会存储该字段。
在有些情况下,`dynamic:false`依然不够,所以还需要更严谨的策略来进一步做限制。
# 严格模式(dynamic:strict)
让我们再创建一个`mappings`,并且将`dynamic`的状态改为`strict`:
```
PUT m3
{
"mappings": {
"doc": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
}
}
}
}
}
```
现在,添加两篇文档:
```
PUT m3/doc/1
{
"name": "小黑",
"age": 18
}
PUT m3/doc/2
{
"name": "小白",
"age": 18,
"sex": "不详"
}
```
第一篇文档添加和查询都没问题。但是,当添加第二篇文档的时候,你会发现报错了:
```
{
"error": {
"root_cause": [
{
"type": "strict_dynamic_mapping_exception",
"reason": "mapping set to strict, dynamic introduction of [sex] within [doc] is not allowed"
}
],
"type": "strict_dynamic_mapping_exception",
"reason": "mapping set to strict, dynamic introduction of [sex] within [doc] is not allowed"
},
"status": 400
}
```
错误提示,严格动态映射异常!说人话就是,当`dynamic:strict`的时候,elasticsearch如果遇到新字段,会抛出异常。
上述这种严谨的作风洒家称为——严格模式!
小结:
- 动态映射(dynamic:true):动态添加新的字段(或缺省)。
- 静态映射(dynamic:false):忽略新的字段。在原有的映射基础上,当有新的字段时,不会主动的添加新的映射关系,只作为查询结果出现在查询中。
- 严格模式(dynamic: strict):如果遇到新的字段,就抛出异常。
一般静态映射用的较多。就像`HTML`的`img`标签一样,`src`为自带的属性,你可以在需要的时候添加`id`或者`class`属性。
当然,如果你非常非常了解你的数据,并且未来很长一段时间不会改变,`strict`不失为一个好选择。
<hr>
see also: [Elasticsearch - 索引](https://www.jianshu.com/p/ee6413896a2b)
欢迎斧正,that's all
三、elasticsearch之mappings的其他设置:index、copy_to、对象属性、settings
前言#
上一小节中,根据dynamic
的状态不同,我们对字段有了更多可自定义的操作。现在再来补充一个参数,使自定义的属性更加的灵活。
index#
首先来创建一个mappings
:
PUT m4
{
"mappings": {
"doc": {
"dynamic": false,
"properties": {
"name": {
"type": "text",
"index": true
},
"age": {
"type": "long",
"index": false
}
}
}
}
}
可以看到,我们在创建索引的时候,为每个属性添加一个index
参数。那会有什么效果呢?
先来添加一篇文档:
PUT m4/doc/1
{
"name": "小黑",
"age": 18
}
再来查询看效果:
GET m4/doc/_search
{
"query": {
"match": {
"name": "小黑"
}
}
}
GET m4/doc/_search
{
"query": {
"match": {
"age": 18
}
}
}
以name
查询没问题,但是,以age
作为查询条件就有问题了:
{
"error": {
"root_cause": [
{
"type": "query_shard_exception",
"reason": "failed to create query: {\n \"match\" : {\n \"age\" : {\n \"query\" : 18,\n \"operator\" : \"OR\",\n \"prefix_length\" : 0,\n \"max_expansions\" : 50,\n \"fuzzy_transpositions\" : true,\n \"lenient\" : false,\n \"zero_terms_query\" : \"NONE\",\n \"auto_generate_synonyms_phrase_query\" : true,\n \"boost\" : 1.0\n }\n }\n}",
"index_uuid": "GHBPeT5pRnSi3g6DkpIkow",
"index": "m4"
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"failed_shards": [
{
"shard": 0,
"index": "m4",
"node": "dhkqLLTsRemm7qEgRdpvTg",
"reason": {
"type": "query_shard_exception",
"reason": "failed to create query: {\n \"match\" : {\n \"age\" : {\n \"query\" : 18,\n \"operator\" : \"OR\",\n \"prefix_length\" : 0,\n \"max_expansions\" : 50,\n \"fuzzy_transpositions\" : true,\n \"lenient\" : false,\n \"zero_terms_query\" : \"NONE\",\n \"auto_generate_synonyms_phrase_query\" : true,\n \"boost\" : 1.0\n }\n }\n}",
"index_uuid": "GHBPeT5pRnSi3g6DkpIkow",
"index": "m4",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Cannot search on field [age] since it is not indexed."
}
}
}
]
},
"status": 400
}
返回的是报错结果,这其中就是index
参数在起作用。
小结:index
属性默认为true
,如果该属性设置为false
,那么,elasticsearch
不会为该属性创建索引,也就是说无法当做主查询条件。
copy_to#
现在,再来学习一个copy_to
属性,该属性允许我们将多个字段的值复制到组字段中,然后将组字段作为单个字段进行查询。
PUT m5
{
"mappings": {
"doc": {
"dynamic":false,
"properties": {
"first_name":{
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
}
PUT m5/doc/1
{
"first_name":"tom",
"last_name":"ben"
}
PUT m5/doc/2
{
"first_name":"john",
"last_name":"smith"
}
GET m5/doc/_search
{
"query": {
"match": {
"first_name": "tom"
}
}
}
GET m5/doc/_search
{
"query": {
"match": {
"full_name": "tom"
}
}
}
上例中,我们将first_name
和last_name
都复制到full_name
中。并且使用full_name
查询也返回了结果:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "m5",
"_type" : "doc",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"first_name" : "tom",
"last_name" : "ben"
}
}
]
}
}
返回结果表示查询成功。那么想要查询tom
或者smith
该怎么办?
GET m5/doc/_search
{
"query": {
"match": {
"full_name": {
"query": "tom smith",
"operator": "or"
}
}
}
}
将查询条件以空格隔开并封装在query
内,operator
参数为多个条件的查询关系也可以是and
,也有简写方式:
GET m5/doc/_search
{
"query": {
"match": {
"full_name": "tom smith"
}
}
}
copy_to
还支持将相同的属性值复制给不同的字段。
PUT m6
{
"mappings": {
"doc": {
"dynamic":false,
"properties": {
"first_name":{
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": ["field1", "field2"]
},
"field1": {
"type": "text"
},
"field2": {
"type": "text"
}
}
}
}
}
PUT m6/doc/1
{
"first_name":"tom",
"last_name":"ben"
}
PUT m6/doc/2
{
"first_name":"john",
"last_name":"smith"
}
上例中,只需要将copy_to
的字段以数组的形式封装即可。无论是通过field1
还是field2
都可以查询。
小结:
copy_to
复制的是属性值而不是属性copy_to
如果要应用于聚合请将filddata
设置为true
- 如果要将属性值复制给多个字段,请用数组,比如
copy_to:["field1", "field2"]
对象属性#
现在,有一个个人信息文档如下:
PUT m7/doc/1
{
"name":"tom",
"age":18,
"info":{
"addr":"北京",
"tel":"10010"
}
}
首先,这样嵌套多层的mappings
该如何设计呢?
PUT m7
{
"mappings": {
"doc": {
"dynamic": false,
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "text"
},
"info": {
"properties": {
"addr": {
"type": "text"
},
"tel": {
"type" : "text"
}
}
}
}
}
}
}
那么,如果要以name
或者age
属性作为查询条件查询难不倒我们。
现在如果要以info
中的tel
为条件怎么写查询语句呢?
GET mapping_test9/doc/_search
{
"query": {
"match": {
"info.tel": "10086"
}
}
}
上例中,info
既是一个属性,也是一个对象,我们称为info
这类字段为对象型字段。该对象内又包含addr
和tel
两个字段,如上例这种以嵌套内的字段为查询条件的话,查询语句可以以字段点子字段的方式来写即可。
settings设置#
设置主、复制分片#
在创建一个索引的时候,我们可以在settings
中指定分片信息:
PUT s1
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text"
}
}
}
},
"settings": {
"number_of_replicas": 1,
"number_of_shards": 5
}
}
number_of_shards
是主分片数量(每个索引默认5个主分片),而number_of_replicas
是复制分片,默认一个主分片搭配一个复制分片。
see also:[elasticsearch mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html#_explicit_mappings)|[copy_to](https://www.elastic.co/guide/en/elasticsearch/reference/6.6/copy-to.html) 欢迎斧正,that's all
四、elasticsearch之mappings parameters
ignore_above
长度超过ignore_above
设置的字符串将不会被索引或存储(个人认为会存储,但不会为该字段建立索引,也就是该字段不能被检索)。 对于字符串数组,ignore_above
将分别应用于每个数组元素,并且不会索引或存储比ignore_above
更长的字符串元素。
PUT w1
{
"mappings": {
"doc":{
"properties":{
"t1":{
"type":"keyword",
"ignore_above": 5
},
"t2":{
"type":"keyword",
"ignore_above": 10 ①
}
}
}
}
}
PUT w1/doc/1
{
"t1":"elk", ②
"t2":"elasticsearch" ③
}
GET w1/doc/_search ④
{
"query":{
"term": {
"t1": "elk"
}
}
}
GET w1/doc/_search ⑤
{
"query": {
"term": {
"t2": "elasticsearch"
}
}
}
①,该字段将忽略任何超过10个字符的字符串。
②,此文档已成功建立索引,也就是说能被查询,并且有结果返回。
③,该字段将不会建立索引,也就是说,以该字段作为查询条件,将不会有结果返回。
④,有结果返回。
⑤,则将不会有结果返回,因为t2
字段对应的值长度超过了ignove_above
设置的值。
该参数对于防止Lucene的术语字节长度限制也很有用,限制长度是32766
。
注意,该ignore_above设置可以利用现有的领域进行更新PUT地图API。
对于值ignore_above
是字符数,但Lucene的字节数为单位。如果您使用带有许多非ASCII字符的UTF-8文本,您可能需要设置限制,32766 / 4 = 8191
因为UTF-8字符最多可占用4个字节。
如果我们观察上述示例中,我们可以看到在设置映射类型时,字段的类型是keyword
,也就是说ignore_above
参数仅针对于keyword
类型有用。
那么如果字符串的类型是text
时能用ignore_above
吗,答案是能,但要特殊设置:
PUT w2
{
"mappings": {
"doc":{
"properties":{
"t1":{
"type":"keyword",
"ignore_above":5
},
"t2":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above": 10
}
}
}
}
}
}
}
PUT w2/doc/1
{
"t1":"beautiful",
"t2":"beautiful girl"
}
GET w2/doc/_search ①
{
"query": {
"term": {
"t1": {
"value": "beautiful"
}
}
}
}
GET w2/doc/_search ②
{
"query": {
"term": {
"t2": "beautiful"
}
}
}
①,不会有返回结果。
②,有返回结果,因为该字段的类型是text
。
但是,当字段类型设置为text
之后,ignore_above
参数的限制就失效了。
欢迎斧正,that's all see also:[官网7.0:ignore_above](https://www.elastic.co/guide/en/elasticsearch/reference/7.0/ignore-above.html) | [ignore_above](https://www.elastic.co/guide/en/elasticsearch/reference/7.0/ignore-above.html)
五、Removal of mapping types
IMPORTANT
在elasticsearch6.0.0或更高的版本中创建索引仅能包含单个映射类型。在具有多种映射类型的5.x版本中创建的索引将继续像以前一样在elasticsearch6.x中运行。类型将在elasticsearch7.0.0中的API中弃用,并在8.0.0中完全删除。
环境:
- elasticsearch6.5.4
- kibana6.5.4
- windows 10
什么是映射类型?
从elasticsearch发布以来,每个文档都存储在单个索引中并分配了单个映射类型。映射类型用于表示要编制索引的文档或实体的类型。例如微博(twitter)索引可能具有用户(user)类型和推文(tweet)两个类型。
每种映射类型都可以有自己的字段,因此用户(user)类型可能有full_name
、user_name
、email
字段;而推文(tweet)类型可能有content
、tweet_at
字段和用户(user)类型的user_name
字段。
每个文档都有一个_type
包含类型名称的元字段,通过在URL中指定类型名称,搜索可以限制为一种或多种类型:
GET twitter/user,tweet/_search
{
"query":{
"match":{
"user_name":"kimchy"
}
}
}
该_type
字段与文档组合_id
以生成_uid
字段,因此具有相同类型的文档_id
可以存储在单个索引中。
映射类型也用于在文档中建立父子关系,因此类型的文档question
可以是类型文档的父类answer
。
扯了半天淡,一切不都是挺好的嘛?那还为啥要删除映射类型呢?
为什么要删除映射类型?
最初(其实到现在),为了便于理解elasticsearch的数据组织,通常拿elasticsearch和关系型数据库做对比,比如我们谈到一个es索引(index)时,通常将它比喻为类似于SQL数据库中的database
,而类型(type)等同于SQL数据库中的表。
这真是一个糟糕的比喻!让我们有了错误理解。因为在SQL数据库中,表彼此独立,一个表中的字段与另一个表中具有相同名称的字段无关,而映射类型中的字段不是这种情况。
在elasticsearch的索引中,不同映射类型具有相同名称的字段在内部由相同的Lucene字段支持。换句话说,使用上面的示例,用户(user)类型中的user_name
字段存储在和推文(tweet)类型中的user_name
字段完全相同的字段中,而且两种类型中的user_name
字段必须具有相同的映射(定义)。
当我们希望删除一个类型的日期字段和同一个索引中另一个类型的布尔字段时,这可能会导致挫败感(可以理解为删除失败)。
最重要的是,在同一索引中存储具有很少或没有共同字段的不同实体会导致稀疏数据并干扰Lucene有效压缩文档的能力。
出于这些原因,我们决定从elasticsearch中删除映射类型的概念。
映射类型的替代方法
将映射类型分开存储在索引中
第一种方法是每个文档类型都有一个索引,例如微博(twitter)索引中,我们可以将推文(tweet)类型和用户(user)类型分开,分别存储在独立的索引中。这样两个相互的索引就不会引起字段冲突了。
这中方法有两个好处:
- 数据更可能是密集的,因此受益于Lucene中使用的压缩技术。
- 用于全文搜索评分的词条统计将会更精确,应为同一索引中的所有文档都代表单个实体。
每个索引的大小可以根据其包含的文档数量进行适当的调整,比如我们为用户(user)类型分配较少的主分片,而为推文(tweet)类型分配较多的主分片。
自定义类型字段
当然了,集群中可以存储多少个主分片是有限制的,我们不希望仅为几千个文档的集合而浪费整个分片。在这种情况下,我们可以实现自己的自定义type
字段,该字段的工作方式与旧的_type
相似。
还是上面微博(twitter)例子,最初,它的映射类型看起来是这样的:
PUT twitter
{
"mappings": {
"user":{
"properties":{
"name":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"email":{
"type":"keyword"
}
}
},
"tweet":{
"properties":{
"content":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"tweet_at":{
"type":"date"
}
}
}
}
}
PUT twitter/user/kimchy
{
"name":"狗子",
"user_name":"二狗子",
"email":"dog@twodog.com"
}
PUT twitter/tweet/1
{
"name":"kimchy",
"tweet_ad":"2019-04-30T10:26:20Z",
"content":"单身狗求包养"
}
GET twitter/tweet/_search
{
"query": {
"match": {
"user_name": "kimchy"
}
}
}
如上示例,请在5.x及以下版本测试(据我推测的.......)!
我们也可以通过添加自定义type
字段来实现相同目的:
PUT twitter
{
"mappings": {
"doc":{
"properties":{
"type":{
"type":"keyword"
},
"name":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"email":{
"type":"text"
},
"content":{
"type":"text"
},
"tweet_at":{
"type":"date"
}
}
}
}
}
PUT twitter/doc/user-kimchy
{
"type":"user",
"name":"狗子",
"user_name":"二狗子",
"email":"dog@twodog.com"
}
PUT twitter/doc/tweet-1
{
"type":"tweet",
"user_name":"kimchy",
"tweet_at":"2019-04-30T10:26:20Z",
"content":"单身狗求包养"
}
GET twitter/_search
{
"query": {
"bool": {
"must":[
{
"match": {
"user_name": "kimchy"
}
}
],
"filter": {
"match":{
"type":"tweet"
}
}
}
}
}
上述示例6.5.4版本运行无误。
没有映射类型的父/子
以前,通过将一个映射类型设置为父级,将一个或多个其他映射类型设置为子级来表示父子关系。现在,没有了多类型,我们就不能再使用这种语法了。除了表示文档之间的关系方式已改为使用新的join字段之外,父子特征将继续像以前一样运行。
删除映射类型的计划
这个删除映射类型的计划,对于用户来说是一个很大的变化,所以我们试图让它尽可能轻松,更改将如下所示:
在elasticsearch5.6.0中:
- index.mapping.single_type:true在索引上设置将启用在6.0中强制执行的单索引类型。
- 父子的join字段替换可用于在5.6中创建索引。
在elasticsearch6.x中:
- 在5.x中创建的索引将继续在6.x中运行,就像在5.x中一样。
- 在6.x中创建的索引仅允许每个索引使用单一类型,任何字段都可以用于该类型,但必须是唯一的。
- 该
_type
名称可以不再与_id
组合形成_uid
字段,_uid
字段已成为_id
字段的别名。 - 新索引不再支持旧的父/子关系,而是应该使用连接字段。
- 不推荐使用
_default_mapping
类型。 - 在6.7中,索引创建、索引模板和映射API支持查询字符串参数(include_type_name),该参数仅表示请求和响应是否应该包含类型名称,默认为true,应该设置为一个显式值,以便准备升级到7.0。未设置
include_type_name
将导致一个弃用警告,没有显式类型的索引将使用默认的类型名称_doc
。
在elasticsearch7.x中:
- 不推荐在请求中指定类型。例如,索引文档不再需要文档类型。对于自动生成的id,新的索引API在显式ids和
POST {index_name}/_doc
的情况下是PUT {index_name}/_doc/{id}
。 - 索引创建,索引模板和映射API中的
include_type_name
参数将默认为false,未设置参数将导致启动警告。 - 删除了
_default_mapping
类型。
在elasticsearch8.x中:
- 不在支持在请求中指定类型。
include_type_name
参数已删除。
将多类型索引迁移到单一类型
Reindex API可用于将多类型索引转换为单类型索引。下面的例子可以在Elasticsearch 5.6或Elasticsearch 6.x中使用。在6.x中,不需要指定index.mapping
。默认为单一类型。
每种文档类型的索引
第一个示例将微博(twitter)索引拆分为推文(tweets)索引和用户(users)索引:
PUT users
{
"mappings": {
"user":{
"properties":{
"name":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"email":{
"type":"keyword"
}
}
}
}
}
PUT tweets
{
"mappings": {
"tweet":{
"properties":{
"content":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"tweet_at":{
"type":"date"
}
}
}
}
}
POST _reindex
{
"source": {
"index":"twitter",
"type":"user"
},
"dest": {
"index":"users"
}
}
POST _reindex
{
"source": {
"index":"twitter",
"type":"tweet"
},
"dest": {
"index": "tweets"
}
}
上述代码在6.5.4版本中运行无误。
上述的示例意思是,在之前我们在微博(twitter)索引中,有两个类型(tweet和user)。现在要将两个类型分开,成为独立的索引。所以,首先先创建出各自的索引(tweets和users),然后通过POST _reindex
来完成迁移工作。
自定义类型字段
第二个示例添加自自定义的type
字段,并将其设置为原始值_type
。它还添加了类型到id,以防有任何不同类型的文档具有冲突的id:
PUT new_twitter
{
"mappings": {
"doc":{
"properties":{
"type":{
"type":"keyword"
},
"name":{
"type":"text"
},
"user_name":{
"type":"keyword"
},
"email":{
"type":"keyword"
},
"content":{
"type":"text"
},
"tweet_at":{
"type":"date"
}
}
}
}
}
POST _reindex
{
"source": {
"index":"twitter"
},
"dest":{
"index": "new_twitter"
},
"script": {
"source": """
ctx._source.type = ctx._type;
ctx._id = ctx._type + "-" + ctx._id;
ctx._type = "doc";
"""
}
}
上述代码在6.5.4版本运行无误。
说明,这篇文档99%参考了官方文档!剩下的1%,添加了自己的理解(不保证准确性)!并且所有的测试都是在kibana中的测试的,kibana和elasticsearch版本都是6.5.4!
总之,通篇看下来,如果对elasticsearch,尤其是各版本不太了解的话,这篇文档看着索然无味!重要的是看不懂,如果我们是新手,接触elasticsearch的时候,就是从6.x版本开始的,那只要记得,一个索引下面只能创建一个类型就行了,其中各字段都具有唯一性,如果在创建映射的时候,如果没有指定文档类型,那么该索引的默认索引类型是_doc
,不指定文档id则会内部帮我们生成一个id字符串。其他的,who care?
see also:[Removal of mapping types](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/removal-of-types.html) 欢迎斧正,that's all