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,但强烈不建议这样做

json 复制代码
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%确定该字段永远不会用于排序、聚合或脚本时才可以这样做。这可以节省磁盘空间。

json 复制代码
"my_field": {
  "type": "keyword",
  "doc_values": false // 谨慎禁用!
}

三、 fielddata:内存中的倒排索引

1. 是什么?
fielddatadoc_values内存版 互补方案,主要用于 text 字段。

2. 为什么需要它?
text 字段默认会被分词(analyzed),因此它们默认没有 doc_values 。如果你突然需要对一个 text 字段进行聚合或排序(例如,对 product_name 这个 text 字段做 terms 聚合),ES需要能够通过文档ID快速找到该文档的字段值。

fielddata 就是将所有分词后的词项包含它们的文档 的对应关系,从磁盘上的倒排索引加载到JVM堆内存 中形成的数据结构。这是一个昂贵且延迟高的操作。

3. 工作原理与用途

  • 默认关闭 ,需要手动在映射中为 text 字段开启。
  • 数据存储在 JVM堆内存中,极易导致内存溢出(OOM)和集群不稳定。
  • 主要用于 text 字段的聚合和排序。
json 复制代码
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 类型或多字段:

json 复制代码
"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,提升性能。

json 复制代码
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. 使用方法

json 复制代码
"status_code": {
  "type": "keyword",
  "null_value": "NULL" // 将显式的null替换为字符串"NULL"
}

索引数据

json 复制代码
{ "status_code": "active" }
{ "status_code": null }     // 实际在索引中,这个字段的值是 "NULL"
{ }                         // 这个文档的status_code字段不存在,值仍是null

查询

现在,你可以找到所有 status_codenull 的文档了:

json 复制代码
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 按需使用,用于需要查询空值的业务字段

作为一名架构师,你的目标是:

  1. 保证功能 :确保 _source 可用。
  2. 追求性能 :善用 doc_values,避免使用 fielddata
  3. 精细优化 :在明确收益后,再考虑使用 storenull_value 这类高级参数。

透彻理解这些底层机制,将使你能够设计出既高效又稳健的Elasticsearch系统,从容应对各种复杂的业务场景。

相关推荐
admiraldeworm4 小时前
Spring Boot + Spring AI 最小可运行 Demo
java·人工智能·ai
当牛作馬5 小时前
ES常用查询命令
数据库·mysql·elasticsearch
fs哆哆5 小时前
在VB.net中一维数组,与VBA有什么区别
java·开发语言·数据结构·算法·.net
johnZhangqi5 小时前
深圳大学-计算机信息管理课程实验 C++ 自考模拟题
java·开发语言·c++
奔跑草-5 小时前
【服务器】Apache Superset功能、部署与体验
运维·服务器·apache·powerbi·superset
David爱编程5 小时前
并发编程三大特性全解析:原子性、可见性、有序性,一文讲透!
java·后端
Sally璐璐5 小时前
Go语言变量声明与初始化详解
java·开发语言·golang
羊锦磊6 小时前
[ Servlet 服务器]
运维·服务器·servlet
C4程序员6 小时前
北京JAVA基础面试30天打卡14
java·开发语言·面试