服务端阅读 05月28日 01:59
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 查询:"title": { "type": "text", "analyzer": "ik_max_word"}在 text 字段上执行 term 查询是常见错误——分词后的 token 与原始值不一致,term 查询会返回空结果。keyword 不分词,原样存储,适合 term 查询、排序和聚合:"status": { "type": "keyword", "ignore_above": 256}多字段模式是生产环境的标准做法,同一个业务字段同时提供 text 和 keyword 两种能力:"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 取整,查询时还原:"price": { "type": "scaled_float", "scaling_factor": 100}这样 99.99 实际存储为 9999(long),既省空间又避免浮点精度问题。注意:存储标识符(如数据库主键 ID)应使用 keyword 而非 long。数字类型的 term 查询会做数值比较,而标识符需要的是精确字符串匹配。date 类型:格式必须显式声明"created_at": { "type": "date", "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ||epoch_millis"}format 支持用 || 分隔多种格式。不指定时 Elasticsearch 会猜测,一旦数据中混入不同格式就会导致解析失败。始终显式声明 format 是最佳实践。object 与 nested:嵌套数据的两种处理方式object 类型会将嵌套字段扁平化存储。当数组中包含多个对象时,扁平化会导致跨字段的关联丢失:// 文档内容{"users": [{"name": "张三", "age": 25}, {"name": "李四", "age": 30}]}// object 扁平化后实际存储{"users.name": ["张三", "李四"], "users.age": [25, 30]}此时查询 name=张三 AND age=30 会误匹配,因为扁平化后张三和 30 没有关联。nested 类型为每个数组元素建立独立文档,保证字段间关联:"users": { "type": "nested", "properties": { "name": { "type": "keyword" }, "age": { "type": "integer" } }}查询时必须使用 nested 查询:{ "query": { "nested": { "path": "users", "query": { "bool": { "must": [ { "term": { "users.name": "张三" } }, { "term": { "users.age": 25 } } ] } } } }}nested 的代价是查询更复杂、索引更大。如果数组元素之间不需要跨字段关联查询,用 object 即可。flattened:动态元数据的轻量选择当日志或事件中包含大量不确定 key 的元数据字段时,逐个定义 mapping 既繁琐又浪费。flattened 将整个对象作为一个 keyword 存储:"metadata": { "type": "flattened"}支持对内部字段的查询和聚合,但不支持全文搜索。适合标签、注解等结构不固定的场景。geopoint 与 geoshape:地理信息处理"location": { "type": "geo_point"}geo_point 支持距离查询、范围过滤和聚合:{ "query": { "geo_distance": { "distance": "5km", "location": { "lat": 39.9, "lon": 116.4 } } }}geo_shape 用于复杂地理区域(多边形、线段),支持空间关系查询(相交、包含等)。dense_vector:语义搜索的基础"embedding": { "type": "dense_vector", "dims": 768, "index": true, "similarity": "cosine"}dense_vector 存储向量数据,配合 kNN 查询实现语义搜索。index: true(ES 8.0+)启用向量索引以加速近似最近邻检索。rank_feature:影响相关性但不参与过滤"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 查询的性能searchasyou_type:专为即输即搜优化,自动构建 ngram 子字段completion:用于 suggester API 的自动补全runtime fields:查询时动态计算运行时字段不在索引时生成,而在查询时通过脚本计算,适合临时分析或验证逻辑:"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 示例{ "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 切换的方式在线完成,无需停服。