当前位置:   article > 正文

ES中关联关系处理方式汇总_elasticsearch 的一对一关联索引查询

elasticsearch 的一对一关联索引查询

前言

本节主要介绍在ES中关联关系的处理方式。


一、方案汇总

根据《Elasticsearch权威指南》以及官网中的介绍,ES针对关联关系的处理主要有如下方式:

  1. 应用层关联
  2. 非规划化数据
  3. 嵌套对象
  4. 父子关系文档
  5. Terms lookup跨索引查询

Join、Nested、Object、Flattened字段类型对比

二、应用层关联

对索引数据不进行特殊处理,而是在应用程序中通过多次查询实现数据的关联查询。
例如,比方下面的例子,一个问题会有多个答案,且问题数据和答案数据在不同的索引中。

1、创建问题索引question_index

PUT question_index
{
  "mappings": {
      "properties": {
        "id":{"type": "keyword"},
        "text":{"type": "keyword"}
      }
    }
}

PUT question_index/_doc/1?refresh
{
  "id":"1",
  "text": "我是第一个问题"
}

PUT question_index/_doc/2?refresh
{
  "id":"2",
  "text": "我是第二个问题"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

2、创建答案索引question_index
说明:其中pid是问题id。

PUT answer_index
{
  "mappings": {
      "properties": {
        "pid":{"type": "keyword"},
        "text":{"type": "keyword"}
      }
    }
}

PUT answer_index/_doc/1?refresh 
{
  "pid":"1",
  "text": "问题一的答案1"
}

PUT answer_index/_doc/2?refresh
{
  "pid":"1",
  "text": "问题一的答案2"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3、业务场景
现在需要查询第一个问题的对应答案信息,我们可以这样做:

首先,根据问题名称查出对应记录的id。

GET question_index/_search
{
  "query": {
    "term": {
      "text": {
        "value": "我是第一个问题"
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后,将第一个查询得到的结果将填充到 terms 过滤器中,从answer_index中查询出答案数据。

GET answer_index/_search
{
  "query": {
    "terms": {
      "pid": [
        "1"
      ]
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

优缺点分析:
采用应用层关联的主要优点是简单,不需要对数据结构做额外的处理,可以对任意两个不同的索引进行关联查询。
缺点是必须进行多次查询。

适用场景:
应用层关联适用于关联数据较少的情况,原因是terms对大量数据的进行多值匹配查询性能会比较差。

使用terms可以进行多值查询, 只要目标文档匹配terms查询中的一个值, 此文档就会被标记为查询结果中的一个, 但terms的参数值是有限制的, 默认65535个元素, 你可以通过设置index.max_terms_count来进行更改。
还可以通过terms lookup语法来解决terms参数元素过多的情况。

三、非规范化数据

为了获得较好的检索性能,最好的方法是在索引建模时进行非规范化数据存储,通过对文档数据字段的冗余保存避免访问时进行关联查询。
比如下面的例子,希望通过用户姓名找到他写的博客文章。
常规的方法索引结构如下,在blog_index索引中只保存user_id,用来关联用户信息。

PUT user_index
{
  "mappings": {
     "properties": {
        "id":  {"type": "keyword"},
        "name":   {"type": "keyword"},
        "email":   {"type": "keyword"}
     }
  }
}

PUT blog_index
{
  "mappings": {
      "properties": {
        "title":{"type": "keyword"},
        "body":{"type": "keyword"},
        "user_id":{"type": "keyword"}
      }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

非规范化数据处理:
说明:
将用户信息直接通过Object字段类型保存在博客索引数据中,这样通过数据的冗余保存,就避免了关联查询。

PUT blog_index
{
  "mappings": {
      "properties": {
        "title":{"type": "keyword"},
        "body":{"type": "keyword"},
        "user":{
          "properties": {
            "id":  {"type": "keyword"},
            "name":   {"type": "keyword"},
            "email":   {"type": "keyword"}
          }
        }
      }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

查询用户名称为老万的博客数据:

GET blog_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "user.name": "老万"}}
      ]
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

优缺点分析:
数据非规范化的优点是速度快。因为每个文档都包含了所需的所有信息,当这些信息需要在查询进行匹配时,并不需要进行昂贵的联接操作。
缺点是由于对大量数据进行了冗余存储,会占用更大的存储空间,且对关联数据的更新操作会更复杂。

四、嵌套对象

通过nested构建嵌套数据类型,也可以实现数据的关联关系。
在上面的非规范化数据中,已经演示了通过Object字段类型来冗余保存关联数据避免数据关联查询。

那么两者有什么区别呢?

Object fileds 和nested fileds的区别:

官方说明:Object fileds 和nested fileds的区别
如果需要索引对象数组并保持数组中每个对象的独立性,请使用嵌套数据类型而不是对象数据类型。
在内部,嵌套对象将数组中的每个对象作为单独的隐藏文档进行索引,这意味着可以使用嵌套查询独立于其他对象查询每个嵌套对象。

简单来说:
Object fileds适合保存简单对象,不能用来保存对象数组,因为它不能保证多个对象查询时的独立性。
nested fileds适合保存对象数组。

## 1、创建索引,指定user字段为嵌套对象
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "user": {
        "type": "nested" 
      }
    }
  }
}
## 2、添加数据
PUT my-index-000001/_doc/1
{
  "group" : "fans",
  "user" : [
    {
      "first" : "John",
      "last" :  "Smith"
    },
    {
      "first" : "Alice",
      "last" :  "White"
    }
  ]
}
## 3、查询数据;查询姓Alice,名Smith的用户。如果是user是Object类型可以查询到记录。
## 而nested类型由于每个对象相互隔离,没有满足条件的记录
GET my-index-000001/_search
{
  "query": {
    "nested": {
      "path": "user",
      "query": {
        "bool": {
          "must": [
            { "match": { "user.first": "Alice" }},
            { "match": { "user.last":  "Smith" }} 
          ]
        }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

优缺点分析:
Object fileds 适合一对一的关联关系
nested fileds 适合一对多的关联关系。
两者都是通过非规范化数据,利用数据的冗余保存来避免关联查询。
无论是Object fileds 还是nested fileds ,都是在同一条记录中保存数据的关联关系。

五、父子关系文档

通过join字段类型,构建索引记录间的父子关联关系。
ES中通过join类型字段构建父子关联

官网地址:Join field type
join类型的字段主要用来在同一个索引中构建父子关联关系。通过relations定义一组父子关系,每个关系都包含一个父级关系名称和一个子级关系名称。

示例:
创建索引my_index,并在mappings中指定关联字段my_join_field的type类型为join,
并通过relations属性指定关联关系,父级关系名称为question,子级关系名称为answer。
这里的父子级关系的名称可以自己定义,在向索引中添加数据时,需要根据定义的关系名称
指定my_join_field字段的值。
my_join_field关联字段的名称也可以自定义。

PUT my_index
{
  "mappings": {
      "properties": {
        "text":{"type": "keyword"},
        "my_join_field": { 
          "type": "join",
          "relations": {
            "question": "answer" 
          }
        }
      }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

优缺点分析:
通过Join字段构建的父子关联关系,数据保存在同一索引的相同分片下,但是父记录和子记录分别保存的不同的索引记录中。而通过Object fileds和nested fileds构建的关联关系都是在同一条索引记录中。
所以,Join字段构建的父子关联关系更适合保存关联数据比较多的场景。
并且由于父子关联关系都是独立的记录存储,所以可以更方便的对父、子级数据单独进行新增、更新、删除等操作。
缺点主要是has_child 或 has_parent 查询的查询性能会比较差。

注意⚠️:
Join字段不能像关系型数据库中的join使用,在ES中为了保证良好的查询性能,最佳的实践是将数据模型设置为非规范化文档,也就是通过字段冗余构造宽表。
针对每一个join字段,has_child 或 has_parent 查询都会对您的查询性能造成重大影响。

六、Terms lookup跨索引查询

目前,只发现通过Terms lookup可以实现跨索引的关联查询。如果有其他方面,欢迎留言交流。

说明:
Terms lookup查询通过id获取现有文档的字段值,然后使用这些值作为搜索词进行二次查询。

1、创建参数索引,并添加数据

PUT params_index/_doc/1
{
  "group" : "fans",
  "name" : [ 
     "老万", "小明"
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2、创建博客索引,并添加数据

## DELETE blog_index

PUT blog_index
{
  "mappings": {
      "properties": {
        "title":{"type": "keyword"},
        "body":{"type": "keyword"},
        "user_name":{"type": "keyword"}
      }
    }
}

PUT blog_index/_doc/1?refresh
{
  "title": "老万的第一篇博客",
  "body":"开始es学习的第一天……",
  "user_name": "老万"
}

PUT blog_index/_doc/2?refresh
{
  "title": "老万的第二篇博客",
  "body":"学习ES的关联查询……",
  "user_name": "老万"
}

PUT blog_index/_doc/3?refresh
{
  "title": "三亚旅游日记",
  "body":"海边打卡",
  "user_name": "小明"
}

PUT blog_index/_doc/4?refresh
{
  "title": "王者日记",
  "body":"今天5杀上王者",
  "user_name": "小王"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

3、根据参数索引params_index查询blog_index中的记录

GET blog_index/_search
{
  "query": {
    "terms": {
      "user_name" : {
            "index" : "params_index",
            "id" : "1",
            "path" : "name"
        }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

总结

本文主要对ES中关联关系处理方式进行了汇总说明。
主要有如下方式:

  1. 应用层关联
  2. 非规划化数据
  3. 嵌套对象
  4. 父子关系文档
  5. Terms lookup跨索引查询

根据每种方式的优缺点和适用场景,在实际项目中正确选用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/1007905
推荐阅读
相关标签
  

闽ICP备14008679号