13、ElasticSearch 7.3 实战:定制动态映射(dynamic mapping)
1、dynamic mapping
ElasticSearch
中有一个非常重要的特性——动态映射,即索引文档前不需要创建索引、类型等信息,在索引的同时会自动完成索引、类型、映射的创建。
当ES在文档中碰到一个以前没见过的字段时,它会利用动态映射(dynamic mapping
)来决定该字段的类型,并自动地对该字段添加映射。
有时这正是需要的行为,但有时不是,需要留意。你或许不知道在以后你的文档中会添加哪些字段,但是你想要它们能够被自动地索引。或许你只是想要忽略它们。或者,尤其当你将ES当做主要的数据存储使用时,大概你会希望这些未知的字段会抛出异常来提醒你注意这一问题。
幸运的是,你可以通过dynamic
设置来控制这一行为,它能够接受以下的选项:
- true:默认值。动态添加字段
- false:新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍将出现在返回点击的源字段中。这些字段不会添加到映射中,必须显式添加新字段。
- strict:如果碰到陌生字段,抛出异常
dynamic
设置可以适用在根对象上或者object
类型的任意字段上。你应该默认地将dynamic
设置为strict
,但是为某个特定的内部对象启用它:
创建mapping
PUT /my_index
{
"mappings": {
"dynamic": "strict",
"properties": {
"title": {
"type": "text"
},
"address": {
"type": "object",
"dynamic": "true"
}
}
}
插入数据
PUT /my_index/_doc/1
{
"title": "my article",
"content": "this is my article",
"address": {
"province": "guangdong",
"city": "guangzhou"
}
}
报错,原因为content
为新增字段。会抛出异常
{
"error": {
"root_cause": [
{
"type": "strict_dynamic_mapping_exception",
"reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
}
],
"type": "strict_dynamic_mapping_exception",
"reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed"
},
"status": 400
}
2、自定义 dynamic mapping策略
2.1 数据类型
如果你知道你需要动态的添加的新字段,那么你也许会启用动态映射。然而有时动态映射的规则又有些不够灵活。幸运的是,你可以调整某些设置来让动态映射的规则更加适合你的数据。
es会根据传入的值,推断类型,具体如下表所示。
JSON data type |
Elasticsearch data type |
ES中的数据类型 |
|
No field is added. |
不会添加字段 |
|
|
boolean |
floating point number |
|
double |
integer |
|
long |
object |
|
object |
array |
Depends on the first non- |
依赖于第一个非null得值 |
string |
Either a |
如果通过了date检测,则为date 如果通过了numeric检测,则为Number |
2.2 date_detection 日期探测
默认会按照一定格式识别date
,比如yyyy-MM-dd
。但是如果某个field
先过来一个2017-01-01
的值,就会被自动dynamic mapping
成date
,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type
的date_detection
,如果有需要,自己手动指定某个field
为date
类型。
首先删除上面新建的索引
DELETE my_index
然后下面的语句代表的含义为:日期探测为false
,表示可添加不是date数据类型的字段,也不会被推断成date
类型。address
字段里面为true
,代表可动态往里面添加新的字段。
PUT /my_index
{
"mappings": {
"date_detection": false,
"properties": {
"title": {
"type": "text"
},
"address": {
"type": "object",
"dynamic": "true"
}
}
}
}
测试插入数据。下面的语句代表最外层级新增content
、post_date
两个字段,address
层级新增province
、city
字段。
PUT /my_index/_doc/1
{
"title": "my article",
"content": "this is my article",
"address": {
"province": "guangdong",
"city": "guangzhou"
},
"post_date": "2019-09-10"
}
查看映射
GET /my_index/_mapping
返回,可以看到字段都成功新增,日期检测(date_detection
)为false
,所以post_date
字段类型不是date
类型。
{
"my_index" : {
"mappings" : {
"date_detection" : false,
"properties" : {
"address" : {
"dynamic" : "true",
"properties" : {
"city" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"province" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"content" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"post_date" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text"
}
}
}
}
}
下面为自定义日期格式语法,读者可自行试验。
PUT my_index
{
"mappings": {
"dynamic_date_formats": ["MM/dd/yyyy"]
}
}
插入数据
PUT my_index/_doc/1
{
"create_date": "09/25/2019"
}
2.3 numeric_detection 数字探测
虽然json
支持浮点和整数数据类型,但某些应用程序或语言有时可能需要将数字呈现为字符串。通常正确的解决方案是显式地映射这些字段,但是可以启用数字检测(默认情况下禁用)来自动完成这些操作。
首先删除上面新建的索引
DELETE my_index
然后开启数字检测
PUT my_index
{
"mappings": {
"numeric_detection": true
}
}
插入数据
PUT my_index/_doc/1
{
"my_float": "1.0",
"my_integer": "1"
}
查看映射
GET my_index/_mapping
返回
{
"my_index" : {
"mappings" : {
"numeric_detection" : true,
"properties" : {
"my_float" : {
"type" : "float"
},
"my_integer" : {
"type" : "long"
}
}
}
}
}
可以看到两个字段被映射为浮点类型了。
3、定制dynamic mapping template
通过dynamic_templates
,你可以拥有对新字段的动态映射规则拥有完全的控制。你设置可以根据字段名称或者类型来使用一个不同的映射规则。
每个模板都有一个名字,可以用来描述这个模板做了什么。同时它有一个mapping
用来指定具体的映射信息,和至少一个参数(比如match
)用来规定对于什么字段需要使用该模板。
首先删除上面新建的索引
DELETE my_index
运行下面的语句,代表的含义为:匹配以_en
结尾并且是string
类型的字段,设置它的type
为text
,使用english
分词。
PUT /my_index
{
"mappings": {
"dynamic_templates": [
{
"en": {
"match": "*_en",
"match_mapping_type": "string",
"mapping": {
"type": "text",
"analyzer": "english"
}
}
}
]
}
}
插入数据
PUT /my_index/_doc/1
{
"title": "this is my first article"
}
PUT /my_index/_doc/2
{
"title_en": "this is my first article"
}
查看映射
GET my_index/_mapping
返回,可以看到title_en
字段采用english
分词器,说明模板生效了。
{
"my_index" : {
"mappings" : {
"dynamic_templates" : [
{
"en" : {
"match" : "*_en",
"match_mapping_type" : "string",
"mapping" : {
"analyzer" : "english",
"type" : "text"
}
}
}
],
"properties" : {
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title_en" : {
"type" : "text",
"analyzer" : "english"
}
}
}
}
}
搜索
GET my_index/_search?q=first
GET my_index/_search?q=is
title
字段没有匹配到任何的dynamic
模板,默认就是standard
分词器,不会过滤停用词,is
会进入倒排索引,用is
来搜索是可以搜索到的
title_en
字段匹配到了dynamic
模板,就是english
分词器,会过滤停用词,is
这种停用词就会被过滤掉,用is
来搜索就搜索不到了
4、模板写法
下面给出了一些大概的写法,读者可根据自身实际需求自定义模板
PUT my_index
{
"mappings": {
"dynamic_templates": [
{
"integers": {
"match_mapping_type": "long",
"mapping": {
"type": "integer"
}
}
},
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "text",
"fields": {
"raw": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
]
}
}
模板参数:匹配满足的字段、不匹配满足的字段、匹配的数据类型、路径匹配、路径不匹配。
"match": "long_*",
"unmatch": "*_text",
"match_mapping_type": "string",
"path_match": "name.*",
"path_unmatch": "*.middle",
"match_pattern": "regex",
"match": "^profit_\d+$"
5、应用场景
5.1 结构化搜索
默认情况下,elasticsearch
将字符串字段映射为带有子关键字(keyword
)字段的文本字段。但是,如果只对结构化内容进行索引,而对全文搜索不感兴趣,则可以仅将“字段”映射为“关键字”。但是请注意,这意味着为了搜索这些字段,必须搜索索引所用的完全相同的值。
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
5.2 仅搜索
与前面的示例相反,如果您只关心字符串字段的全文搜索,并且不打算对字符串字段运行聚合、排序或精确搜索,您可以将其仅映射为文本字段(这是es5
之前的默认行为)
{
"strings_as_text": {
"match_mapping_type": "string",
"mapping": {
"type": "text"
}
}
}
5.3 norms 不关心评分
当计算得分的时候,是否需要把字段长度用作参数计算。
尽管计算得分时把字段长度考虑在内可以提高得分的精确性,但这样会消耗大量的磁盘空间(每个文档的每个字段都会消耗一个字节,即使某些文档不包含这个字段)。因此,如果不需要计算字段的得分,你应该禁用该字段的norms
。特别是这个字段仅用于聚合或者过滤。
{
"properties": {
"title": {
"type": "text",
"norms": false
}
}
}