深入浅出:Elasticsearch 中的_source字段到底怎么用?
你有没有遇到过这种情况:在 Kibana 里点开一条日志,想看看完整内容,结果提示“文档不可见”?或者发现 Elasticsearch 集群磁盘占用飙升,排查一圈才发现是_source存了太多冗余数据?
这背后,很可能就是_source字段惹的祸——它默默影响着你的查询能力、存储成本和系统性能。但别担心,今天我们就来彻底讲清楚这个“既重要又容易被误解”的核心机制。
什么是_source?一句话说清它的角色
简单来说,_source就是你写进去的原始 JSON 文档本身。
当你执行下面这条命令:
PUT /my_index/_doc/1 { "title": "深入理解 ES", "author": "张三", "tags": ["elasticsearch", "search"] }Elasticsearch 不仅会把title、author这些字段拆开建立倒排索引(用于搜索),还会把整个 JSON 对象原封不动地存一份到一个叫_source的特殊字段中。
📌关键点:
_source不参与搜索过程,它是专门用来“记住原始长什么样”的。
所以,当你执行GET /my_index/_doc/1或者做全文检索时能直接看到完整文档内容,靠的就是_source。
听起来好像理所当然?其实不然。很多人不知道的是:你可以控制要不要存_source,甚至可以只存其中一部分字段。而这,正是优化 Elasticsearch 性能与成本的关键入口之一。
_source到底有什么用?这些功能都离不开它
别小看这一份“备份”,很多你以为理所当然的功能,其实全依赖_source是否可用。我们来看几个典型场景:
✅ 支持文档更新
如果你要用update API修改某个字段:
POST /my_index/_update/1 { "doc": { "read_count": 100 } }Elasticsearch 实际上是:
1. 先从_source取出原始 JSON;
2. 合并新字段;
3. 重新索引整条文档。
如果_source被禁用了——对不起,这个操作直接失败。
✅ 高亮显示(Highlighting)
搜索关键词时,页面上高亮标出匹配的文字,体验很好对吧?
但你知道吗?高亮功能需要知道原文结构才能准确标记位置。而这份“原文”,就来自_source。没了它,highlight 直接失效。
✅ Top Hits 聚合展示详情
比如你想查每个用户的最近一次登录记录:
"aggs": { "latest_login": { "top_hits": { "size": 1, "sort": [{ "timestamp": "desc" }] } } }这里的top_hits返回的是完整的文档内容,依然得靠_source。
✅ Reindex 和数据迁移
当你升级集群、调整 mapping 或分片策略时,通常要用_reindex把旧索引导入新索引。如果原索引没有_source,那这场迁移基本就废了——没源数据,拿什么复制?
所以,能不能关掉_source?当然可以!但代价是什么?
既然_source占空间,那我能不能干脆不存?答案是:技术上完全可以,但必须接受功能退化。
我们来看一组直观对比:
| 功能 | 启用_source | 禁用_source |
|---|---|---|
| 查询返回完整文档 | ✅ 是 | ❌ 否 |
| 使用 update 更新文档 | ✅ 是 | ❌ 否 |
| 高亮关键词 | ✅ 是 | ❌ 否 |
| top_hits 聚合取详情 | ✅ 是 | ❌ 否 |
| reindex 数据迁移 | ✅ 是 | ❌ 否 |
| 存储开销 | 较高(约 +30%~60%) | 极低 |
结论很明确:开启 = 功能完整 + 成本更高;关闭 = 节省空间 + 功能受限。
那么问题来了:什么时候该关?什么时候坚决不能关?
实战配置指南:四种常见控制方式详解
方式一:完全禁用_source—— 极致压缩场景专用
适用于那些“只写一次、永不修改、也不需要查看详情”的数据,比如 IoT 设备上报的指标、Nginx 访问日志等。
PUT /metrics-no-source { "mappings": { "_source": { "enabled": false }, "properties": { "device_id": { "type": "keyword" }, "cpu_usage": { "type": "float" }, "timestamp": { "type": "date" } } } }插入后尝试获取文档:
GET /metrics-no-source/_doc/1你会发现返回结果里根本没有_source字段!
⚠️警告:一旦禁用,以下操作全部失效:
-GET查不到原始内容
-update报错
-reindex无法复制数据
- Kibana “查看文档” 功能空白
🔍 建议使用场景:纯聚合分析型日志、监控指标流,且业务确认永远不需要回溯原始事件。
方式二:部分保留_source—— 安全与效率兼顾的最佳实践
更常见的需求是:我想存_source,但不想暴露敏感字段,比如密码、token、身份证号。
这时可以用includes和excludes来做过滤:
PUT /users-safe-source { "mappings": { "_source": { "includes": [ "user_id", "profile.*" ], "excludes": [ "password", "auth_token", "private_info" ] }, "properties": { "user_id": { "type": "keyword" }, "username": { "type": "keyword" }, "password": { "type": "keyword" }, "auth_token": { "type": "keyword" }, "profile.name": { "type": "text" }, "profile.email": { "type": "keyword" } } } }插入一条用户数据:
PUT /users-safe-source/_doc/1 { "user_id": "u1001", "username": "alice", "password": "123456", "auth_token": "xyz789", "profile": { "name": "Alice Wang", "email": "alice@company.com" } }查询结果中的_source只包含:
{ "user_id": "u1001", "profile": { "name": "Alice Wang", "email": "alice@company.com" } }✅ 效果达成:
- 减少了网络传输量;
- 避免前端意外泄露隐私;
- 存储略降;
- 仍支持 update、highlight 等功能。
💡 提示:支持通配符,如
profile.*、logs.*,非常适合嵌套对象的细粒度控制。
方式三:启用压缩存储 —— 冷数据归档利器
虽然不能改变_source的格式,但 Elasticsearch 支持对其内容进行压缩存储,默认使用 LZ4 算法。
如果你希望进一步节省磁盘空间(尤其是冷节点上的历史数据),可以切换为更高压缩率的编码器:
PUT /archive-compressed { "settings": { "index.codec": "best_compression" }, "mappings": { "_source": { "enabled": true } } }"best_compression"实际使用 DEFLATE 算法,压缩率比默认高 10%~20%,但写入速度稍慢一点。
📌适用场景:
- 冷热架构中的冷节点;
- 归档类索引(如半年前的日志);
- 对写入性能不敏感、但极度关注存储成本的环境。
⚖️ 权衡建议:不要在热节点上广泛使用,避免影响写入吞吐;可在 ILM 生命周期策略中为“冷阶段”自动切换 codec。
方式四:用stored_fields替代 —— 当_source必须关闭时的补救方案
前面说了,禁用_source后就不能返回原始文档了。但如果我只是想展示几个关键字段呢?比如日志的时间戳和错误级别?
这时候可以用store: true的字段配合stored_fields查询来实现轻量级替代。
映射设置:
PUT /logs-with-store { "mappings": { "_source": { "enabled": false }, "properties": { "timestamp": { "type": "date", "store": true }, "level": { "type": "keyword", "store": true }, "message": { "type": "text" } } } }查询时指定返回字段:
GET /logs-with-store/_search { "stored_fields": ["timestamp", "level"] }返回结果:
"hits": { "hits": [ { "_id": "1", "fields": { "timestamp": ["2023-04-01T10:00:00Z"], "level": ["ERROR"] } } ] }⚠️ 注意限制:
-store只支持 keyword、date、numeric 等精确值类型;
-text类型即使设置了store: true,也只能返回原始值,不能做分词后的片段提取;
- 无法用于update或highlight;
- 存储效率不如_source统一管理灵活。
🧩 总结:这是“不得已而为之”的方案,适合极端压缩需求下的妥协选择。
实际案例:电商行为日志如何平衡性能与成本?
某电商平台每天产生数亿条用户点击、浏览、加购行为日志。初期所有数据都保留_source,几个月后存储暴涨至 PB 级别。
痛点分析:
- 大部分查询只关心user_id,action_type,item_id,timestamp
- 几乎不用update
- Kibana 上只需展示关键字段,无需查看完整原始报文
- 团队愿意牺牲“查看详情”功能换取成本下降
解决方案:
1. 创建新索引模板,禁用_source
2. 将高频查询字段设为store: true
3. 查询统一使用stored_fields获取最小集
PUT /user_behavior_v2 { "mappings": { "_source": { "enabled": false }, "properties": { "user_id": { "type": "keyword", "store": true }, "action_type": { "type": "keyword", "store": true }, "item_id": { "type": "keyword", "store": true }, "timestamp": { "type": "date", "store": true }, "context": { "type": "object" } // 不存储,仅用于索引过滤 } } }成果:
- 存储空间减少约 60%
- 写入吞吐提升 15%
- 查询延迟略有下降
- Kibana 表格正常展示,但“查看文档”为空(可接受)
✅ 成功在功能与成本之间找到平衡点。
最佳实践总结:别再盲目配置了!
最后送上一份开发者必看的决策清单:
| 场景 | 推荐做法 |
|---|---|
| 通用搜索(商品、文章、客服工单) | ✅ 启用_source,保持默认 |
| 用户资料、订单系统含敏感信息 | ✅ 启用 +excludes过滤密码/token |
| 日志分析(需查看详情) | ✅ 启用,确保 Kibana 可用 |
| 海量指标采集(CPU/内存) | ❌ 禁用_source,极致压缩 |
| 实时仪表盘只需关键字段 | ❌ 禁用 +stored_fields补偿 |
| 支持文档更新的应用 | ❗ 必须启用,禁止关闭 |
✅ 黄金法则五条:
- 除非有明确压测数据支撑,否则不要轻易禁用
_source - 敏感字段优先通过
excludes过滤,而非应用层处理 - 冷数据可结合
best_compression进一步瘦身 - mapping 一旦创建,
_source设置不可更改,务必提前规划 - 在 ILM 或索引模板中预设策略,避免后期重构
写在最后:真正懂 ES 的人,都在细节上下功夫
_source看似只是一个字段,实则是连接数据写入与查询体验的核心枢纽。它不像分片、副本那样显眼,也不像 query DSL 那样炫酷,但它默默地决定了你的系统是否具备灵活性、可维护性和扩展性。
掌握_source的控制方法,不是为了炫技,而是为了让 Elasticsearch 真正服务于业务——既不过度浪费资源,也不因小失大失去关键能力。
下次你在设计索引结构时,不妨多问一句:
“我的这个索引,真的需要
_source吗?如果需要,要存全部还是部分?”
这个问题的答案,可能就是你系统能否稳定运行三年的关键伏笔。
如果你正在搭建日志平台、搜索服务或数据分析系统,欢迎留言交流你的_source使用经验,我们一起避坑成长。