es联查方案汇总

一、方案汇总

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

1.应用层关联

2.非规划化数据

3.嵌套对象

4.父子关系文档

5.Terms lookup跨索引查询

二、具体方案解析

1.应用层关联

实际上通过多次查询实现数据的关联查询。在第一个索引中查询出结果集,然后将结果集作为查询条件在第二个条件中查询

2.非规范化数据Object fileds

为了获得较好的检索性能,最好的方法是在索引建模时进行非规范化数据存储,通过对文档数据字段的冗余保存避免访问时进行关联查询。

比如下面的例子,希望通过用户姓名找到他写的博客文章。

常规的方法索引结构如下,在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"}
      }
    }
}

非规范化数据处理:

说明:

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

PUT blog_index
{
  "mappings": {
      "properties": {
        "title":{"type": "keyword"},
        "body":{"type": "keyword"},
        "user":{
          "properties": {
            "id":  {"type": "keyword"},
            "name":   {"type": "keyword"},
            "email":   {"type": "keyword"}
          }
        }
      }
    }
}

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

GET blog_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "term": { "user.name": "老万"}}
      ]
    }
  }
}

优缺点分析:

数据非规范化的优点是速度快。因为每个文档都包含了所需的所有信息,当这些信息需要在查询进行匹配时,并不需要进行昂贵的联接操作。

缺点是由于对大量数据进行了冗余存储,会占用更大的存储空间,且对关联数据的更新操作会更复杂。

3.嵌套对象nested fileds

通过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" }} 
          ]
        }
      }
    }
  }
}

优缺点分析:

Object fileds 适合一对一的关联关系

nested fileds 适合一对多的关联关系。

两者都是通过非规范化数据,利用数据的冗余保存来避免关联查询。

无论是Object fileds 还是nested fileds ,都是在同一条记录中保存数据的关联关系。

4.父子关系文档

通过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" 
          }
        }
      }
    }
}

优缺点分析:

通过Join字段构建的父子关联关系,数据保存在同一索引的相同分片下,但是父记录和子记录分别保存的不同的索引记录中。而通过Object fileds和nested fileds构建的关联关系都是在同一条索引记录中。

所以,Join字段构建的父子关联关系更适合保存关联数据比较多的场景。

并且由于父子关联关系都是独立的记录存储,所以可以更方便的对父、子级数据单独进行新增、更新、删除等操作。

缺点主要是has_child 或 has_parent 查询的查询性能会比较差。

注意⚠️:

Join字段不能像关系型数据库中的join使用,在ES中为了保证良好的查询性能,最佳的实践是将数据模型设置为非规范化文档,也就是通过字段冗余构造宽表。

针对每一个join字段,has_child 或 has_parent 查询都会对您的查询性能造成重大影响。

5.Terms lookup跨索引查询

①什么是 Terms lookup?

Terms lookup 将获取现有文档的字段值。 然后,Elasticsearch 将这些值用作搜索词。 搜索大量术语时,这将很有帮助。 由于术语查找从文档中获取值,因此必须启用 _source映射字段以使用术语查找。 _source 字段默认情况下处于启用状态。

注意:默认情况下,Elasticsearch 将字词查询限制为最多 65,536 个字词。 这包括使用术语查找获取的术语。 你可以使用 index.max_terms_count 设置更改此限制。

②要执行术语查找,请使用以下参数

  • index:(必需,字符串)从中获取字段值的索引的名称。
  • id:(必需,字符串)要从中获取字段值的文档的ID。
  • path:(必需,字符串)要从中获取字段值的字段名称。 Elasticsearch 使用这些值作为查询的搜索词。 如果字段值包含嵌套的内部对象的数组,则可以使用点表示法语法访问这些对象。
  • routing:(可选,字符串)从中获取术语值的文档的自定义路由值。 如果在为文档建立索引时提供了自定义路由值,则此参数是必需的。

③Terms lookup 例子

若要查看术语查找的工作原理,请尝试以下示例。

我们按照如下的方法来创建两个不同的索引:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "color": { "type": "keyword" }
    }
  }
}
 
PUT my-index-000002
{
  "mappings": {
    "properties": {
      "favorite_color": { "type": "keyword" }
    }
  }
}

我们使用如下的方法来创建上面两个索引的内容:

POST _bulk
{ "index" : { "_index" : "my-index-000001", "_id" : "1" } }
{ "color" : ["blue", "green"] }
{ "index" : { "_index" : "my-index-000001", "_id" : "2" } }
{ "color" : ["blue"] }
{ "index" : { "_index" : "my-index-000002", "_id" : "1" } }
{ "favorite_color" : "blue" }

在上面,我们为 my-index-000001 索引创建了两个文档,为 my-index-000002 索引创建了一个文档。

按照正常的搜索,我们想搜索 my-index-000001 中 color 为 blue 的所有文档,那么我可以使用如下的命令:

GET my-index-000001/_search
{
  "query": {
    "match": {
      "color": "blue"
    }
  }
}

GET my-index-000001/_search
{
  "query": {
    "term": {
      "color": {
        "value": "blue"
      }
    }
  }
}

在上面,我们使用了一个固定的 blue 关键字在搜索的命令中。假如有一种情况是,我的这个 blue 不是硬编码,而是需要动态地变化。它可以从另外一个索引中搜索到,那么我们该怎么进行这个搜索呢?我们可以使用 terms lookup query 来实现这个。它的写法是这样的:

GET my-index-000001/_search
{
  "query": {
    "terms": {
      "color": {
        "index": "my-index-000002",
        "id": "1",
        "path": "favorite_color"
      }
    }
  }
}

在上面,我们使用了 my-index-000002 索引来搜索,查询 id 为 “1” 的文档,并使用 favorite_color 来作为 path。我们知道在这个 favorite_color,id 为 "1" 的文档中,它的值是 blue,也即我们使用 blue 来进行查询。

④局限性

查询的时候只支持id,对应的是索引中的_id,而且id的值只能是一个,别的条件是不支持的,所以为了使用联查,需要额外维护一张表,表的id是你期望的作为查询条件的值,再维护一个其他字段对应path里的字段,这样就能实现联查

⑤java实现

BoolQueryBuilder boolQuery = new BoolQueryBuilder();
String index = "user_skus";
String id = "100000";
String path = "skus";
boolQuery.filter(QueryBuilders.termsLookupQuery("skuCode", new TermsLookup(index, id, path)));

https://developer.aliyun.com/article/1054493

https://blog.csdn.net/UbuntuTouch/article/details/112857984

https://www.cnblogs.com/frozenfire/p/16739192.html

posted @ 2023-01-14 13:55  未月廿三  阅读(2136)  评论(0编辑  收藏  举报