Mapping类似数据库中的schema的定义,作用如下:
- 定义索引中的字段的名称
- 定义字段的数据类型,例如字符串,数字,布尔等
- 字段,倒排索引的相关配置(Analyzed or Not Analyzed,Analyzer)
ES中Mapping映射可以分为动态映射和静态映射。
动态映射:
在关系数据库中,需要事先创建数据库,然后在该数据库下创建数据表,并创建表字段、类型、长度、主键等,最后才能基于表插入数据。而Elasticpearch中不需要定义Mapping映射(即关系型数据库的表、字段等),在文档写入Elasticsearch时,会根据文档字段自动识别类型,这种机制称之为动态映射。
动态映射(Dynamic Mapping)的机制,使得我们无需手动定义Mappings,Elasticsearch会自动根据文档信息,推算出字段的类型。但是有时候会推算的不对,例如地理位置信息。当类型如果设置不对时,会导致一些功能无法正常运行,例如Range查询
静态映射:
静态映射是在Elasticsearch中也可以事先定义好映射,包含文档的各字段类型、分词器等,这种方式称之为静态映射。
Dynamic Mapping类型自动识别:
JSON类型 | ElasticSearch类型 |
字符串 |
|
布尔值 | boolean |
浮点数 | float |
整数 | long |
对象 | Object |
数组 | 由第一个非空数值的类型所决定 |
空值 | 忽略 |
动态映射:
创建文档(es根据数据类型,会自动创建映射)
# 创建文档(es根据数据类型,会自动创建映射)
PUT /user/_doc/1
{
"name": "张三",
"age": 26,
"address": "北京市朝阳区"
}
运行结果:
{
"_index" : "user",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
获取文档映射
# 获取文档映射
GET /user/_mapping
运行结果:
{
"user" : {
"mappings" : {
"properties" : {
"address" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
Mapping一旦建立后,那后期是否还可以更新或者修改Mapping字段类型信息呢?
情况一:新增加字段
- dynamic设为true时,一旦有新增字段的文档写入,Mapping 也同时被更新
- dynamic设为false,Mapping 不会被更新,新增字段的数据无法被索引,但是信息会出现在_source中
- dynamic设置成strict(严格控制策略),文档写入失败,抛出异常
情况二:对于已有字段,一旦已经有数据写入,就不再支持修改字段定义
- Lucene实现的倒排索引,—旦生成后,就不允许修改
- 如果希望改变字段类型,必须Reindex API,重建索引
如果修改了字段的数据类型,会导致已被索引的数据无法被搜索;但是如果是增加新的字段,就不会有这样的影响。
首先新建mapping信息:
PUT user2
{
"mappings": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text"
},
"address": {
"type": "object",
"dynamic": "true"
}
}
}
}
运行结果:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "user2"
}
插入新的文档,原因为age为新增字段报错,抛出异常,之前mapping中dynamic设置的为strict
# 插入新的文档,原因为age为新增字段报错,抛出异常,之前mapping中dynamic设置的为strict
PUT user2/_doc/1
{
"name": "张三",
"age": 26,
"address": {
"provienc": "北京市",
"city": "北京市"
}
}
运行结果:
{
"error" : {
"root_cause" : [
{
"type" : "strict_dynamic_mapping_exception",
"reason" : "mapping set to strict, dynamic introduction of [age] within [_doc] is not allowed"
}
],
"type" : "strict_dynamic_mapping_exception",
"reason" : "mapping set to strict, dynamic introduction of [age] within [_doc] is not allowed"
},
"status" : 400
}
接下来修改dynamic信息,更改为true后再次执行上面的语句:
# 修改mapping信息
PUT user2/_mapping
{
"dynamic": "true"
}
运行结果:
{
"acknowledged" : true
}
dynamic修改为true后,再次插入一个新的文档
# dynamic修改为true后,再次插入一个新的文档
PUT user2/_doc/1
{
"name": "张三",
"age": 26,
"address": {
"provienc": "北京市",
"city": "北京市"
}
}
运行结果:
{
"_index" : "user2",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
添加新字段后,再次查询mapping信息
# 添加新字段后,再次查询mapping信息
GET user2/_mapping
运行结果: 【可以看出新增加了age字段的信息】
{
"user2" : {
"mappings" : {
"dynamic" : "true",
"properties" : {
"address" : {
"dynamic" : "true",
"properties" : {
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"provienc" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
}
}
}
}
}
对已经存在的mapping映射进行修改:
具体方法:
- 如果要推倒现有的映射,你得重新建立—个静态索引
- 然后把之前索引里的数据导入到新的索引里
- 删除原创建的索引
- 为新索引起个别名,为原索引名
注意:通过上述4个步骤实现了索引的平滑过渡,并且是零停机!
1.重新创建一个静态索引,并将数据迁移到新索引中
# 重新创建一个静态索引
POST _reindex
{
"source": {
"index": "user2"
},
"dest": {
"index": "new_user2"
}
}
运行结果:
{
"took" : 185,
"timed_out" : false,
"total" : 1,
"updated" : 0,
"created" : 1,
"deleted" : 0,
"batches" : 1,
"version_conflicts" : 0,
"noops" : 0,
"retries" : {
"bulk" : 0,
"search" : 0
},
"throttled_millis" : 0,
"requests_per_second" : -1.0,
"throttled_until_millis" : 0,
"failures" : [ ]
}
2.查看新索引mapping信息
# 查看新索引mapping信息
GET new_user2/_mapping
运行结果: 【新索引和旧索引mapping信息是相同的】
{
"new_user2" : {
"mappings" : {
"properties" : {
"address" : {
"properties" : {
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"provienc" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
3.查看新索引的数据信息
# 查看新索引的数据信息
GET new_user2/_search
运行结果: 【新索引已经包含了旧索引所有的数据】
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "new_user2",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "张三",
"age" : 26,
"address" : {
"provienc" : "北京市",
"city" : "北京市"
}
}
}
]
}
}
4.删除之前的旧索引
# 删除旧索引
DELETE user2
运行结果:
{
"acknowledged" : true
}
5.为新索引起个别名,别名为旧索引的名称
# 为新索引起别名
PUT new_user2/_alias/user2
运行结果:
{
"acknowledged" : true
}
注意:起别名前要确保别名不是已经存在的索引,如果存在则报错!这也是起别名之前先删除旧索引的原因!
6.查询旧索引(即为新索引的别名)
# 查询旧索引(即为新索引的别名)
GET user2
# 效果等同于 GETuser2
GET new_user2
运行结果:
{
"new_user2" : {
"aliases" : {
"user2" : { }
},
"mappings" : {
"properties" : {
"address" : {
"properties" : {
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"provienc" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"settings" : {
"index" : {
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "1",
"provided_name" : "new_user2",
"creation_date" : "1667317074691",
"number_of_replicas" : "1",
"uuid" : "rvKsAyTKQeuc-bWklCW-8A",
"version" : {
"created" : "7170699"
}
}
}
}
}
注意:新起的别名是不能DELETE的!抛出异常!