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

ElasticSearch映射数据类型的选择

2023-03-293.6k 阅读

1. 引言

在使用 ElasticSearch 进行数据存储和检索时,正确选择映射数据类型至关重要。映射定义了文档中字段的类型,它决定了 ElasticSearch 如何索引和存储数据,进而影响到搜索的准确性和性能。本文将深入探讨 ElasticSearch 中各种映射数据类型,以及如何根据实际需求做出合适的选择。

2. 核心数据类型

2.1 文本类型(text)

  • 特点:text 类型用于存储全文文本,如文章内容、描述等。ElasticSearch 会对 text 类型的字段进行分词处理,将文本拆分成一个个单词,然后构建倒排索引。这使得 text 类型字段非常适合用于全文搜索。
  • 示例:假设我们有一个博客文章索引,文章内容字段可定义为 text 类型。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "content": {
                "type": "text"
            }
        }
    }
}

在这个例子中,content 字段用于存储文章正文,ElasticSearch 会对其进行分词,比如文章中有 “ElasticSearch 是一个强大的搜索引擎”,可能会被分词为 “ElasticSearch”、“是”、“一个”、“强大”、“的”、“搜索引擎” 等,然后基于这些分词构建倒排索引。当用户搜索 “强大的搜索引擎” 时,ElasticSearch 可以通过倒排索引快速找到包含这些分词的文档。

2.2 关键词类型(keyword)

  • 特点:keyword 类型用于存储精确值,如 ID、类别标签、URL 等。keyword 类型不会进行分词,而是直接将整个值作为一个词条进行索引。这使得 keyword 类型适合用于精确匹配、排序和聚合操作。
  • 示例:继续以博客文章索引为例,文章的分类标签可定义为 keyword 类型。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "category": {
                "type": "keyword"
            }
        }
    }
}

假设文章分类为 “技术”,那么这个 “技术” 就会作为一个完整的词条被索引。当我们要查找分类为 “技术” 的文章时,ElasticSearch 可以直接通过精确匹配找到对应的文档。如果将分类标签定义为 text 类型,分词后可能会导致精确匹配出现问题。

2.3 数值类型(Numeric Types)

ElasticSearch 支持多种数值类型,如 byteshortintegerlongfloatdouble 等。

  • 特点:数值类型用于存储数字。不同的数值类型有不同的取值范围和精度。例如,byte 类型取值范围为 -128 到 127,适合存储较小的整数;double 类型可以存储高精度的浮点数。数值类型支持数学运算、范围查询和聚合操作。
  • 示例:如果我们在博客文章索引中要记录文章的阅读量,可以使用 integer 类型。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "views": {
                "type": "integer"
            }
        }
    }
}

这样我们就可以对阅读量进行诸如 “阅读量大于 1000” 的范围查询,或者计算平均阅读量等聚合操作。

2.4 日期类型(date)

  • 特点:date 类型用于存储日期和时间。ElasticSearch 支持多种日期格式,如 yyyy - MM - ddyyyy - MM - dd HH:mm:ss 等。日期类型支持日期范围查询、排序和聚合操作。
  • 示例:在博客文章索引中,记录文章发布日期。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "published_date": {
                "type": "date"
            }
        }
    }
}

可以通过类似 published_date > "2023 - 01 - 01" 的查询来筛选出 2023 年 1 月 1 日之后发布的文章。

2.5 布尔类型(boolean)

  • 特点:boolean 类型用于存储布尔值 truefalse。常用于表示一些状态,如文章是否置顶、是否公开等。
  • 示例:在博客文章索引中,判断文章是否公开。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "is_public": {
                "type": "boolean"
            }
        }
    }
}

通过 is_public:true 的查询可以找到所有公开的文章。

2.6 二进制类型(binary)

  • 特点:binary 类型用于存储二进制数据,如图片、音频等。不过,ElasticSearch 本身并不擅长处理二进制数据的内容检索,通常只用于存储二进制数据的元数据信息。
  • 示例:如果我们要在索引中记录图片的二进制数据(虽然不太推荐直接存储大图片),可以定义如下:
PUT /image_index
{
    "mappings": {
        "properties": {
            "image_data": {
                "type": "binary"
            }
        }
    }
}

但实际应用中,更多是将图片存储在对象存储中,在 ElasticSearch 中只记录图片的路径等元数据。

3. 复杂数据类型

3.1 对象类型(object)

  • 特点:对象类型用于表示一个 JSON 对象,它允许在一个字段中嵌套多个子字段。这在描述具有层次结构的数据时非常有用。
  • 示例:假设博客文章有一个作者信息字段,作者信息包含姓名、邮箱和简介。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "author": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "text"
                    },
                    "email": {
                        "type": "keyword"
                    },
                    "bio": {
                        "type": "text"
                    }
                }
            }
        }
    }
}

这样我们就可以通过 author.name 来查询特定作者姓名的文章。

3.2 嵌套类型(nested)

  • 特点:嵌套类型本质上也是用于处理对象,但与普通对象类型不同的是,嵌套类型可以对每个嵌套的对象进行独立的索引和查询。这在处理数组中的对象时非常重要,因为普通对象类型在数组情况下会出现数据关联问题。
  • 示例:假设博客文章有多个标签,每个标签有名称和描述。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "tags": {
                "type": "nested",
                "properties": {
                    "name": {
                        "type": "keyword"
                    },
                    "description": {
                        "type": "text"
                    }
                }
            }
        }
    }
}

如果是普通对象类型,当有多个标签时,搜索某个标签描述可能会出现误匹配。而嵌套类型可以确保每个标签对象是独立索引和查询的。

3.3 地理类型(geo - point、geo - shape)

  • 特点geo - point 类型用于存储地理坐标(经度和纬度),geo - shape 类型用于存储更复杂的地理形状,如多边形。这两种类型支持地理空间查询,如查找某个点附近的文档、判断某个点是否在某个地理形状内等。
  • 示例:假设博客文章带有发布地点的地理坐标。
PUT /blog_index
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo - point"
            }
        }
    }
}

可以通过 geo_distance 查询来查找距离某个坐标点一定距离内的文章。

4. 选择合适的数据类型

4.1 根据查询需求选择

  • 全文搜索:如果需要进行全文搜索,如搜索文章内容、描述等,应选择 text 类型。例如,在新闻搜索应用中,用户可能会输入一段描述性的文字来搜索相关新闻,此时新闻内容字段就适合用 text 类型。
  • 精确匹配:对于需要精确匹配的字段,如 ID、类别等,keyword 类型是最佳选择。比如电商应用中,商品的 SKU 号码,必须精确匹配才能找到对应的商品。
  • 范围查询:数值类型和日期类型适合范围查询。例如,在酒店预订应用中,用户可能会根据价格范围(数值类型)或入住日期范围(日期类型)来筛选酒店。

4.2 根据存储和性能考虑

  • 存储空间:选择合适的数值类型可以节省存储空间。如果数值范围较小,使用 byteshort 而不是 long。例如,一个表示文章评论数的字段,通常不会超过 integer 类型的范围,就无需使用 long 类型。
  • 索引性能text 类型由于需要分词,索引构建的性能相对较低,而 keyword 类型直接索引整个值,性能较高。在设计索引时,如果字段不需要全文搜索,尽量使用 keyword 类型以提高索引性能。

4.3 数据一致性和准确性

  • 类型匹配:确保数据类型与实际存储的数据一致。如果将数值类型字段错误定义为 text 类型,可能会导致无法进行数值相关的查询和聚合操作。例如,将产品价格字段定义为 text,就无法计算平均价格。
  • 数据转换:ElasticSearch 会尝试自动转换数据类型,但可能会出现转换错误。因此,在插入数据前,最好进行数据类型的验证和转换,以保证数据的准确性。

5. 动态映射与显式映射

5.1 动态映射

  • 原理:当向 ElasticSearch 索引中插入一个新文档时,如果文档中的字段在映射中不存在,ElasticSearch 会根据文档中字段的值自动推断数据类型,并动态添加到映射中。
  • 示例
POST /blog_index/_doc
{
    "new_field": "example value"
}

ElasticSearch 会根据 "example value" 推断 new_fieldtext 类型,并自动更新映射。

  • 优缺点:优点是使用方便,无需预先定义所有字段的映射。缺点是可能会导致映射不符合预期,比如推断的数据类型错误,而且在大规模数据插入时,动态更新映射可能会影响性能。

5.2 显式映射

  • 原理:显式映射是指在创建索引时,手动定义所有字段的映射。这样可以精确控制每个字段的数据类型、索引方式等。
  • 示例
PUT /blog_index
{
    "mappings": {
        "properties": {
            "title": {
                "type": "text"
            },
            "views": {
                "type": "integer"
            }
        }
    }
}
  • 优缺点:优点是可以保证映射的准确性和一致性,性能也更好,适合生产环境。缺点是需要预先规划好索引结构,对于需求变化频繁的场景,维护成本较高。

6. 数据类型的更新与迁移

6.1 数据类型更新的限制

  • 不能直接更新:在 ElasticSearch 中,一旦字段被索引,通常不能直接更改其数据类型。例如,不能将 text 类型直接改为 keyword 类型。这是因为不同数据类型的索引结构不同,直接更改会导致索引数据不一致。
  • 部分更改可能:某些情况下,可以在不改变数据类型本质的前提下进行一些参数调整。比如,可以在 text 类型上更改分词器等设置。

6.2 数据迁移方法

  • 重新索引:如果需要更改字段的数据类型,最常用的方法是重新索引。首先创建一个新的索引,并定义正确的映射。然后通过 reindex API 将旧索引的数据迁移到新索引中。
POST _reindex
{
    "source": {
        "index": "old_blog_index"
    },
    "dest": {
        "index": "new_blog_index"
    }
}

在迁移过程中,可以对数据进行转换,以适应新的映射。例如,将旧索引中作为 text 类型存储的数值转换为 integer 类型存储在新索引中。

  • 使用别名:为了在迁移过程中不影响业务,可使用索引别名。先将别名指向旧索引,在重新索引完成后,将别名切换到新索引,这样应用程序无需更改索引名称即可无缝切换到新索引。

7. 实战案例分析

7.1 电商产品搜索案例

假设我们要构建一个电商产品搜索系统。产品数据包含产品名称、描述、价格、品牌、类别、库存数量、上架日期等信息。

  • 产品名称和描述:产品名称和描述适合用 text 类型,以支持用户通过各种描述性词语搜索产品。例如,用户搜索 “高性能笔记本电脑”,产品描述中的相关词语会被分词并匹配。
"properties": {
    "product_name": {
        "type": "text"
    },
    "description": {
        "type": "text"
    }
}
  • 价格:价格是数值类型,由于电商价格一般不会太大,使用 floatdouble 类型即可。这里使用 float 类型。
"properties": {
    "price": {
        "type": "float"
    }
}
  • 品牌和类别:品牌和类别适合用 keyword 类型,用于精确匹配和聚合。比如,用户筛选某个品牌或类别的产品。
"properties": {
    "brand": {
        "type": "keyword"
    },
    "category": {
        "type": "keyword"
    }
}
  • 库存数量:库存数量是整数,使用 integer 类型。
"properties": {
    "stock_quantity": {
        "type": "integer"
    }
}
  • 上架日期:上架日期使用 date 类型,方便按日期范围筛选产品,如查找近一个月上架的产品。
"properties": {
    "listed_date": {
        "type": "date"
    }
}

7.2 地理位置相关案例

假设我们有一个旅游景点推荐系统,景点数据包含景点名称、介绍、地理位置、门票价格、开放时间等信息。

  • 地理位置:地理位置使用 geo - point 类型,这样可以根据用户当前位置推荐附近的景点。
"properties": {
    "location": {
        "type": "geo - point"
    }
}
  • 景点名称和介绍:与电商案例类似,使用 text 类型。
"properties": {
    "attraction_name": {
        "type": "text"
    },
    "description": {
        "type": "text"
    }
}
  • 门票价格:数值类型,根据实际情况选择合适的数值类型,这里假设使用 float 类型。
"properties": {
    "ticket_price": {
        "type": "float"
    }
}
  • 开放时间:可以使用 date 类型,或者更灵活地使用字符串类型并自定义解析规则,以处理复杂的开放时间逻辑,如每周一闭馆等。这里先使用 date 类型简单处理。
"properties": {
    "opening_time": {
        "type": "date"
    }
}

8. 总结与最佳实践

  • 预规划映射:在项目开始阶段,充分分析数据结构和查询需求,精心规划索引的映射。尽量使用显式映射,以保证数据的准确性和性能。
  • 理解数据类型特性:深入理解每种数据类型的特点和适用场景,根据实际数据选择最合适的数据类型。避免因数据类型选择不当导致的查询不准确或性能问题。
  • 关注性能优化:在选择数据类型时,考虑存储空间和索引性能。例如,合理选择数值类型以节省空间,避免不必要的 text 类型使用以提高索引速度。
  • 定期维护和更新:随着业务的发展,数据结构和查询需求可能会发生变化。定期检查和更新映射,确保索引始终保持最佳性能和数据一致性。通过重新索引等方法,及时处理数据类型的变更需求。

通过以上对 ElasticSearch 映射数据类型的详细探讨和实践案例分析,希望能帮助读者在实际项目中准确、高效地选择和使用数据类型,构建出性能卓越的搜索应用。