bigywater.tech
Elasticsearch · 11

object 与 nested 的区别

真实对比object和nested两种映射存同一份带数组的数据,验证object类型真实存在的跨对象假阳性匹配问题,以及nested类型对查询语法的真实硬性要求。

object类型
真实把数组拍平,跨对象错误组合会假阳性命中
nested+nested查询
真实正确隔离,同一子文档内部条件才算数
忘套nested语法
真实哪怕最简单的匹配也会变成0命中

做的事:同一份users数组,真实分别用object和nested映射存一遍

代码PUT /obj_test {"mappings":{"properties":{"users":{"type":"object"}}}} PUT /nested_test {"mappings":{"properties":{"users":{"type":"nested"}}}} POST .../_doc/1 {"users": [{"name":"alice","age":30}, {"name":"bob","age":40}]} # 故意查一个错误的跨对象组合:alice并且40岁(真实数据里alice是30,bob才是40) GET /obj_test/_search {"query":{"bool":{"must":[ {"match":{"users.name":"alice"}}, {"term":{"users.age":40}} ]}}}

验证:object索引真实把两个数组拍平了,错误的跨对象组合真实命中了

实测 · 本机 Elasticsearch 8.15.0 执行输出
obj_test的mapping:
users.name → text+keyword
users.age  → long
(真实扁平化成了users.name:[alice,bob]和users.age:[30,40]两个独立数组)

查询"alice并且40岁": hits.total.value = 1   ← 真实命中了,但alice真实是30岁,不是40岁!
这是object类型最真实、也最危险的陷阱:Lucene底层真实没有"一个对象里的字段互相关联"这种结构,object映射会把数组里每个对象的同名字段真实拍平成并列的多值字段——users.name变成["alice","bob"],users.age变成[30,40],两个数组之间"第几个元素对应第几个元素"这份关联信息在拍平的那一刻就真实丢失了。查询"name=alice并且age=40"的时候,ES真实只是分别检查这两个数组里"存不存在alice"和"存不存在40",两者都为真,查询就真实判定为命中——哪怕这两个值分别属于数组里两个完全不同的对象。

验证:nested索引配合nested查询,同样错误的组合真实被正确拒绝了

实测 · 本机执行输出
GET /nested_test/_search {"query":{"nested":{"path":"users","query":{"bool":{"must":[
  {"match":{"users.name":"alice"}}, {"term":{"users.age":40}}
]}}}}}
→ hits.total.value = 0    ← 真实正确:没有任何一个user对象同时满足这两个条件

换成真实正确的组合(alice并且30岁):
→ hits.total.value = 1    ← 真实命中

这组对比真实证明了nested类型的底层机制:数组里的每个对象真实被索引成一个完全独立的、隐藏的Lucene文档(这些隐藏文档真实跟父文档存在同一个Lucene segment里,但对外不可见),nested查询真实的语义是"在同一个隐藏子文档内部,这些条件必须同时成立"——alice和30同属一个子文档,查询真实能对上;alice和40分属两个不同的子文档,nested查询真实会正确判定它们不构成一个满足条件的匹配。

真实更意外的发现:忘了套nested查询,哪怕最简单的匹配也会真实返回0命中

实测 · 本机执行输出
对nested_test直接查(不套nested wrapper):
GET /nested_test/_search {"query":{"match":{"users.name":"alice"}}}
→ hits.total.value = 0    ← 真实0命中!即使alice真实存在于这个文档里
这是本文认为整篇最容易让人措手不及的真实发现:nested字段底层那些独立的隐藏子文档,默认真实完全不参与普通的顶层查询——哪怕只是最简单的match users.name:alice,不套一层nested查询,返回的真实是0命中,不是报错,也不是"至少按字段名蒙对了一部分",而是彻彻底底查不到。这跟object类型的行为是两个极端:object太"宽松"(允许错误的跨对象组合悄悄命中),nested默认情况下又太"严格到看不见"(忘了套查询语法,数据实际存在却查不出来,很容易被误判成"写入失败了")。真实使用nested类型时,所有涉及这个字段的查询、聚合、排序都必须显式包一层nested语法(聚合对应nested aggregation),这是选择nested类型时必须接受的真实代价——换来数据正确性,但查询写法会比object复杂不少,团队里不熟悉这一点的人很容易写出"查不到任何结果"的查询还以为是数据问题。

分步演示:从object的跨对象误匹配到nested查询的两个极端

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

object的假阳性匹配 vs nested的正确隔离与默认不可见
这一步新验证的查询方式

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 nested 查询与隐藏子文档机制
    京ICP备2026067502号-1京公网安备11010502063487号