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中的数据类型

null

No field is added.

不会添加字段

true or false

boolean field

boolean

floating point number

float field

double

integer

long field

long

object

object field

object

array

Depends on the first non-null value in the array.

依赖于第一个非null得值

string

Either a date field (if the value passes date detection), a double or long field (if the value passes numeric detection) or a text field, with a keyword sub-field.

如果通过了date检测,则为date

如果通过了numeric检测,则为Number

2.2 date_detection 日期探测

默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mappingdate,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个typedate_detection,如果有需要,自己手动指定某个fielddate类型。

首先删除上面新建的索引

DELETE my_index

然后下面的语句代表的含义为:日期探测为false,表示可添加不是date数据类型的字段,也不会被推断成date类型。address字段里面为true,代表可动态往里面添加新的字段。

PUT /my_index
{
  "mappings": {
    "date_detection": false,
    "properties": {
      "title": {
        "type": "text"
      },
      "address": {
        "type": "object",
        "dynamic": "true"
      }
    }
  }
}

测试插入数据。下面的语句代表最外层级新增contentpost_date两个字段,address层级新增provincecity字段。

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类型的字段,设置它的typetext,使用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
    }
  }
}