真实关闭_source并给字段单独开store,验证stored_fields和docvalue_fields如何在没有_source的情况下取到数据,以及Update API为什么会因此彻底失效。
代码PUT /nosource_test {"mappings": { "_source": {"enabled": false}, "properties": { "title": {"type": "keyword", "store": true}, "views": {"type": "integer"} } }} POST .../_doc/1 {"title":"hello", "views":42} GET .../_doc/1 # 普通取文档 GET .../_search {"stored_fields":["title"]} # 走store GET .../_search {"docvalue_fields":["views"]} # 走doc_values POST .../_update/1 {"doc":{"views":43}} # 能更新吗
GET /nosource_test/_doc/1
→ {"_index":"nosource_test","_id":"1","found":true}
(真实没有任何_source字段,连title和views的值都看不到)这是关闭_source最直接的真实后果:found:true说明这条文档真实存在,但响应里真实完全没有数据本身——_source存的是写入时那份完整的原始JSON备份,关掉它之后,这份"万能备份"真实不再存在,默认的取值方式完全拿不到任何字段内容。
GET .../_search {"stored_fields":["title"]}
→ fields.title = ["hello"] ← 真实拿到了,走的是title字段单独开的store:true
GET .../_search {"docvalue_fields":["views"]}
→ fields.views = [42] ← 真实也拿到了,走的是views字段默认开启的doc_valuesPOST /nosource_test/_update/1 {"doc":{"views":43}}
→ 400 document_source_missing_exception: "[1]: document source missing"这是本文认为关闭_source最真实的代价所在:ES的Update API真实不是在Lucene层面原地修改某个字段(Lucene的段文件本身真实是不可变的,本系列后面会详细验证这一点),而是真实先读出这条文档完整的_source,在内存里跟传入的doc合并成新版本,再整条重新写入——这个"读出旧_source再合并"的步骤真实完全依赖_source的存在,没有它,Update API从机制根源上就真实没法工作,哪怕只是想改一个字段的值。这也是为什么关闭_source通常只适合那些写入后不再需要原地更新、只需要只读查询的数据场景(纯日志、不可变的归档数据),一旦业务真实需要partial update,关闭_source这条路真实走不通。
每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。
_source.enabled / stored_fields / docvalue_fields