MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch映射的数据类型分析

2021-11-016.0k 阅读

ElasticSearch 映射基础

在 ElasticSearch 中,映射(Mapping)定义了文档(Document)及其包含的字段(Field)如何被存储和索引。映射就像是数据库中的表结构定义,但 ElasticSearch 的映射更加灵活,支持动态映射(Dynamic Mapping),即在文档被索引时,如果某个字段之前未定义过,ElasticSearch 会自动推断其数据类型并添加到映射中。

每个索引(Index)都有一个映射,它可以定义文档的字段名、数据类型、是否索引、是否存储等属性。映射主要包含以下几个重要部分:

  • 元字段(Meta - fields):用于描述文档的元数据,如 _index(文档所属的索引名)、_type(文档类型,在 ElasticSearch 7.0 及以上版本逐渐弃用)、_id(文档的唯一标识)等。
  • 字段映射(Field Mappings):定义文档中每个字段的数据类型和相关属性。

核心数据类型

  1. 字符串类型(Text 和 Keyword)

    • Text:用于全文搜索的字符串类型。当一个字段被定义为 text 类型时,ElasticSearch 会对其进行分词(Tokenization)处理,将文本拆分成一个个单词(Tokens),然后对这些单词进行索引。这使得在进行全文搜索时可以高效地匹配文本中的各个单词。
      PUT my_index
      {
        "mappings": {
          "properties": {
            "content": {
              "type": "text"
            }
          }
        }
      }
      
      在上述示例中,content 字段被定义为 text 类型,适合存储文章内容、评论等需要进行全文搜索的文本。
    • Keyword:适用于精确匹配和聚合操作的字符串类型。keyword 类型的字段不会进行分词,而是将整个字符串作为一个整体进行索引。常用于存储标识符、标签、状态等字段。
      PUT my_index
      {
        "mappings": {
          "properties": {
            "product_id": {
              "type": "keyword"
            }
          }
        }
      }
      
      这里的 product_id 字段适合用于精确查找特定产品,例如通过产品 ID 进行查询。
  2. 数值类型

    • Byte:8 位有符号整数,范围是 -128 到 127。
    • Short:16 位有符号整数,范围是 -32768 到 32767。
    • Integer:32 位有符号整数,范围是 -2147483648 到 2147483647。
    • Long:64 位有符号整数,范围是 -9223372036854775808 到 9223372036854775807。
    • Float:32 位单精度浮点数。
    • Double:64 位双精度浮点数。
    • Half - Float:16 位半精度浮点数,适用于对精度要求不高但对存储空间敏感的场景。
    • Scaled - Float:用于存储浮点数,通过指定一个缩放因子(Scaling Factor)来减少存储空间。例如:
      PUT my_index
      {
        "mappings": {
          "properties": {
            "price": {
              "type": "scaled_float",
              "scaling_factor": 100
            }
          }
        }
      }
      
      上述 price 字段使用 scaled_float 类型,假设实际价格为 123.45,存储时会乘以缩放因子 100 变为 12345 存储为整数,查询时再进行相应的转换,这样可以在一定程度上节省存储空间。
  3. 日期类型(Date) ElasticSearch 支持多种日期格式,包括 strict_date_optional_time(例如:2023 - 01 - 01T12:00:00Z)、epoch_millis(毫秒级时间戳)等。日期类型可以用于时间序列数据的存储和查询,比如日志记录的时间、订单创建时间等。

    PUT my_index
    {
      "mappings": {
        "properties": {
          "create_time": {
            "type": "date"
          }
        }
      }
    }
    

    可以通过 format 参数指定日期格式,例如:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "create_time": {
            "type": "date",
            "format": "yyyy - MM - dd HH:mm:ss"
          }
        }
      }
    }
    
  4. 布尔类型(Boolean) 用于表示 true 或 false 的值,常用于存储逻辑判断结果,如 is_published(文章是否发布)、is_active(用户是否活跃)等字段。

    PUT my_index
    {
      "mappings": {
        "properties": {
          "is_published": {
            "type": "boolean"
          }
        }
      }
    }
    
  5. 二进制类型(Binary) 用于存储二进制数据,如图片、音频、视频等。但需要注意的是,ElasticSearch 并不擅长处理大规模的二进制数据存储,通常只是在必要时存储一些小型的二进制文件片段。二进制数据以 Base64 编码的字符串形式存储。

    PUT my_index
    {
      "mappings": {
        "properties": {
          "small_image": {
            "type": "binary"
          }
        }
      }
    }
    

复杂数据类型

  1. 数组类型(Array) ElasticSearch 中没有专门的数组数据类型定义,任何字段都可以包含多个值,即形成数组。例如,一个用户可能有多个兴趣爱好,我们可以这样定义映射:

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

    然后在文档中可以这样插入数据:

    POST my_index/_doc
    {
      "hobbies": ["reading", "swimming", "traveling"]
    }
    

    当查询时,ElasticSearch 会将数组中的每个元素当作独立的值进行处理,支持对数组中任何一个值的匹配查询。

  2. 对象类型(Object) 用于表示 JSON 对象,文档中的字段可以包含嵌套的对象结构。例如,一个订单文档可能包含客户信息,客户信息本身又是一个对象:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "order_info": {
            "type": "object",
            "properties": {
              "customer_name": {
                "type": "text"
              },
              "customer_email": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
    

    插入文档如下:

    POST my_index/_doc
    {
      "order_info": {
        "customer_name": "John Doe",
        "customer_email": "john@example.com"
      }
    }
    
  3. 嵌套类型(Nested) 嵌套类型是对象类型的一种特殊情况,用于处理对象数组。当一个对象数组中的每个对象需要独立进行查询和聚合时,就需要使用嵌套类型。例如,一个商品文档可能包含多个评论,每个评论都是一个对象,并且需要对每个评论进行独立的查询:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "reviews": {
            "type": "nested",
            "properties": {
              "author": {
                "type": "text"
              },
              "content": {
                "type": "text"
              },
              "rating": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
    

    插入文档如下:

    POST my_index/_doc
    {
      "reviews": [
        {
          "author": "Alice",
          "content": "Great product!",
          "rating": 4
        },
        {
          "author": "Bob",
          "content": "Not bad.",
          "rating": 3
        }
      ]
    }
    

    这样在查询时,可以精确地针对某个评论进行过滤和聚合操作,例如查询评分大于 3 的评论。

地理空间数据类型

  1. 地理点类型(Geo - Point) 用于存储地理位置的经纬度信息,常用于位置相关的应用,如附近的商店查找、轨迹跟踪等。可以通过多种方式定义地理点字段:

    • 数组形式[longitude, latitude]
    • 对象形式{"lat": latitude, "lon": longitude}
    PUT my_index
    {
      "mappings": {
        "properties": {
          "location": {
            "type": "geo_point"
          }
        }
      }
    }
    

    插入文档如下:

    POST my_index/_doc
    {
      "location": [116.3975, 39.9085]
    }
    

    ElasticSearch 提供了丰富的地理空间查询功能,如查询某个范围内的地理点等。

  2. 地理形状类型(Geo - Shape) 用于存储复杂的地理形状,如多边形、线等。例如,定义一个区域的边界多边形:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "area_boundary": {
            "type": "geo_shape"
          }
        }
      }
    }
    

    插入文档时,需要按照 GeoJSON 格式定义地理形状:

    POST my_index/_doc
    {
      "area_boundary": {
        "type": "Polygon",
        "coordinates": [
          [
            [116.388, 39.907],
            [116.392, 39.905],
            [116.395, 39.908],
            [116.388, 39.907]
          ]
        ]
      }
    }
    

    可以通过地理形状查询来判断某个地理点是否在该区域内等操作。

特殊数据类型

  1. IP 类型(IP) 用于存储 IP 地址,无论是 IPv4 还是 IPv6 地址都可以存储。例如,记录用户访问网站的 IP 地址:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "visitor_ip": {
            "type": "ip"
          }
        }
      }
    }
    

    插入文档如下:

    POST my_index/_doc
    {
      "visitor_ip": "192.168.1.1"
    }
    

    ElasticSearch 支持对 IP 地址进行范围查询等操作,比如查询某个 IP 段内的访问记录。

  2. Completion 类型 主要用于实现自动完成(Autocomplete)功能。它基于一种特殊的数据结构,能够高效地提供前缀匹配建议。例如,实现一个搜索框的自动完成功能,当用户输入部分关键字时,提供相关的建议:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "suggestion": {
            "type": "completion"
          }
        }
      }
    }
    

    插入文档如下:

    POST my_index/_doc
    {
      "suggestion": "apple"
    }
    

    在查询时,可以通过 suggest API 来获取自动完成的建议。

  3. Token Count 类型 用于统计文本字段中的词元(Tokens)数量。例如,统计文章的字数(假设以词元数量近似表示):

    PUT my_index
    {
      "mappings": {
        "properties": {
          "article_content": {
            "type": "text"
          },
          "word_count": {
            "type": "token_count",
            "counter_field": "article_content"
          }
        }
      }
    }
    

    这里 word_count 字段通过 counter_field 关联到 article_content 字段,在文档索引时会自动统计 article_content 字段的词元数量并存储到 word_count 字段中。

映射属性详解

  1. 索引属性(index)

    • true:表示该字段会被索引,默认值为 true。只有被索引的字段才能被搜索。例如:
      PUT my_index
      {
        "mappings": {
          "properties": {
            "title": {
              "type": "text",
              "index": true
            }
          }
        }
      }
      
    • false:表示该字段不会被索引,不能被搜索,但仍然会存储在文档中。常用于存储一些不需要搜索但需要在查询结果中展示的信息,如图片的原始文件名等。
      PUT my_index
      {
        "mappings": {
          "properties": {
            "image_original_name": {
              "type": "keyword",
              "index": false
            }
          }
        }
      }
      
  2. 存储属性(store)

    • true:表示该字段的值会被单独存储,默认值为 false。当 storetrue 时,可以在查询结果中直接获取该字段的值,而不需要从_source 字段中提取。例如:
      PUT my_index
      {
        "mappings": {
          "properties": {
            "summary": {
              "type": "text",
              "store": true
            }
          }
        }
      }
      
    • false:表示该字段的值不会被单独存储,需要从 _source 字段中获取。通常,对于大多数字段,不需要设置 storetrue,因为 _source 字段已经包含了文档的原始内容。
  3. 分析器属性(analyzer) 分析器(Analyzer)用于在索引和查询阶段对文本进行分词等处理。对于 text 类型的字段,可以指定分析器。例如,使用 standard 分析器(默认分析器):

    PUT my_index
    {
      "mappings": {
        "properties": {
          "content": {
            "type": "text",
            "analyzer": "standard"
          }
        }
      }
    }
    

    也可以自定义分析器,通过组合字符过滤器(Character Filter)、分词器(Tokenizer)和词元过滤器(Token Filter)来满足特定的文本处理需求。例如,创建一个自定义分析器,先将文本转换为小写,然后进行分词:

    PUT my_index
    {
      "settings": {
        "analysis": {
          "analyzer": {
            "my_analyzer": {
              "tokenizer": "standard",
              "filter": "lowercase"
            }
          }
        }
      },
      "mappings": {
        "properties": {
          "content": {
            "type": "text",
            "analyzer": "my_analyzer"
          }
        }
      }
    }
    
  4. 多字段属性(multi - fields) 有时一个字段需要以不同的方式进行索引和查询。例如,一个 title 字段,既需要进行全文搜索,又需要进行精确匹配。这时可以使用多字段特性:

    PUT my_index
    {
      "mappings": {
        "properties": {
          "title": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword"
              }
            }
          }
        }
      }
    }
    

    这样,title 字段可以用于全文搜索,而 title.keyword 字段可以用于精确匹配,如查找特定标题的文档。

动态映射规则

  1. 基本类型推断

    • 当 ElasticSearch 遇到一个新的字段时,会根据字段值来推断其数据类型。例如,如果字段值是一个整数,会推断为 long 类型;如果是浮点数,会推断为 double 类型。如果是字符串,会根据字符串的内容和格式来判断,如果字符串看起来像日期,会推断为 date 类型;如果字符串包含多个单词且需要全文搜索,会推断为 text 类型;如果字符串是单个值且用于精确匹配,会推断为 keyword 类型。
    • 例如,插入如下文档:
      POST my_index/_doc
      {
        "age": 30,
        "name": "John Doe",
        "is_active": true,
        "create_time": "2023 - 01 - 01"
      }
      
      ElasticSearch 会自动推断 agelong 类型,nametext 类型(同时自动创建 name.keyword 字段用于精确匹配),is_activeboolean 类型,create_timedate 类型。
  2. 动态模板(Dynamic Templates) 动态模板允许用户自定义动态映射规则。通过动态模板,可以根据字段名的模式、数据类型等条件来定义映射。例如,定义一个动态模板,让所有以 _id 结尾的字段都被映射为 keyword 类型:

    PUT my_index
    {
      "mappings": {
        "dynamic_templates": [
          {
            "id_template": {
              "match": "*_id",
              "mapping": {
                "type": "keyword"
              }
            }
          }
        ]
      }
    }
    

    这样,当插入包含 user_idproduct_id 等字段的文档时,这些字段会被自动映射为 keyword 类型。

映射更新注意事项

  1. 字段添加 可以随时向现有映射中添加新的字段,只要索引处于打开状态。例如,向已有的 my_index 索引添加一个新的 description 字段:

    PUT my_index/_mapping
    {
      "properties": {
        "description": {
          "type": "text"
        }
      }
    }
    
  2. 字段修改 修改现有字段的数据类型通常是不允许的,因为这可能会导致已索引的数据无法正确查询。如果确实需要修改字段类型,一种常见的方法是创建一个新的索引,将旧索引的数据迁移到新索引,并使用新的映射。例如:

    • 首先创建新索引并定义新映射:
      PUT new_my_index
      {
        "mappings": {
          "properties": {
            "old_field": {
              "type": "new_type"
            }
          }
        }
      }
      
    • 然后使用 reindex API 将旧索引的数据迁移到新索引:
      POST _reindex
      {
        "source": {
          "index": "my_index"
        },
        "dest": {
          "index": "new_my_index"
        }
      }
      
  3. 映射删除 无法直接删除单个字段的映射,但可以删除整个索引,然后重新创建索引并定义新的映射。

    DELETE my_index
    

    然后重新创建:

    PUT my_index
    {
      "mappings": {
        "properties": {
          // 新的映射定义
        }
      }
    }
    

通过深入理解 ElasticSearch 映射的数据类型及其相关属性和操作,开发人员可以根据具体的业务需求,灵活、高效地设计索引结构,从而充分发挥 ElasticSearch 的强大搜索和数据分析能力。无论是简单的文本搜索,还是复杂的地理空间查询、聚合分析等场景,合理的映射设计都是实现高性能应用的关键基础。在实际应用中,还需要结合数据量、查询频率、存储成本等多方面因素进行综合考量,不断优化映射和索引设置,以满足业务的发展和变化。同时,随着 ElasticSearch 版本的不断更新,一些特性和功能可能会有所变化,需要持续关注官方文档以获取最新信息。