在系统开发过程中,,随着业务不断发展和需求的不断迭代,业务系统中的原有数据表结构无法满足现有功能需求,这个时候常用的做法就是修改表结构,增加或者修改原有schema中的字段类型。
与传统关系型数据相比,ES有一个明显的差异,就是可以在原有索引中添加新的字段数据,而无需做其他类似schema的 调整。在关系型数据库中,表结构定义完毕后,只能在表中插入schema中指定的字段信息。而在ES中是可以打破这个限制的。
动态映射
在ES中,这种可以动态添加新字段的能力叫做动态映射。通过 dynamic 属性控制。该属性 可接受的选项如下:
true:动态添加新的字段
false:忽略新的字段
strict:如果遇到新字段抛出异常
配置参数 dynamic 可以用在根 object 或其他object 类型的字段上。这种灵活的配置可以实现不同对象类型中的动态映射能力。
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic": "strict",
"properties": {
"title": { "type": "string"},
"stash": {
"type": "object",
"dynamic": true
}
}
}
}
}
在上述的配置中,类型my_type中不允许添加新的字段,而在内部对象stash 中可以动态创建新字段。
我们尝试给 stash 对象添加新的可检索的字段:
PUT /my_index/my_type/1
{
"title": "This doc adds a new field",
"stash": { "new_field": "new value" }
}
响应如下:
{
"_index" : "my_index",
"_type" : "my_type",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "This doc adds a new field",
"stash" : {
"new_field" : "new vlaue!"
}
}
}
但是对根节点对象 my_type 进行同样的操作会失败:
PUT /my_index/my_type/1
{
"title": "This throws a StrictDynamicMappingException",
"new_field": "Fail!"
}
{
"error" : {
"root_cause" : [
{
"type" : "strict_dynamic_mapping_exception",
"reason" : "mapping set to strict, dynamic introduction of [new_field] within [my_type] is not allowed"
}
],
"type" : "strict_dynamic_mapping_exception",
"reason" : "mapping set to strict, dynamic introduction of [new_field] within [my_type] is not allowed"
},
"status" : 400
}
虽然ES支持动态映射的能力,但是细心的小伙伴可能会有这样一个问题:向原有的索引中添加一个新的字段,ES会将这个字段当做什么类型处理呢? 因为新添加的字段没有在索引的mapping中指定过类型,此时,那就只能依靠ES自身的规则来判断了,但是业务场景是丰富多样的,完全依赖ES的自身"黑盒"规则,是不能满足需求的,这里我们举例说明一下:
当向索引中第一次添加一个新字段note,并指定value是一个字符串:
{ "note": "2022-01-01" }
当在再次添加note数据:
{ "note": "today is 2022-07-23" }
却会报错。这是为什么呢?
当 ES 遇到一个新的字符串字段时,它会检测这个字段是否包含一个可识别的日期,比如 2022-01-01 。如果它像日期,这个字段就会被作为 date 类型添加,此时新添加的字段note,就被ES标记为date类型了,那么第二次添加 { “note”: “today is 2022-07-23” }时, 发现 “today is 2022-07-23” 不是date类型,此时就会因为类型不匹配而报错。
由此可见,ES的动态映射在类型判定规则上,不太能够满足业务需求。对于此类问题,ES提供了动态模板,让使用者自定义类型解析规则。
动态模板
使用 dynamic_templates ,可以让我们完全控制生成字段的映射。在动态模板中,可以通过字段名称或数据类型来应用不同的映射。
PUT my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{
"string2long": {
"match_mapping_type": "string",
"mapping": {
"type": "long"
}
}
}
]
}
}
}
在上面的动态模板string2long中,使用属性:match_mapping_type 来限制可以使用该模板的新映射:只有sting类型的新映射才可以使用该模板,并且在ES中被映射的类型是long。
PUT my_index/my_type/1
{
"my_long": "5",
}
会使用 string2long模板,并将my_long定义为long类型。
但是如果是以下语句就会存在异常:
PUT my_index/my_type/1
{
"my_string": "abc"
}
因为 "abc"无法被解析成 long类型字段。
上面 match_mapping_type 通过字段类型来控制使用哪个动态模板,在动态模板中match属性,可以根据字段名称来控制使用哪个动态模板:
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "es": {
"match": "*_string",
"mapping": {
"type": "string"
}
}
}}
]
}}
模板es表示:只有字段类型以"_string"结尾的新映射,才会使用该模板。
其实 match_mapping_type 和 match 两个属性可以同时使用,同时使用时,只不过加强了使用该模板的限制,只有两个条件都满足的时候,才会使用该模板,除此之外,一个索引可以定义多个动态模板,多个模板之间的优先级为:模板定义的位置顺序,定义靠前的优先级更高。
PUT /my_index
{
"mappings": {
"dynamic_templates": [
{ "es": {
"match": "*_es",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "spanish"
}
}},
{ "en": {
"match": "*_he",
"match_mapping_type": "long",
"mapping": {
"type": "long"
}
}}
]
}}
在上面定义中,es模板的优先级高于en。使用es模板的条件为:字段类型以"_es"结尾,同时类型为string;而使用en模板的条件为:字段类型为long,同时字段名称以"_he"结尾的映射。