1. 前述

嵌套类型是对象数据类型的一个特殊化版本,它允许对象数组以一种可以相互独立查询(内嵌关系,对象内关联不丢失)的方式进行索引。

2. 内部对象数组是如何进行展平的(未使用nested字段)

内部对象数组字段的工作方式可能与你预期的不同。Lucene是没有内部对象的概念的。Elasticsearch将对象层次结构展平为一个简单的字段名和值列表。例如,以下文档:

2.1 定义索引类型(内部对象)

  1. PUT inner_object_index/_doc/1
  2. {
  3. "group" : "stars",
  4. "user" : [
  5. {
  6. "first" : "张",
  7. "last" : "学友"
  8. },
  9. {
  10. "first" : "刘",
  11. "last" : "德华"
  12. },
  13. {
  14. "first" : "郭",
  15. "last" : "富城"
  16. },
  17. {
  18. "first" : "黎",
  19. "last" : "明"
  20. }
  21. ]
  22. }

在ES内部,将上述文档转换为更扁平化的文档,如下

  1. {
  2. "group" : "fans",
  3. "user.first" : [ "张", "刘", "郭", "黎" ],
  4. "user.last" : [ "学友", "德华", "富城", "明" ]
  5. }

user.first和user.last字段被展平为多值字段,以“刘德华”为例,姓氏“刘”和名字“德华”之间的关联将丢失,下面实例查询姓氏为“”名字为“富城”(文档中并没有“刘富城”这个人)的文档:

2.2 查询文档

  1. GET inner_object_index/_search
  2. {
  3. "query": {
  4. "bool": {
  5. "must": [
  6. { "match": { "user.first": "刘" }},
  7. { "match": { "user.last": "德华" }}
  8. ]
  9. }
  10. }
  11. }

2.3 查询结果

  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 5,
  6. "successful" : 5,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : 1,
  12. "max_score" : 0.8630463,
  13. "hits" : [
  14. {
  15. "_index" : "nested_object_index",
  16. "_type" : "_doc",
  17. "_id" : "1",
  18. "_score" : 0.8630463,
  19. "_source" : {
  20. "group" : "stars",
  21. "user" : [
  22. {
  23. "first" : "张",
  24. "last" : "学友"
  25. },
  26. {
  27. "first" : "刘",
  28. "last" : "德华"
  29. },
  30. {
  31. "first" : "郭",
  32. "last" : "富城"
  33. },
  34. {
  35. "first" : "黎",
  36. "last" : "明"
  37. }
  38. ]
  39. }
  40. }
  41. ]
  42. }
  43. }

3. 对象数组使用嵌套字段定义(使用nested字段)

如果需要索引对象数组并保持数组中每个对象的独立性,应该使用嵌套数据类型(nested datatype)而不是对象数据类型(object datatype)。在内部,嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以使用嵌套查询,独立于其他对象查询每个嵌套对象:

3.1 定义索引类型

  1. PUT nested_objects_index
  2. {
  3. "mappings": {
  4. "_doc": {
  5. "properties": {
  6. "group":{
  7. "type":"keyword"
  8. },
  9. "user": {
  10. "type": "nested"
  11. }
  12. }
  13. }
  14. }
  15. }

3.2 插入测试数据

  1. PUT nested_objects_index/_doc/1
  2. {
  3. "group" : "stars",
  4. "user" : [
  5. {
  6. "first" : "张",
  7. "last" : "学友"
  8. },
  9. {
  10. "first" : "刘",
  11. "last" : "德华"
  12. },
  13. {
  14. "first" : "郭",
  15. "last" : "富城"
  16. },
  17. {
  18. "first" : "黎",
  19. "last" : "明"
  20. }
  21. ]
  22. }

3.3 查询示例1及结果1(嵌套查询)

查询姓氏为“刘”名字为“富城”(“刘”“德华”不是同一个内嵌对象)的文档:

  1. GET nested_objects_index/_search
  2. {
  3. "query":{
  4. "nested":{
  5. "path":"user",
  6. "query":{
  7. "bool":{
  8. "must":[
  9. {"match":{"user.first":"刘"}},
  10. {"match":{"user.last":"富城"}}
  11. ]
  12. }
  13. }
  14. }
  15. }
  16. }
  1. {
  2. "took" : 0,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 5,
  6. "successful" : 5,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : 0,
  12. "max_score" : null,
  13. "hits" : [ ]
  14. }
  15. }

由于文档中并没有“刘富城”这个人,查询结果为空,显然,使用nested定义的字段,在ES内部,将对象数组扁平化的过程中,对象属性之间的关联关系不会发生改变

3.4 查询示例2及结果2(嵌套查询,突出显示)

查询姓氏为“刘”名字为“德华”(“刘”“德华”为同一个内嵌对象)的文档:

  1. GET nested_objects_index/_search
  2. {
  3. "query": {
  4. "nested": {
  5. "path": "user",
  6. "query": {
  7. "bool": {
  8. "must": [
  9. { "match": { "user.first": "刘" }},
  10. { "match": { "user.last": "德华" }}
  11. ]
  12. }
  13. },
  14. "inner_hits": {
  15. "highlight": {
  16. "fields": {
  17. "user.first": {},
  18. "user.last": {}
  19. }
  20. }
  21. }
  22. }
  23. }
  24. }

查询结果如下:

  1. {
  2. "took" : 2,
  3. "timed_out" : false,
  4. "_shards" : {
  5. "total" : 5,
  6. "successful" : 5,
  7. "skipped" : 0,
  8. "failed" : 0
  9. },
  10. "hits" : {
  11. "total" : 1,
  12. "max_score" : 3.4789643,
  13. "hits" : [
  14. {
  15. "_index" : "nested_objects_index",
  16. "_type" : "_doc",
  17. "_id" : "1",
  18. "_score" : 3.4789643,
  19. "_source" : {
  20. "group" : "stars",
  21. "user" : [
  22. {
  23. "first" : "张",
  24. "last" : "学友"
  25. },
  26. {
  27. "first" : "刘",
  28. "last" : "德华"
  29. },
  30. {
  31. "first" : "郭",
  32. "last" : "富城"
  33. },
  34. {
  35. "first" : "黎",
  36. "last" : "明"
  37. }
  38. ]
  39. },
  40. "inner_hits" : {
  41. "user" : {
  42. "hits" : {
  43. "total" : 1,
  44. "max_score" : 3.4789643,
  45. "hits" : [
  46. {
  47. "_index" : "nested_objects_index",
  48. "_type" : "_doc",
  49. "_id" : "1",
  50. "_nested" : {
  51. "field" : "user",
  52. "offset" : 1
  53. },
  54. "_score" : 3.4789643,
  55. "_source" : {
  56. "first" : "刘",
  57. "last" : "德华"
  58. },
  59. "highlight" : {
  60. "user.first" : [
  61. "<em>刘</em>"
  62. ],
  63. "user.last" : [
  64. "<em>德</em><em>华</em>"
  65. ]
  66. }
  67. }
  68. ]
  69. }
  70. }
  71. }
  72. }
  73. ]
  74. }
  75. }

inner_hits属性允许我们突出显示匹配的嵌套文档。

4. 嵌套文档使用

5. 嵌套定义参数分析

参数名称 参数说明
dynamic 是否支持将新属性动态添加到现有嵌套对象。接收true(默认)、false和strict。
properties 嵌套对象中的字段,可以是任何数据类型,包括嵌套类型(即嵌套对象里面定义嵌套字段)。可以将新properties添加到现有嵌套对象中。

提示:由于嵌套文档作为单独的文档编制索引,因此只能在嵌套查询(nested query)、嵌套/反向嵌套聚合(nested/reverse_nested aggregations)或嵌套内部命中(nested inner hits)的范围内访问它们。

6. 嵌套字段数量限制

为包含100个嵌套字段的文档编制索引,实际上是为101个文档编制索引,因为每个嵌套文档都作为单独的文档编制索引。为了防止错误定义的映射(mapping),每个索引可以定义的嵌套字段的数量限制为50个。

7. 防止映射爆炸(mapping explosion)的设置

在索引中定义太多字段可能导致映射爆炸,这种爆炸可能导致内存不足错误和数据难以恢复。这个问题可能比预期的更常见。例如,考虑这样一种情况:插入的每个新文档都会引入新字段。这在动态映射(dynamic mappings)中很常见。每次文档包含新字段时,这些字段都会出现在索引的映射中。对于量数据少这并不担心,但随着映射的增长,这可能会成为一个问题。以下设置允许您限制手动或动态创建的字段映射的数量,以防止错误文档导致映射爆炸:

配置项 配置项说明
index.mapping.total_fields.limit 索引中字段的最大数目。字段和对象映射(object mappings)以及字段别名(field aliases)都将计入此限制。默认值为1000。
index.mapping.depth.limit 字段的最大深度。以inner objects的数量来衡量。例如,如果所有字段都在根对象级别定义,则深度为1。如果有一个对象映射,则深度为2,以此类推。默认值为20。
index.mapping.nested_fields.limit 索引中嵌套字段的最大数目,默认为50。为一个包含100个嵌套字段的文档编制索引实际上是为101个文档编制索引,因为每个嵌套文档都作为单独的隐藏文档编制索引