Elasticsearch 有哪些字段类型?如何正确选择?
Elasticsearch 的字段类型直接决定了索引的存储方式、查询性能和分析能力。选错类型会导致分词异常、聚合失败、存储膨胀,甚至需要重建索引。下面从类型分类、核心类型详解、选型原则三个层面系统梳理。
字段类型总览
Elasticsearch 的字段类型可分为以下几类:
| 类别 | 主要类型 | 典型场景 |
|---|---|---|
| 文本 | text、keyword | 全文搜索 / 精确匹配 |
| 数值 | integer、long、float、double、scaled_float | 范围查询、排序、聚合 |
| 日期 | date | 时间范围过滤 |
| 布尔 | boolean | 状态标记 |
| 复杂结构 | object、nested、flattened | 嵌套文档 |
| 地理 | geo_point、geo_shape | 位置搜索 |
| 语义搜索 | dense_vector、sparse_vector | 向量检索 |
| 排序特征 | rank_feature、rank_features | 影响相关性评分 |
| 自动补全 | search_as_you_type、completion | 搜索建议 |
| 其他 | ip、alias、constant_keyword、wildcard | 特殊用途 |
核心类型详解
text 与 keyword:最常混淆的一对
text 和 keyword 是 Elasticsearch 中最基础也最容易被误用的两种类型。
text 会经过分词器处理,拆分为多个 token 后建立倒排索引,适合 match 查询:
json"title": { "type": "text", "analyzer": "ik_max_word" }
在 text 字段上执行 term 查询是常见错误——分词后的 token 与原始值不一致,term 查询会返回空结果。
keyword 不分词,原样存储,适合 term 查询、排序和聚合:
json"status": { "type": "keyword", "ignore_above": 256 }
多字段模式是生产环境的标准做法,同一个业务字段同时提供 text 和 keyword 两种能力:
json"title": { "type": "text", "analyzer": "ik_max_word", "fields": { "keyword": { "type": "keyword" } } }
这样 title 用于全文搜索,title.keyword 用于精确匹配和聚合。
数值类型:够用就行,不必贪大
integer(4字节)、long(8字节)、float(4字节)、double(8字节)是最常用的数值类型。选型原则很简单:能满足需求的最小类型优先。
scaled_float 适合货币等固定精度场景,它存储时自动乘以 scaling_factor 取整,查询时还原:
json"price": { "type": "scaled_float", "scaling_factor": 100 }
这样 99.99 实际存储为 9999(long),既省空间又避免浮点精度问题。
注意:存储标识符(如数据库主键 ID)应使用 keyword 而非 long。数字类型的 term 查询会做数值比较,而标识符需要的是精确字符串匹配。
date 类型:格式必须显式声明
json"created_at": { "type": "date", "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ||epoch_millis" }
format 支持用 || 分隔多种格式。不指定时 Elasticsearch 会猜测,一旦数据中混入不同格式就会导致解析失败。始终显式声明 format 是最佳实践。
object 与 nested:嵌套数据的两种处理方式
object 类型会将嵌套字段扁平化存储。当数组中包含多个对象时,扁平化会导致跨字段的关联丢失:
json// 文档内容 {"users": [{"name": "张三", "age": 25}, {"name": "李四", "age": 30}]} // object 扁平化后实际存储 {"users.name": ["张三", "李四"], "users.age": [25, 30]}
此时查询 name=张三 AND age=30 会误匹配,因为扁平化后张三和 30 没有关联。
nested 类型为每个数组元素建立独立文档,保证字段间关联:
json"users": { "type": "nested", "properties": { "name": { "type": "keyword" }, "age": { "type": "integer" } } }
查询时必须使用 nested 查询:
json{ "query": { "nested": { "path": "users", "query": { "bool": { "must": [ { "term": { "users.name": "张三" } }, { "term": { "users.age": 25 } } ] } } } } }
nested 的代价是查询更复杂、索引更大。如果数组元素之间不需要跨字段关联查询,用 object 即可。
flattened:动态元数据的轻量选择
当日志或事件中包含大量不确定 key 的元数据字段时,逐个定义 mapping 既繁琐又浪费。flattened 将整个对象作为一个 keyword 存储:
json"metadata": { "type": "flattened" }
支持对内部字段的查询和聚合,但不支持全文搜索。适合标签、注解等结构不固定的场景。
geo_point 与 geo_shape:地理信息处理
json"location": { "type": "geo_point" }
geo_point 支持距离查询、范围过滤和聚合:
json{ "query": { "geo_distance": { "distance": "5km", "location": { "lat": 39.9, "lon": 116.4 } } } }
geo_shape 用于复杂地理区域(多边形、线段),支持空间关系查询(相交、包含等)。
dense_vector:语义搜索的基础
json"embedding": { "type": "dense_vector", "dims": 768, "index": true, "similarity": "cosine" }
dense_vector 存储向量数据,配合 kNN 查询实现语义搜索。index: true(ES 8.0+)启用向量索引以加速近似最近邻检索。
rank_feature:影响相关性但不参与过滤
json"page_rank": { "type": "rank_feature" }, "hot_score": { "type": "rank_features" }
rank_feature 存储单个正数,rank_features 存储 key-value 对。它们只能用于 rank_feature 查询中提升相关性评分,不能用于过滤或聚合。适合引入外部信号(如热度、权威度)影响排序。
其他实用类型
- ip:存储 IPv4/IPv6,支持 CIDR 范围查询
- alias:字段别名,指向另一个字段,查询时自动转发
- constant_keyword:所有文档该字段值相同,优化存储
- wildcard:优化
wildcard和prefix查询的性能 - search_as_you_type:专为即输即搜优化,自动构建 ngram 子字段
- completion:用于
suggesterAPI 的自动补全
runtime fields:查询时动态计算
运行时字段不在索引时生成,而在查询时通过脚本计算,适合临时分析或验证逻辑:
json"runtime": { "price_gte_100": { "type": "boolean", "script": { "source": "emit(doc['price'].value >= 100)" } } }
优点是无需 reindex 即可添加字段;缺点是每次查询都要计算,大量数据下性能损耗明显。生产环境中高频查询的字段应转为索引字段。
选型原则
第一步:确认查询方式
| 查询方式 | 推荐类型 |
|---|---|
| 全文搜索 | text |
| 精确匹配 / 过滤 / 聚合 | keyword |
| 范围查询 | 数值类型 / date |
| 地理距离 | geo_point |
| 语义搜索 | dense_vector |
| 嵌套关联查询 | nested |
第二步:考虑存储与性能
keyword比text索引开销小,高频过滤字段优先用keywordscaled_float在货币场景下比double更精确且更省空间nested查询比object慢,只在需要字段关联时使用flattened减少 mapping 膨胀,但牺牲全文搜索能力
第三步:避免常见错误
- 不要在
text字段上执行term查询,用match或改用keyword - 标识符字段用
keyword,不要用数值类型 - 日期字段始终显式声明
format - 需要字段关联的嵌套数组必须用
nested,不能用object - 运行时字段不要用于高频过滤或大规模聚合
完整 mapping 示例
json{ "mappings": { "properties": { "title": { "type": "text", "analyzer": "ik_max_word", "fields": { "keyword": { "type": "keyword" } } }, "status": { "type": "keyword" }, "price": { "type": "scaled_float", "scaling_factor": 100 }, "is_active": { "type": "boolean" }, "created_at": { "type": "date", "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ||epoch_millis" }, "location": { "type": "geo_point" }, "ip_address": { "type": "ip" }, "users": { "type": "nested", "properties": { "name": { "type": "keyword" }, "age": { "type": "integer" } } }, "metadata": { "type": "flattened" }, "embedding": { "type": "dense_vector", "dims": 768, "index": true, "similarity": "cosine" }, "hot_score": { "type": "rank_feature" } } } }
选对字段类型是 Elasticsearch 索引设计的根基。先明确查询方式,再权衡存储与性能,最后对照常见错误排查——三步走基本覆盖绝大多数场景。如果业务演进导致类型需要变更,通过 reindex + alias 切换的方式在线完成,无需停服。