重要:
在Elasticsearch6.0.0或更新的版本中创建的索引可能只包含一个单一的映射类型。
在Elasticsearch5.x中创建的多映射类型的索引在Elasticsearch6.x中将如以前一样可用。
映射类型将在Elasticsearch7.0.0被彻底移除。
什么是映射类型?
从Elasticsearch的第一版发布开始,每个文档被存储在一个单独的索引,并分配一个映射类型。一个映射类型被用于表示文档或被索引的实体的类型,例如一个 twitter 索引可能有一个 user 类型和一个 tweet 类型。
每个索引类型可以有属于它自己的字段,所以 user 类型可能有一个 full_name 字段,一个 user_name 字段,和一个 email 字段,tweet类型可能有一个 content 字段,一个 tweeted_at 字段,像user类型,有一个 user_name 字段。
每个文档有一个 _type 的元数据字段包含了这个类型的名称,然后通过在URL中指定类型的名称,发起的搜索能够被限制在一个或多个索引中:
GET twitter/user,tweet/_search
{
"query": {
"match": {
"user_name": "kimchy"
}
}
}
这个 _type 字段和文档的 _id 字段一起生成了一个 _uid 字段,所以在一个单独的索引中可以有不同类型中文档有着相同的 _id。
映射类型也同样被用于建立文档间的 parent-child relationship ,所以文档类型是 question的文档能够是文档类型是 answer的 parents。
为什么要删除映射类型?
一开始,我们我们谈到 一个 ES的索引类似于关系型数据库中的数据库,一个映射类型则相当于关系型数据库中的一张表。
这是一个错误的类比,导致了错误的假设。在一个关系型数据库中,表之间是相互独立的。一个表中的列与另一个表中同名的列没有关系。然而在映射类型中却不是这样的。
在一个Elasticsearch的索引中,有相同名称字段的不同映射类型在Lucene内部是由同一个字段支持的。换言之,看下面的这个例子,user 类型中的 user_name字段和tweet类型中的user_name字段实际上是被存储在同一个字段中,而且两个user_name字段在这两种映射类型中都有相同的定义(如类型都是 text或者都是date)。
这会导致一些问题,比如,当你希望在一个索引中的两个映射类型,一个映射类型中的 deleted 字段映射为一个日期数据类型的字段,而在另一个映射类型中的deleted字段映射为一个布尔数据类型的字段,这就会失败。
最重要的是,在一个索引中存储那些有很少或没有相同字段的实体会导致稀疏数据,并且干扰Lucene有效压缩文档的能力。
基于这些原因,我们决定从Elasticsearch中删除映射类型的概念。
映射类型的替代选择
每个索引一个文档类型
第一个替代方案是每个索引一个文档类型。你可以把 tweets存储在一个 tweets 索引和一个 user索引中,而不是把他们存储在同一个索引中。索引之间是完全独立的,所以他们之间不存在类型冲突。
这个方法有两个好处:
- 数据更有可能是密集的,因此可以从Lucene中使用的压缩技术中获益。
- 在全文搜索中的为评分使用的单词统计更有可能是准确的,因为同一个索引中的所有文档都代表一个单一的实体类型。
每个索引都可以根据它包含的文档的数量设置索引的大小:你可以使用较少的主分片给 users,更多的主分片给 tweets。
自定义类型字段
当然,在集群中可以存在的主分片是有限制的,因此你可能不想浪费一整个分片来存储仅仅几千个文档。在这种情况下,你可以实现你自己定义的 type 字段,该字段将以类似于旧的 _type 的方式工作。
让我们继续使用上面的 user/tweet例子。
最初,工作流应该是这样的:
PUT twitter
{
"mappings": {
"user": {
"properties": {
"name": { "type": "text" },
"user_name": { "type": "keyword" },
"email": { "type": "keyword" }
}
},
"tweet": {
"properties": {
"content": { "type": "text" },
"user_name": { "type": "keyword" },
"tweeted_at": { "type": "date" }
}
}
}
}
PUT twitter/user/kimchy
{
"name": "Shay Banon",
"user_name": "kimchy",
"email": "shay@kimchy.com"
}
PUT twitter/tweet/1
{
"user_name": "kimchy",
"tweeted_at": "2017-10-24T09:00:00Z",
"content": "Types are going away"
}
GET twitter/tweet/_search
{
"query": {
"match": {
"user_name": "kimchy"
}
}
}
你可以通过添加自定义类型字段类实现相同的目标:
PUT twitter
{
"mappings": {
"doc": {
"properties": {
"type": { "type": "keyword" },
"name": { "type": "text" },
"user_name": { "type": "keyword" },
"email": { "type": "keyword" },
"content": { "type": "text" },
"tweeted_at": { "type": "date" }
}
}
}
}
PUT twitter/doc/user-kimchy
{
"type": "user",
"name": "Shay Banon",
"user_name": "kimchy",
"email": "shay@kimchy.com"
}
PUT twitter/doc/tweet-1
{
"type": "tweet",
"user_name": "kimchy",
"tweeted_at": "2017-10-24T09:00:00Z",
"content": "Types are going away"
}
GET twitter/_search
{
"query": {
"bool": {
"must": {
"match": {
"user_name": "kimchy"
}
},
"filter": {
"match": {
"type": "tweet"
}
}
}
}
}
显式类型 type 字段取代隐式 _type 字段。
没有映射类型的Parent/Child
以前,一个parent-child 关系由一个父元素的映射类型和一个或多个子元素的映射类型来表示。如果没有映射类型,我们就不能使用这种语法。parent-child特性将继续像以前一样发挥作用,只是表示文档之间的关系的表达方式已经被更改为使用新的 join 字段。
删除映射类型的计划安排
这对我们的用户来说是一个巨大的变化,所以我们尽可能让它不痛苦。这个变化将会如下所示:
Elasticsearch 5.6.0
- 设置 index.mapping.single_type: true 将在索引上启用6.0中执行的单类型/索引 行为
- 对于在5.6中创建的索引,可以使用 join 字段代替 parent-child.
Elasticsearch 6.x
- 在5.x中创建的索引将在6.x中和之前5.0中一样起作用
- 在6.x中创建的索引仅允许以单类型/索引的方式。任何名称都可以用于该类型,但只有一个类型名。
- _type字段不能再与_id结合以形成_uid字段。_uid字段已成为_id字段的别名。 新的索引不再支持旧式样式的parent/child,而是应该使用 join 字段。
- 弃用 default 映射类型
Elasticsearch 7.x
- URL中的 type 参数是可选的。例如,索引文档不再需要文档类型。
- GET|PUT _mapping api
支持一个查询字符串参数(include_type_name),该参数指示主题是否应该包含一个类型名称的层。默认值是
true。7.x中没有显式类型的索引将使用 doc 作为类型的名称。 - 删除 default 映射类型
Elasticsearch 8.x
- URL中不再支持 type 参数
- Include_type_name 参数默认是 false
Elasticsearch 9.x
- 删除include_type_name参数
迁移多类型索引到单类型
可以使用 Reindex API 将多类型索引转换为单类型索引。下面的例子能够在Elasticsearch 5.6或Elasticsearch6.x中使用。在6.x中,没有必要指定 index.mapping.single_type,因为这是默认的。
索引一个文档类型
第一个例子用于分离我们 twitter索引到一个 tweets索引和一个 users 索引:
PUT users
{
"settings": {
"index.mapping.single_type": true
},
"mappings": {
"user": {
"properties": {
"name": {
"type": "text"
},
"user_name": {
"type": "keyword"
},
"email": {
"type": "keyword"
}
}
}
}
}
PUT tweets
{
"settings": {
"index.mapping.single_type": true
},
"mappings": {
"tweet": {
"properties": {
"content": {
"type": "text"
},
"user_name": {
"type": "keyword"
},
"tweeted_at": {
"type": "date"
}
}
}
}
}
POST _reindex
{
"source": {
"index": "twitter",
"type": "user"
},
"dest": {
"index": "users"
}
}
POST _reindex
{
"source": {
"index": "twitter",
"type": "tweet"
},
"dest": {
"index": "tweets"
}
}
自定义类型字段
下一个示例将添加一个自定义类型字段,并将其设置为原始 _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"
},
"tweeted_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';
"""
}
}