ES object与nested

在 ES 中,objectnested 是用来表示复杂 JSON 结构的两种字段类型

object 类型

object 类型是最基本的嵌套结构,用来表示 JSON 对象中的字段。这些字段会被扁平化存储在同一个文档中。

使用场景

  • 当你需要存储一个简单的对象或键值对,并且不需要在查询时进行复杂的关联操作。
  • 适合处理非数组的嵌套结构,或者数组中的元素之间不需要保持独立的关联。

定义方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PUT /my_index
{
"mappings": {
"properties": {
"user": {
"type": "object",
"properties": {
"first_name": {
"type": "text"
},
"last_name": {
"type": "text"
}
}
}
}
}
}

存储的数据

1
2
3
4
5
6
7
PUT /my_index/_doc/1
{
"user": {
"first_name": "John",
"last_name": "Doe"
}
}

查询示例

对于 object 类型,查询是基于字段的简单扁平化索引。

1
2
3
4
5
6
7
8
GET /my_index/_search
{
"query": {
"match": {
"user.first_name": "John"
}
}
}

优缺点

  • 优点:适合简单嵌套对象,查询性能较好,易于使用。
  • 缺点:当对象嵌套在数组中时,查询的关联性问题可能无法得到正确处理(比如数组中的多个元素之间的关联关系会丢失)。

扁平化问题

当对象存储为 object 类型时,所有字段都会被扁平化。这意味着如果存储的是数组,多个元素的字段值会被简单地索引在一起,查询时可能会返回意想不到的结果。

nested 类型

nested 类型用于存储嵌套的数组,并保持每个数组元素之间的独立性。Elasticsearch 会为每个数组元素创建独立的文档,因此可以保证数组中的对象之间不会因为扁平化而失去关联性。

使用场景

  • 当你需要存储一个复杂的对象数组,并且希望数组中的每个对象在查询时保持独立的关联性(即查询时不会因为扁平化造成不准确的匹配)。
  • 适合处理需要复杂查询条件的对象数组。

定义方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PUT /my_index
{
"mappings": {
"properties": {
"comments": {
"type": "nested",
"properties": {
"author": {
"type": "text"
},
"content": {
"type": "text"
},
"likes": {
"type": "integer"
}
}
}
}
}
}

存储的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /my_index/_doc/1
{
"comments": [
{
"author": "Alice",
"content": "This is a great post",
"likes": 5
},
{
"author": "Bob",
"content": "Thanks for sharing!",
"likes": 2
}
]
}

查询示例

使用 nested 类型时,你需要明确指定 nested 查询,来保证查询的精确性。

查询评论中 author 为 “Alice” 且 likes 为 5 的文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /my_index/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{ "match": { "comments.author": "Alice" }},
{ "match": { "comments.likes": 5 }}
]
}
}
}
}
}

优缺点

  • 优点:支持嵌套数组的精确查询,能正确处理数组中对象之间的关系。
  • 缺点:由于每个数组元素被独立索引,因此存储和查询的性能成本更高(相比 object 类型)。

区别总结

特性 object 类型 nested 类型
结构 文档中简单的嵌套对象 适用于数组中的嵌套对象
存储方式 扁平化存储在同一个文档中 每个数组元素独立存储,关联性保持
查询关联性 扁平化可能导致数组中不同对象字段混淆 维持数组中对象之间的独立性
查询语法 普通查询 需要使用 nested 查询
性能 查询性能较好,适合简单结构 查询性能较低,适合复杂数组结构
适用场景 适合简单对象或非数组结构 适合需要复杂查询的嵌套数组

什么时候使用 object,什么时候使用 nested

使用 object 类型:

  • 数据结构较为简单,没有数组,或者数组中的字段之间不需要保持严格的关联。
  • 需要较高的查询性能,且不需要处理复杂的嵌套查询逻辑。
  • 查询需求不涉及对嵌套数组内部对象的精确匹配。

使用 nested 类型:

  • 需要存储嵌套的对象数组,且数组中的对象需要在查询时保持独立性。
  • 需要对嵌套数组的内部对象进行复杂的查询,确保精确匹配(如数组中的某个对象的多个字段必须同时满足查询条件)。
  • 愿意为了精确查询支付更高的性能和存储成本。

实例对比

object 类型的查询误差:

假设你存储了如下数据(对象数组形式):

1
2
3
4
5
6
7
PUT /my_index/_doc/1
{
"users": [
{ "first_name": "Alice", "age": 30 },
{ "first_name": "Bob", "age": 40 }
]
}

你执行以下查询:

1
2
3
4
5
6
7
8
9
10
11
GET /my_index/_search
{
"query": {
"bool": {
"must": [
{ "match": { "users.first_name": "Alice" }},
{ "match": { "users.age": 40 }}
]
}
}
}

结果可能会匹配到这个文档,尽管实际数据中 "Alice" 的年龄是 30,而不是 40。因为 object 类型会扁平化字段,导致查询结果混淆。

nested 类型的精确匹配:

相同的数据如果定义为 nested 类型,则可以避免这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT /my_index
{
"mappings": {
"properties": {
"users": {
"type": "nested",
"properties": {
"first_name": { "type": "text" },
"age": { "type": "integer" }
}
}
}
}
}

当你执行相同的查询时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /my_index/_search
{
"query": {
"nested": {
"path": "users",
"query": {
"bool": {
"must": [
{ "match": { "users.first_name": "Alice" }},
{ "match": { "users.age": 40 }}
]
}
}
}
}
}

此时,查询将不会返回错误的结果,因为 nested 保证了数组元素的独立性。

总结

  • object 类型 适用于简单的对象结构,不会引起数组字段之间的混淆。
  • nested 类型 适合复杂的嵌套对象数组,能够在查询时维持元素的独立性,避免因为扁平化引起的数据不一致问题。