ES_数据存储知识
一、 _source
字段:数据的“真相之源”
1. 是什么?
_source
是一个独立的、特殊的元字段。它存储了你在索引文档时提交的原始JSONbody的完整内容。
2. 工作原理与用途
- 写入:当你索引一个文档
{"title": "My Book", "price": 29.99}
,这个完整的JSON对象就会被原封不动地存储到_source
字段中。 - 读取:
- 搜索返回:当你执行搜索请求,默认返回的
hits.hits._source
里的数据,就是直接从_source
字段提取的,并非从倒排索引中重建。这是为什么ES搜索返回结果如此之快的原因之一。 - 重建索引/更新:
update
API和reindex
API都严重依赖_source
字段。更新操作本质上是“获取文档 -> 修改_source
-> 重新索引”;重建索引则是直接从源索引读取_source
来写入新索引。 - 高亮(Highlighting):高亮功能通常需要访问原始文本内容来确定字符位置,
_source
是其数据来源之一。
- 搜索返回:当你执行搜索请求,默认返回的
3. 禁用 _source
?
你可以通过映射禁用 _source
,但强烈不建议这样做。
PUT my_index
{"mappings": {"_source": {"enabled": false},"properties": {...}}
}
禁用后果:
- 无法使用
update
API。 - 无法直接通过
_reindex
API 迁移数据。 - 无法使用高亮(除非显式地将高亮所需字段设置为
"store": true
)。 - 无法在查询结果中看到原始文档。
- 调试困难。
最佳实践:永远不要禁用 _source
。如果担心磁盘占用,可以通过索引压缩、使用更高效的硬件等方式解决,而不是牺牲核心功能。
二、 doc_values
:列式存储的基石
1. 是什么?
doc_values
是默认开启的、在磁盘上的、基于列式存储的数据结构。它的存在是为了高效地完成排序(Sorting)、聚合(Aggregation)和脚本访问(Scripting)。
2. 为什么需要它?
倒排索引是为“查找包含某个词项的文档”而优化的(即搜索)。但要回答“对某个字段进行聚合或排序”就非常困难且低效,因为这需要遍历所有文档并获取字段值。
doc_values
解决了这个问题。它为所有支持它的字段类型(如 keyword
, numeric
, date
, geo_point
, ip
等,但不包括 text
)在索引时额外创建了一个列式存储结构。
Document | price (Field Value) |
---|---|
Doc_1 | 100 |
Doc_2 | 200 |
Doc_3 | 150 |
3. 工作原理与用途
- 列式存储:数据按列(字段)存储,而不是按行(文档)。这使得ES可以高效地:
- 排序:快速遍历一列中的所有值。
- 聚合:计算一列中值的总和、平均值、百分位数等。
- 脚本访问:在脚本中引用
doc['price'].value
时,数据来源于doc_values
。
- 磁盘存储:
doc_values
存储在磁盘上,但OS会将其缓存到文件系统缓存中,以提供高速访问。
4. 禁用 doc_values
?
你可以按字段禁用 doc_values
,但仅在你100%确定该字段永远不会用于排序、聚合或脚本时才可以这样做。这可以节省磁盘空间。
"my_field": {"type": "keyword","doc_values": false // 谨慎禁用!
}
三、 fielddata
:内存中的倒排索引
1. 是什么?
fielddata
是 doc_values
的内存版互补方案,主要用于 text
字段。
2. 为什么需要它?
text
字段默认会被分词(analyzed
),因此它们默认没有 doc_values
。如果你突然需要对一个 text
字段进行聚合或排序(例如,对 product_name
这个 text
字段做 terms
聚合),ES需要能够通过文档ID快速找到该文档的字段值。
fielddata
就是将所有分词后的词项与包含它们的文档的对应关系,从磁盘上的倒排索引加载到JVM堆内存中形成的数据结构。这是一个昂贵且延迟高的操作。
3. 工作原理与用途
- 默认关闭,需要手动在映射中为
text
字段开启。 - 数据存储在 JVM堆内存中,极易导致内存溢出(OOM)和集群不稳定。
- 主要用于
text
字段的聚合和排序。
PUT my_index/_mapping
{"properties": {"my_text_field": {"type": "text","fielddata": true // 警告:请谨慎开启!}}
}
4. fielddata
vs doc_values
特性 | doc_values | fielddata |
---|---|---|
存储介质 | 磁盘(利用文件系统缓存) | JVM堆内存 |
创建时机 | 索引时 | 首次聚合/排序时(延迟加载) |
默认状态 | 对支持字段默认开启 | 对 text 字段默认关闭 |
性能影响 | 低,磁盘操作 | 高,极易引起GC压力和OOM |
适用字段 | keyword , numeric , date , ip 等 | text |
最佳实践:极力避免开启 fielddata
。如果需要对字符串进行聚合和排序,优先使用 keyword
类型或多字段:
"product_name": {"type": "text", // 用于全文搜索"fields": {"raw": { // 用于聚合和排序"type": "keyword" // 使用keyword类型,它天然有doc_values支持}}
}
查询时使用 product_name.raw
进行聚合。
四、 store
参数:存储“额外”的副本
1. 是什么?
默认情况下,字段值存储在 _source
中。如果你设置 "store": true
,ES会额外将该字段的值单独存储在一份空间中。
2. 为什么需要它?
主要目的是在查询结果中返回某些特定的、未被 _source
包含的字段,或者从 _source
中提取某个字段开销很大时(因为 _source
需要被压缩和解压)。
常见误区:"store": true
并不会让字段变得可搜索!字段是否可搜索只由 "index"
参数控制。它只影响该字段的值是否可以被单独返回。
3. 使用场景
场景一:_source
被禁用,但你仍希望返回某个字段的值。
场景二:_source
非常大(例如包含一个很长的文本内容),但你只需要频繁返回其中的一两个小字段(如 title
, date
)。单独存储这些小字段可以避免在每次查询时解压整个庞大的 _source
,提升性能。
PUT my_index
{"mappings": {"_source": {"enabled": true // _source 仍然启用},"properties": {"title": {"type": "text","store": true // 额外存储一份},"content": {"type": "text","store": false // 不额外存储,只在 _source 里}}}
}// 查询时,使用 `stored_fields` 来指定返回哪些存储的字段
GET my_index/_search
{"stored_fields": [ "title" ], // 只返回显式声明为store=true的字段"query": {...}
}
最佳实践:这是一个非常高级的优化选项。在绝大多数情况下,你不需要设置 "store": true
。依赖 _source
是更简单、更通用的做法。
五、 null_value
:处理空值的利器
1. 是什么?
null_value
参数允许你用一个指定的值(如 "NULL"
, 0
, -1
)来替换显式的 null
,以便该字段可以被搜索和聚合。
2. 为什么需要它?
在ES中,一个字段的值为 null
、空数组 []
或者根本不存在,在Lucene底层是等价的——它们都没有被索引。这意味着你无法通过 term
查询找到值为 null
的文档。
3. 使用方法
"status_code": {"type": "keyword","null_value": "NULL" // 将显式的null替换为字符串"NULL"
}
索引数据:
{ "status_code": "active" }
{ "status_code": null } // 实际在索引中,这个字段的值是 "NULL"
{ } // 这个文档的status_code字段不存在,值仍是null
查询:
现在,你可以找到所有 status_code
为 null
的文档了:
GET my_index/_search
{"query": {"term": {"status_code": "NULL" // 搜索被替换后的值}}
}
最佳实践:对于需要区分“空值”业务的字段(如状态码、标签、分类),使用 null_value
可以保证数据的可查询性。
总结与核心建议
概念 | 核心用途 | 默认值 | 建议 |
---|---|---|---|
_source | 存储原始文档,用于结果返回、更新、重建索引 | true | 永远不要禁用 |
doc_values | 为排序、聚合、脚本提供磁盘上的列式存储 | true (对支持字段) | 不要禁用,除非绝对确定用不到 |
fielddata | 为 text 字段的排序聚合提供内存中的数据 | false | 极力避免开启,用keyword 多字段替代 |
store | 额外存储字段值,用于特定优化场景 | false | 大多数情况不需要,优先使用_source |
null_value | 将null 替换为可索引/聚合的默认值 | null | 按需使用,用于需要查询空值的业务字段 |
作为一名架构师,你的目标是:
- 保证功能:确保
_source
可用。 - 追求性能:善用
doc_values
,避免使用fielddata
。 - 精细优化:在明确收益后,再考虑使用
store
和null_value
这类高级参数。
透彻理解这些底层机制,将使你能够设计出既高效又稳健的Elasticsearch系统,从容应对各种复杂的业务场景。