3. 字段类型与映射设计
映射(Mapping)是 Elasticsearch 中定义索引结构的核心机制,它决定了每个字段如何被解析、索引和查询。字段类型选择不当,轻则导致查询结果不准确,重则引发聚合失败、排序异常甚至索引膨胀。本节围绕常用字段类型展开,重点说明它们各自适用的场景、底层行为差异以及选型时容易踩的坑。
text 与 keyword:最容易被混淆的一对
字符串字段在 Elasticsearch 中默认会同时映射为 text 和 keyword 两个子字段,但这并不意味着可以随意使用。两者的根本区别在于是否经过分词器处理。
text 类型会经过分词器拆分成词项(term),适合全文检索。例如商品标题“华为 Mate 60 Pro 智能手机”,标准分词器会拆成“华为”“mate”“60”“pro”“智能”“手机”等词项,用户搜索“华为手机”时可以通过 match 查询命中。但 text 字段不能直接用于排序和聚合,因为它的倒排索引中存储的是分词后的词项,而非原始值。
keyword 类型不做分词,整个字段值作为一个词项存储,适合精确匹配、排序和聚合。例如订单状态“PAID”“SHIPPED”“CANCELLED”,用 keyword 可以精确过滤,也能在聚合中按状态分组统计。如果误用 text,状态值会被拆成单个字母或词,聚合结果将完全失去意义。
实际建模中,常见的做法是同时保留两种能力:
PUT /products
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"raw": { "type": "keyword" }
}
},
"status": { "type": "keyword" }
}
}
}
这样 title 用于全文搜索,title.raw 用于排序或聚合。需要注意的是,keyword 字段有长度限制(默认 ignore_above 为 256),超长文本不适合作为 keyword,否则会被忽略而无法检索。
数值类型:精度与空间的权衡
Elasticsearch 提供了 long、integer、short、byte、double、float、half_float、scaled_float 等多种数值类型。选型时主要考虑取值范围和精度需求。
对于商品价格、金额等需要精确计算的场景,推荐使用 scaled_float,通过 scaling_factor 将浮点数转为整数存储。例如价格保留两位小数,设置 scaling_factor: 100,则 19.99 元存储为 1999,既避免了浮点误差,又比 double 节省空间。对于计数器、库存量等整数场景,integer 通常足够;若可能超过 21 亿,则需使用 long。
数值类型一旦映射后不可修改,若后期需要调整类型,只能通过重建索引(reindex)完成。因此建模初期应充分评估业务增长空间。
日期类型:格式与精度
date 类型支持多种格式,默认接受 ISO 8601 格式(如 2024-06-01T12:00:00Z)以及时间戳。可以通过 format 参数指定自定义格式:
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||epoch_millis"
}
日期字段在聚合中非常常用,例如按天统计订单量、按小时分析日志分布。Elasticsearch 内部将日期统一转换为 UTC 毫秒时间戳存储,查询时再根据时区转换。如果业务涉及多时区,建议在应用层统一转换为 UTC 后再写入,避免时区混乱。
布尔、二进制与范围类型
boolean 类型接受 true/false,也接受字符串 "true"/"false"。它适合标记类字段,如“是否删除”“是否推荐”。布尔字段在过滤查询中效率很高,但不宜用于需要多状态表达的场景。
binary 类型用于存储 Base64 编码的二进制数据,默认不索引,仅存储。它适合存放图片缩略图、加密签名等不需要检索的内容。由于不参与搜索,使用时需明确其用途仅为存储。
range 类型包括 integer_range、float_range、date_range 等,用于表示区间。例如会议室预订系统中,一个预订记录可以表示为 { "gte": "2024-06-01T09:00:00", "lte": "2024-06-01T10:00:00" },查询某时刻是否被占用时使用 range 查询即可。范围类型在时间窗口、价格区间等场景中非常高效。
嵌套与对象类型:结构关系的表达
当文档包含数组对象时,object 类型会将数组扁平化处理,导致跨对象的条件匹配出现错误。例如订单中包含多个商品,每个商品有名称和数量,若使用 object 类型,查询“名称为 A 且数量为 2”时,可能匹配到名称 A 数量 3 和名称 B 数量 2 的组合,因为扁平化后字段之间失去了关联。
nested 类型通过为每个对象单独建立隐藏文档来保持独立性,查询时使用 nested 查询并指定 path,可以精确匹配同一对象内的多个条件。代价是 nested 字段的查询和聚合性能低于普通字段,且更新时需要重建整个 nested 文档。因此,只有在确实需要保持对象内部字段关联时才使用 nested,否则优先考虑 object 或扁平化设计。
选型影响与建模建议
字段类型的选择会从三个层面影响系统:查询正确性、存储成本和性能。将状态字段误设为 text 会导致聚合结果错误;将价格设为 double 可能产生精度偏差;将本可用 object 的场景设为 nested 会带来不必要的性能开销。
建模时建议遵循以下原则:先明确字段的查询方式(全文检索、精确过滤、排序、聚合),再反推类型;对字符串字段默认使用 text + keyword 子字段的组合;数值字段根据精度和范围选择最小够用的类型;日期字段统一使用 UTC;只有在需要保持数组对象内部字段关联时才使用 nested。映射设计一旦确定,后期修改成本较高,前期多花时间评估远比事后重建索引划算。