5月28日 01:59

Elasticsearch 有哪些字段类型?如何正确选择?

Elasticsearch 的字段类型直接决定了索引的存储方式、查询性能和分析能力。选错类型会导致分词异常、聚合失败、存储膨胀,甚至需要重建索引。下面从类型分类、核心类型详解、选型原则三个层面系统梳理。

字段类型总览

Elasticsearch 的字段类型可分为以下几类:

类别主要类型典型场景
文本textkeyword全文搜索 / 精确匹配
数值integerlongfloatdoublescaled_float范围查询、排序、聚合
日期date时间范围过滤
布尔boolean状态标记
复杂结构objectnestedflattened嵌套文档
地理geo_pointgeo_shape位置搜索
语义搜索dense_vectorsparse_vector向量检索
排序特征rank_featurerank_features影响相关性评分
自动补全search_as_you_typecompletion搜索建议
其他ipaliasconstant_keywordwildcard特殊用途

核心类型详解

text 与 keyword:最常混淆的一对

textkeyword 是 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 }

多字段模式是生产环境的标准做法,同一个业务字段同时提供 textkeyword 两种能力:

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:优化 wildcardprefix 查询的性能
  • search_as_you_type:专为即输即搜优化,自动构建 ngram 子字段
  • completion:用于 suggester API 的自动补全

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

第二步:考虑存储与性能

  • keywordtext 索引开销小,高频过滤字段优先用 keyword
  • scaled_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 切换的方式在线完成,无需停服。

标签:ElasticSearch