前面我们聊了 Elasticsearch 的索引、搜索和分词器,今天再来聊另一个基础内容—— Mapping。

Mapping 在 Elasticsearch 中的地位相当于关系型数据库中的 schema,它可以用来定义索引中字段的名字、定义字段的数据类型,还可以用来做一些字段的配置。从 Elasticsearch 7.0开始,Mapping 中不在乎需要定义 type 信息了,具体原因可以看官方的解释

字段的数据类型

我们刚刚提到 Mapping 中可以定义字段的数据类型,这可能是 Mapping 最常用的功能了,所以我们先来看看 Elasticsearch 都支持哪些数据类型。

  • 简单类型:text、keyword、date、long、double、boolean、ip
  • 复杂类型:对象类型、嵌套类型
  • 特殊类型:用于描述地理位置的 geo_point、geo_shape

Elasticsearch 支持的数据类型远不止这些,由于篇幅原因,这里就不一一列举了。我找几个工作中常见的来介绍一下。

首先就是字符串了,Elasticsearch 中的字符串有 text 和 keyword 两种。其中 text 类型的字符串是可以被全文检索的,它会被分词器作用,

PUT my_index
{
  "mappings": {
    "properties": {
      "full_name": {
        "type":  "text"
      }
    }
  }
}

在设置字段类型为 text 时,还可以利用一些参数对这个字段进行更进一步的定制。

index:标记这个字段是否能被搜索,默认是 true

search_analyzer:被搜索时所使用的分词器,默认使用 setting 中设置的分词器

fielddata:字段是否允许在内存中进行排序、聚合,默认是 false

meta:关于字段的一些元数据

像一些id、邮箱、域名这样的字段,我们就需要使用 keyword 类型了。因为 keyword 类型可以支持排序、聚合,并且只能支持精确查询。

有些同学可能会把 ID 设置为数字类型,这也是没问题的,数字类型和 keyword 各有各的好处,使用数字类型可以进行范围查找,而使用 keyword 类型则有更高的查询效率。具体用哪种还要看使用场景。

日期类型在 Elasticsearch 中有三种表现形式

  1. 可以格式化成日期类型的字符串,如"2020-07-26""2015/01/01 12:10:30"这样的
  2. 毫秒级时间戳用 long 类型表示
  3. 秒级时间戳用 integer 类型表示

在 Elasticsearch 内部,日期类型是以 long 类型的毫秒级时间戳存储的,时区使用的是0时区。

我们可以自定义时间格式,默认使用的是strict_date_optional_time||epoch_millis

strict_date_optional_time_nanos是通用的日期格式解析,至少要包含年份,如果要包含时间,则用T分隔,例如yyyy-MM-dd'T'HH:mm:ss.SSSSSSZyyyy-MM-dd

如果想要同时支持多种日期格式,可以使用format字段

PUT my_index
{
  "mappings": {
    "properties": {
      "date": {
        "type":   "date",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

Mapping参数

刚才我们提到配置 Mapping 的日期格式的参数format,Mapping 还提供了很多其他的参数。

  • analyzer
  • boost
  • coerce
  • copy_to
  • doc_values
  • dynamic
  • eager_global_ordinals
  • enabled
  • fielddata
  • fields
  • format
  • ignore_above
  • ignore_malformed
  • index_options
  • index_phrases
  • index_prefixes
  • index
  • meta
  • normalizer
  • norms
  • null_value
  • position_increment_gap
  • properties
  • search_analyzer
  • similarity
  • store
  • term_vector

我们来介绍几个常用的字段。

fields

首先是fields,它可以使同一个字段通过不同的方式实现不同的目的。

例如,我们可以对一个字符串字段设置为text类型,用于全文检索,同时可以利用fields设置为keyword类型,用于排序和聚合。

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "city": {
        "type": "text",
        "fields": {
          "raw": {
            "type":  "keyword"
          }
        }
      }
    }
  }
}

查询时我们就可以使用city进行全文检索,使用city.raw进行排序和聚合。

GET my-index-000001/_search
{
  "query": {
    "match": {
      "city": "york"
    }
  },
  "sort": {
    "city.raw": "asc"
  },
  "aggs": {
    "Cities": {
      "terms": {
        "field": "city.raw"
      }
    }
  }
}

enabled

有些时候,我们只想把某个字段作为数据存储来使用,并不需要用来做搜索,这时,我们就可以将这个字段禁用掉,字段被禁用以后,它所保存的值也不受 mapping 指定的类型控制。

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "user_id": {
        "type":  "keyword"
      },
      "last_updated": {
        "type": "date"
      },
      "session_data": {
        "type": "object",
        "enabled": false
      }
    }
  }
}

上面的例子中,我们禁用掉了 session_data 这个字段,这时,你既可以往 session_data 字段中存储 JSON 格式的数据,也可以存储非 JSON 格式的数据。

除了针对于单个字段的禁用以外,我们还可以直接禁用掉整个 mapping。我们来重新创建一个index

PUT my-index-000002
{
  "mappings": {
    "enabled": false
  }
}

这时,文档所有的字段都不会被索引,只是用来存储。

需要注意的是,无论是具体字段中还是整个 mapping 的 enabled 属性都不可以被修改,因为一旦设置为 false,Elasticsearch 就不会对字段进行索引了,也不会校验数据的合法性,如果产生了脏数据以后再设置为 true,就会造成程序错误。

null_value

null 在 Elasticsearch 中是不可以被索引或搜索的,这里我们所说的 null 并不是狭义上某种语言的 null,而是所有的空值。例如所有值都是 null 的数组,总之,这里的定义就是没有值。

对于有需要搜索空值的业务怎么办呢?Elasticsearch 为我们提供了 null_value 这个参数,它可以指定一个值,搜索时使用这个值来替代空值。

举个栗子

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "status_code": {
        "type":       "keyword",
        "null_value": "NULL"
      }
    }
  }
}

我们给 status_code 字段设置了 null_value"NULL"。这里需要注意, null_value 的类型必须与要查找的数据类型相同,如果在这个例子中 status_code 的类型是long,那么就不能把null_value 设置为 "NULL"

dynamic

对于新增加的字段:

  • dynamic 设置为 true 时,一旦有新增字段的文档写入,Mapping 也会被更新
  • dynamic 设置为 false 时,Mapping 不会被更新,新增字段无法被索引,但信息会出现在 _source
  • dynamic 设置为 strict 时,文档写入失败

对于已有的字段,一旦已经有数据写入,就不再支持修改字段定义

Dynamic Mapping

我们在创建索引时,可以不用手动写 Mappings, Elasticsearch 会帮我们自动识别出字段的类型。我们称之为 Dynamic Mapping。不过有时推算的可能不是很准确。

Elasticsearch 自动识别类型是基于 JSON 的。数据类型的对应关系如下(表格来自 elastic 官网)

Elasticsearch 支持的字段映射的数据类型在这个文档中,除了这些,其他的类型映射都需要显示的指定了。

关于日期类型,默认是可以映射的,但是 Elasticsearch 只能识别几种格式的日期yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis。如果关掉了 date_detection 开关,那么就只能识别为字符串了。

PUT my-index-000001
{
  "mappings": {
    "date_detection": false
  }
}

当然,你也可以根据需要自己指定要识别的日期格式,只需要使用 dynamic_date_formats 参数即可。

PUT my-index-000001
{
  "mappings": {
    "dynamic_date_formats": ["MM/dd/yyyy"]
  }
}

Elasticsearch 还提供了一种把字符串型的数字识别为数字的能力,它是由 numeric_detection 开关控制的。

PUT my-index-000005
{
  "mappings": {
    "numeric_detection": true
  }
}

PUT my-index-000005/_doc/1
{
  "my_float":   "1.0",
  "my_integer": "1"
}

在这个例子中,my_float 会被识别为 float 类型,而 my_integer 会被识别为 long 类型。

Dynamic template

dynamic template 允许我们自定义 mapping ,并应用到具体索引上。dynamic template 的定义一般是这样的

  "dynamic_templates": [
    {
      "my_template_name": {
        ...  match conditions ...
        "mapping": { ... }
      }
    },
    ...
  ]

my_template_name 可以是任意字符串。

match conditions 包括match_mapping_type, match, match_pattern, unmatch, path_match, path_unmatch 这几种。

mapping 就是指匹配到的字段应该使用怎样的 mapping。下面我们介绍几种 match conditions

match_mapping_type

我们先来看一个简单的例子

PUT my-index-000001
{
  "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
              }
            }
          }
        }
      }
    ]
  }
}

这里我们有两个模版,其一是使用 integer 类型来代替 long 类型,其二是将字符串类型映射为 keyword

match 和 unmatch

这两个比较简单,match 是指匹配到模式的字段, unmatch 是表示不匹配的字段。

PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "longs_as_strings": {
          "match_mapping_type": "string",
          "match":   "long_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "long"
          }
        }
      }
    ]
  }
}

在这个例子中,我们需要的是 long_ 开头的字符串,不需要 _text结尾的字符串字段。

除了以上三种之外,其他的就是 match_pattern 用来进行正则匹配,path_matchpath_unmatch 则是表示字段所在路径的是否匹配。

另外 dynamic template 还支持两种变量替换,分别是 {name}{dynamic_type}。其实 name 就是字段名,dynamic_type 就是检测出的字段类型。

总结

关于 Elasticsearch 的 mapping 我们就先聊这些,我认为 mapping 的配置是一个需要经验的事情,当你处理的 case 越来越多之后,就能比较轻松的知道如何更好的配置 mapping 了。此外,mapping 的许多字段和参数文中都没有涉及,对于我而言,大部分都是用到了现查文档,不过也还是建议大家看一看文档,起码遇到问题时能知道大概查找文档的一个方向。这样就会比身边人强不少。

08-05 12:11