索引映射:Mapping

861 阅读18分钟

当在Elasticsearch中进行数据映射时,可以定义如何存储和索引文档及其字段。数据映射包括以下要点:

  1. 映射定义:创建一个包含文档字段的映射定义。此定义还可以包括元数据字段,如_source字段。

  2. 动态映射:Elasticsearch可以根据文档中出现的字段自动创建映射。它会推断字段的数据类型并进行映射,适应动态变化的数据结构。

  3. 显式映射:可以通过显式定义字段的映射类型和属性来进行数据映射。这允许我们精确控制字段的数据类型、索引选项、分词器等。

  4. 动态映射和显式映射的结合使用:可以使用动态映射和显式映射相结合的方式,根据需求灵活定义和管理数据映射。通过显式映射可以提供更高的控制性和灵活性,而动态映射则可以适应动态变化的数据结构。

  5. 映射限制:使用映射限制设置来限制字段映射的数量(无论是手动创建还是动态创建),防止文档导致映射膨胀。通过设置合理的映射限制,可以控制字段的增长速度,防止映射膨胀带来的性能和存储问题。

通过合理使用数据映射,可以确保数据正确地存储和索引,并获得更好的查询和分析性能。动态映射和显式映射各有优势,可以根据具体需求选择合适的方法来定义数据映射。


1. 动态映射

Elasticsearch的一个重要特性是尽可能地让你快速开始探索数据。在索引一个文档时,不需要事先创建索引、定义映射类型或定义字段,只需索引一个文档,索引、类型和字段将自动显示出来。这种自动化的机制使得我们能够迅速地开始使用Elasticsearch,而无需过多的设置和配置。我们可以直接开始索引和查询数据,让Elasticsearch自动处理索引、类型和字段的细节。

For Example:

PUT data/_doc/1 
{ "count": 5 }

上面的SQL,在Elasticsearch中创建了一个"data"索引,使用了名为"_doc"的映射类型,并定义了一个名为"count"的字段,其数据类型为长整型(long)。

Elasticsearch提供了动态映射(Dynamic Mapping)的功能,可以自动检测和添加新的字段。动态映射规则可以根据需要进行定制,包括以下两个方面:

  • 动态字段映射(Dynamic Field Mappings):可以定义字段的数据类型和属性,以适应不同类型的数据。例如,对于字符串字段,可以定义其是否被视为全文字段、是否进行分词等。

  • 动态模板(Dynamic Templates):可以自定义规则,根据匹配条件对动态添加的字段进行映射配置。通过动态模板,可以对不同类型的字段应用不同的映射规则,从而更灵活地管理动态字段。

这些功能使得Elasticsearch能够根据数据的特点自动适应新字段的添加,方便用户进行索引和查询操作。

前面介绍过,索引模板(Index Templates)允许你为新创建的索引(无论是自动创建还是显式创建)配置默认的映射(Mappings)、设置(Settings)和别名(Aliases)。


1.1 动态字段映射

当Elasticsearch检测到文档中的新字段时,默认情况下会动态地将该字段添加到类型映射中。dynamic参数控制此行为。 我们可以显式地告诉Elasticsearch根据传入的文档动态创建字段,方法是将dynamic参数设置为true或runtime。当启用动态字段映射时,Elasticsearch根据下表中的规则确定如何为每个字段映射数据类型。

下表中列出的字段数据类型是Elasticsearch能够动态检测的字段数据类型。对于其他数据类型,必须显式地进行字段映射。

JSON数据类型dynamic:truedyncmic:runtime
null不会新增字段不会新增字段
true/falsebooleanboolean
doublefloatdouble
longlonglong
objectobject不会新增字段
array取决于数组中第一个不为空的值取决于数组中第一个不为空的值
string(日期格式)datedate
string(数值格式)float/longdouble/long
string(其他格式)text类型的字段但是会带有一个keyword类型的子字段keyword

可以在文档级别和对象级别上禁用动态映射。将dynamic参数设置为false会忽略新字段,而strict模式会在遇到未知字段时拒绝文档的索引。

使用update mapping API可以更新现有字段的动态设置。

可以自定义日期检测和数字检测的动态字段映射规则。要定义自定义映射规则,可以应用于其他动态字段,请使用dynamic_templates

1)日期检测

如果启用了date_detection(默认情况下),则会检查新的字符串字段,看其内容是否与dynamic_date_formats中指定的日期模式匹配。如果找到匹配项,则会添加一个新的日期字段,并使用相应的日期格式。

dynamic_date_formats的默认值为:["strict_date_optional_time", "yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]

For example:

PUT my-index-000001/_doc/1
{
  "create_date": "2015/09/02"
}

GET my-index-000001/_mapping 

# response
{
  "my-index-000001" : {
    "mappings" : {
      "properties" : {
        "create_date" : {
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
        }
      }
    }
  }
}

设置date_detection=false可以禁用掉日期检测。

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

PUT my-index-000001/_doc/1 
{
  "create_date": "2015/09/02"
}

create_date 将作为text类型的字段被添加映射中。

dynamic_date_formats 支持我们自定义日期格式。

For Example:

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

PUT my-index-000001/_doc/1
{
  "create_date": "09/25/2015"
}

2)数值检测

数值检测(Numeric detection)是指在Elasticsearch中自动识别字符串字段中的数值,并将其映射为数值类型。默认情况下,Elasticsearch会动态检测字符串字段中的数值,并根据数值的特征将其映射为浮点数(float)或长整型(long)数据类型。

For Example,如果一个字符串字段只包含整数值,Elasticsearch会将其映射为长整型(long)数据类型。如果数值包含小数点或科学计数法,Elasticsearch会将其映射为浮点数(float)数据类型。

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

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

数值检测在Elasticsearch中默认是启用的。但是可以通过修改动态映射规则并指定自定义的数值检测模式来自定义这个行为。

1.2 动态模板

Dynamic templates允许在默认的动态字段映射规则之外更精细地控制Elasticsearch如何映射数据。通过将dynamic参数设置为true或runtime,可以启用动态映射。然后可以使用dynamic templates定义自定义映射,根据匹配条件应用于动态添加的字段:

  • match_mapping_type:基于Elasticsearch检测到的数据类型进行操作。
  • match和unmatch:使用模式匹配字段名称。
  • path_matchpath_unmatch:操作字段的完整点分路径。

如果动态模板没有定义match_mapping_typematchpath_match,它将不会匹配任何字段。但是仍然可以通过名称引用模板,在bulk请求的dynamic_templates部分使用。

在映射规范中,可以使用{name}{dynamic_type}模板变量作为占位符。这样可以根据实际情况为字段指定映射属性。

动态字段映射只会在字段包含具体值时添加。当字段包含null或空数组时,Elasticsearch不会添加动态字段映射。如果在dynamic_template中使用了null_value选项,它只会在索引了具体值的第一个文档之后应用。换句话说,只有在字段有实际值的情况下,才会应用null_value选项定义的值

动态模板以命名对象的数组形式进行指定:

  "dynamic_templates": [
    {
      # 模板名随便起
      "my_template_name": { 
        # 匹配条件可以包含任意 match_mapping_type, match, match_pattern, unmatch, path_match, path_unmatch.
        ... match conditions ... 
        # 匹配上的字段要使用的映射。
        "mapping": { ... } 
      }
    },
    ...
  ]

通常实际开发并不会用到动态映射,这样不好控制实体结构,更多细节->官方文档


2. 显式映射

可以在创建索引时创建字段映射或者向现有索引添加字段。

显式创建索引并指定字段映射。

PUT /my-index-000001
{
  "mappings": {
    "properties": {
      "age":    { "type": "integer" },  
      "email":  { "type": "keyword"  }, 
      "name":   { "type": "text"  }     
    }
  }
}

向现有的索引里面新增字段。

PUT /my-index-000001/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false
    }
  }
}

在Elasticsearch中,不支持直接修改现有字段的数据类型。更改现有字段的数据类型可能导致数据的不一致或丢失。 如果需要更改字段的数据类型,一种常用的做法是创建一个新的索引,并使用新的映射定义来重新索引数据。这样可以确保数据在新字段类型下正确地解析和索引。 另外,可以考虑使用索引别名来实现字段类型的变更。通过创建一个别名,将原始字段和新字段进行关联,然后逐步将应用程序中的查询和索引操作迁移到新字段上,最终停用或删除原始字段。

查看某个索引的映射。

GET /my-index-000001/_mapping

查看索引里指定字段的映射。

GET /my-index-000001/_mapping/field/employee-id

3.运行时字段

运行时字段是在查询时计算的字段。运行时字段可以:

  1. 在不重新索引数据的情况下向现有文档添加字段
  2. 在不了解数据结构的情况下开始处理数据
  3. 在查询时覆盖来自索引字段的值
  4. 为特定用途定义字段而不修改底层模式

我们可以像访问其他字段一样从搜索API访问运行时字段,而Elasticsearch将运行时字段视为普通字段。可以在索引映射或搜索请求中定义运行时字段。这是运行时字段固有灵活性的一部分。

使用_search APIfields参数检索运行时字段的值。运行时字段不会显示在_source字段中,但fields API适用于所有字段,即使它们在原始_source中没有发送。

运行时字段在处理日志数据时非常有用,特别是当你对数据结构不确定时。查询速度会减慢,但索引大小要小得多,可以更快地处理日志而无需对其进行索引。

下面是使用 Elasticsearch 运行时字段的步骤:

  1. 定义索引映射:首先需要在索引的映射中添加一个 runtime 字段定义。可以在索引创建时或后续通过修改映射来添加运行时字段。示例映射定义如下:
PUT /my-index
{
  "mappings": {
    "properties": {
      "runtime_field": {
        "type": "runtime",
        "script": {
          "source": "emit('Hello, World!')"
        }
      }
    }
  }
}

我们添加了一个名为 "runtime_field" 的运行时字段,它使用脚本计算并返回 "Hello, World!"。

  1. 查询使用运行时字段:在查询时,可以使用运行时字段进行过滤、排序和聚合操作。以下是使用运行时字段的示例查询:
GET /my-index/_search
{
  "query": {
    "term": {
      "runtime_field": "Hello, World!"
    }
  }
}

我们使用运行时字段 "runtime_field" 进行了一个术语查询,以筛选包含值 "Hello, World!" 的文档。

  1. 运行时字段的计算逻辑:运行时字段的计算逻辑是通过脚本定义的。可以使用 Painless 脚本语言编写计算逻辑,并通过脚本的 emit 方法返回计算结果。脚本可以访问文档的原始数据和映射字段的值,以便进行计算。以下是一个示例计算平方根的运行时字段:
PUT /my-index
{
  "mappings": {
    "properties": {
      "value": {
        "type": "integer"
      },
      "sqrt_value": {
        "type": "runtime",
        "script": {
          "source": "emit(Math.sqrt(params.value))"
        }
      }
    }
  }
}

我们定义了一个名为 "sqrt_value" 的运行时字段,它通过计算 "value" 字段的平方根来返回结果。

通过使用 Elasticsearch 的运行时字段,可以动态计算和获取与文档相关的数据,而无需预先定义这些字段。这为灵活地处理数据提供了便利,并且可以在查询时根据需要定制字段计算逻辑。


4. 字段类型

以下是Elasticsearch 7.x 版本支持的大部分字段类型及简要说明:

字段类型说明
boolean布尔类型,表示真或假
binary二进制类型,用于存储二进制数据
keyword用于精确匹配和排序的关键字类型,不进行分词和索引
long长整型,表示整数值
integer整型,表示整数值
short短整型,表示小范围整数值
byte字节型,表示很小的整数值
double双精度浮点型,表示小数值
float单精度浮点型,表示小数值
half_float半精度浮点型,表示小数值
scaled_float缩放浮点型,适用于需要更高精度的小数值
date日期类型,表示日期和时间
alias别名类型,用于为字段创建别名
text用于全文搜索的文本类型,进行分词和索引
range范围类型,表示一定范围内的数值
object对象类型,用于嵌套复杂结构的字段
nested嵌套类型,类似于对象类型,但可以在查询中单独检索和过滤嵌套字段
ipIP地址类型,表示IPv4或IPv6地址
completion完全匹配类型,用于自动完成和搜索建议
token_count令牌计数类型,用于存储文本字段中的令牌数量
percolator预处理器类型,用于查询与文档相匹配的查询语句
join关联类型,用于定义父子关系和多对多关系
geo_point地理点类型,用于表示地理坐标点
geo_shape地理形状类型,用于表示地理区域和多边形
murmur3哈希类型,用于存储哈希值
attachment附件类型,用于存储和索引文档附件
per_field_analyzer指定字段使用不同的分析器

在Elasticsearch中,数组不需要专用的字段数据类型。默认情况下,任何字段都可以包含零个或多个值,但是,数组中的所有值都必须是相同的字段类型。

4.1 字符串类型

在 7.x 之后的版本中,字符串类型只有 keywordtext 两种,旧版本的 string 类型已不再支持。

keyword 类型适合存储简短、结构化的字符串,例如产品 ID、产品名字等。其适合用于聚合、过滤、精确查询。

text 类型的字段适合存储全文本数据,如短信内容、邮件内容等。text 的类型数据将会被分词器进行分词,最终成为一个个词项存储在倒排索引中。

4.2 日期类型

JSON 中是没有日期类型的,所以其形式可以如下表示:

  • 字符串包含日期格式,例如:"2015-01-01" 或者 "2015/01/01 12:10:30"。
  • 时间戳,以毫秒或者秒为单位。

实际上,在底层 ES 都会把日期类型转换为 UTC(如果有指定时区的话),并且作为毫秒形式的时间戳用一个 long 来存储。

4.3 数字类型

数字类型分为 byte、short、integer、long、float、double、half_float、scaled_float、unsigned_long

除了 half_floatscaled_float,其他的就不再介绍了。half_float 是一种 16 位的半精度浮点数,限制为有限值。scaled_float 是缩放类型的的浮点数。

在满足需求的前提下,应当选择尽可能小的数据类型,除了可能会减少存储空间外,也会提高索引数据和检索数据的效率。

4.4 对象与嵌套类型

我们的数据很多时候都需要用对象和数组、嵌套类型等复杂数据类型来表示的,例如一个订单可能有多个子订单,这个时候子订单就需要保存为一个数组。

JSON 中是可以嵌套对象的,保存对象类型可以用 object 类型,但实际上在 ES 中会将原 JSON 文档扁平化存储的。假如下单人字段是一个对象,那么可以表示为:

{
  "createUser": {
    "first":"zhang",
    "last":"san"
  }
}

但实际上,ES 在存储的时候会转化为以下格式存储:

{
  "createUser.first": "zhang",
  "createUser.last": "san"
}

嵌套类型后面再分析。

5. 映射限制

使用以下设置可以限制字段映射(手动或动态创建)的数量,并防止文档导致映射爆炸:

index.mapping.total_fields.limit: 索引中的最大字段数。字段和对象映射以及字段别名都将计入此限制。默认值为1000。

限制是为了防止映射和搜索变得过大。更高的值可能会导致性能下降和内存问题,尤其是在负载高或资源少的集群中。 如果增加此设置,建议还增加indics.query.bool.max_clause_count设置,该设置限制查询中布尔子句的最大数量。

index.mapping.depth.limit: 字段的最大深度。例如所有字段都定义在根上,深度就是1,如果有一个对象映射,深度就是2。

index.mapping.nested_fields.limit:索引中不同嵌套映射的最大数目。只有在特殊情况下,当需要相互独立地查询对象数组时,才应使用嵌套类型。为了防止设计不当的映射,此设置限制了每个索引的唯一嵌套类型的数量。默认值为50。

index.mapping.nested_objects.limit:单个文档在所有嵌套类型中可以包含的嵌套JSON对象的最大数量。当文档包含太多嵌套对象时,此限制有助于防止内存不足错误。默认值为10000。

index.mapping.field_name_length.limit:字段名称的最大长度设置,默认值是Long.MAX_VALUE(无限大)。

index.mapping.dimension_fields.limit:测试阶段,仅仅Es内部使用。

6. 常用参数

Mapping 参数可以用来控制某个字段的特性,例如这个字段是否被索引、用什么分词器、空值是否可以被搜索到等。这里介绍几个开发中比较常用的参数。

6.1 index

当某个字段不需要被索引和查询的时候,可以使用 index 参数进行控制,其接受的值为 true(默认值) 或者 false。

PUT index-0000001
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "index": false # name 字段不进行索引操作
      },
      "address": { "type": "text" }
    }
  }
}

6.2 analyzer

指定使用哪个分词器,当我们进行全文本搜索的时候,会将检索的内容先进行分词,然后再进行匹配。默认情况下,检索内容使用的分词器会与字段指定的分词器一致,但如果设置了 search_analyzer,检索内容使用的分词器将会与 search_analyzer 设定的一致。

PUT index-0000001
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "simple", 
        "search_analyzer": "standard" 
      }
    }
  }
}

6.3 dynamic

可以在文档和对象级别对 Dynamic Mapping 进行控制。

PUT index-0000001
{
  "mappings": {
    "dynamic": "strict", # 1,文档级别,表示文档不能动态添加 top 级别的字段
    "properties": { 
      "user": { # 2,author 对象继承了文档级别的设置。    
        "properties": {
          "address": { 
            "dynamic": "true", # 3,表示 address 对象可以动态添加字段
            "properties":{}
          },
          "country": { "properties":{} }
        }
      }
    }
  }
}

在 1 处,我们控制了整个文档的 dynamic 为 strict,即如果写入不存在的字段,文档数据写入会失败。 其中 user 对象没有设置 dynamic 属性,其将会继承 top 级别的 dynamic 设置。 我们在 "user. address" 对象级别中也设置了 dynamic 属性为 true,其效果是 address 对象可以动态添加字段。

6.4 null_value

如果需要对 null 值实现搜索的时候,需要设置字段的 null_value参数。null_value 参数默认值为 null,其允许用户使用指定值替换空值,以便它可以索引和搜索。

**null_value 只决定数据是如何索引的,不影响 _source 的内容, 并且 null_value 的值的类型需要与字段的类型一致,**例如一个 long 类型的字段,其 null_value 的值不能为字符串。使用 "NULL" 显式值来代替 null。

# 创建索引
PUT null_value_index
{
  "mappings": {
    "properties": {
      "id": { "type": "keyword" },
      "email": {
        "type": "keyword",
        "null_value": "NULL" # 使用 "NULL" 显式值
      }
    }
  }
}

# 插入数据
PUT null_value_index/_doc/1
{
  "id": "1",
  "email": null
}

# 查询空值数据
GET null_value_index/_search
{
  "query": {
    "term": { "email": "NULL" } # 使用显式值来查询空值的文档
  }
}

6.5 copy_to

copy_to 参数允许用户复制多个字段的值到目标字段,这个字段可以像单个字段那样被查询。

# 创建索引
PUT users
{
  "mappings": {
    "properties": {
      "first_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "last_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "full_name": { "type": "text" }
    }
  }
}

# 插入数据
PUT users/_doc/1
{
  "first_name": "zhang",
  "last_name": "san"
}

# 查询
GET users/_search
{
  "query": {
    "match": {
      "full_name": {
        "query": "zhang san",
        "operator": "and"
      }
    }
  }
}

# 结果
{
  "hits" : {
    "hits" : [
      {
        "_source" : {
          "first_name" : "zhang",
          "last_name" : "san"
        }
      }
    ]
  }
}

可以看到,返回的结果中,_source 里是不包含 full_name 字段的。

6.6 doc_values

对数据进行检索的时候,倒排索引可以提高检索的效率,但是在对字段进行聚合、排序、使用脚本访问字段值等操作的时候,需要一种不同的数据结构来支持。

Doc values 是基于列式存储的结构,在索引数据的时候创建。它存储的值与 _source 中的值相同,使用列式存储结构使得 Doc values 在处理聚合、排序操作上更高效。Doc values 支持几乎所有的类型字段,但是 textannotated_text 除外。

Doc values 默认是开启的,保存 Doc values 结构需要很大的空间开销,如果某个字段不需要排序、聚合、使用脚本访问,那么应该禁用此字段的 Doc values 来节省磁盘空间。

PUT index-000001
{
  "mappings": {
    "properties": {
      "status_code": { 
        "type":       "keyword"
      },
      "session_id": { 
        "type":       "keyword",
        "doc_values": false
      }
    }
  }
}

更多内容->官方文档