前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Elasticsearch(二) 文档关系分析

Elasticsearch(二) 文档关系分析

作者头像
张凝可
发布2019-08-21 22:33:29
1.1K0
发布2019-08-21 22:33:29
举报
文章被收录于专栏:技术圈技术圈技术圈

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

关系型数据库中往往存在关系,包括1对1,1对多,多对多,通过连接,可以进行多表查询。elasticsearch作为数据存储,搜索,分析的框架,在存储上采用文档式的存储方式,因此再把关系型数据库比如mysql的数据导入elasticsearch时,需要根据数据关系设计索引映射。数据关系,数据特点以及在性能上需求都会影响着我们如何设计索引映射。

对象类型(最擅长处理一对一关系)

将对象作为文档的一个字段值。比如店铺和位置就是一对一的关系,我们可以将位置最为文档的一个字段,而位置可能包括名称,经纬度等字段。

{
    "shop_name":"KFC",
    "location":{
        "location_name":"华润五彩城",
        "geolocation":"51.52,-0.099094"
    }
}

而为了能够通过location_name搜索到这篇文档,文档最终会将location展开,类似于下面这个样子

{
    "shop_name":"KFC",
    "location.location_name":"华润五彩城",
    "location.geolocation":"51.52,-0.099094"
}

因此在检索的时候我们需要在location.location_name上检索

curl 'host:port/index_name/type_name/_search?pretty' -d '
{
    "query":{
        "match":{
            "location.location_name":"五彩城"
        }
    }
}
'

对象类型的映射是自动识别的,另外想假设要将一个对象数组作为文档的字段也是可行的,但是会发生什么样的问题呢?像下面这样的。

{
    "shop_name":"KFC",
    "locations":[{
        "location_name":"KFC华润五彩城店",
        "geolocation":"51.52,-0.099094"
    },
    {
        "location_name":"KFC国贸店",
        "geolocation":"21.52,-0.299094"
    }   
    ]
}

在建立索引的时候是无法区分边界的,也就是说可能会出现跨对象但是满足查询条件的文档出现,比如KFC华润五彩城店会和21.52,-0.299094成为一个新的对象,即便这个对象不存在。这是由于 JSON 格式的文档被处理成如下的扁平式键值对的结构。

{
    "shop_name":"KFC",
    "locations.location_name":["KFC华润五彩城店","KFC国贸店"],
    "locations.geolocation":["51.52,-0.099094","21.52,-0.299094"]
}

而嵌套对象恰恰能解决这类问题,这是因为会将对象数组中的对象分别索引到分隔的文档上。

嵌套类型,父子关系(一对多关系)

嵌套类型需要在索引映射上显示定义

"location": {
          "type" : "nested",
          "properties" : {
            "location_name" : { "type" : "string" },
            "geolocation" : { "type" : "string"}
          }
}

location的type为nested,而对象类型的type为object.

由于嵌套对象 被索引在独立隐藏的文档中,无法直接查询它们。 必须使用 nested 查询去获取它们:

{
       "nested": {
            "path": "comments", 
            "query": {
              “match”:{
                "comment.name":"John"
                }
            }
          }
}

嵌套类型其实是将一对多关系放在一篇文档中,这样做存在优点,也存在缺点,使用者可以根据自己的需求进行选择。

优点:

明确对象数组中对象的边界。

缺点:

一旦子文档发生改变需要重新索引整篇文档

父子关系

父子关系相对于嵌套文档,更为灵活,因为父辈和子辈都是独立的elasticsearch文档,可以自行管理。对于子文档需要在映射中定义_parent字段,在索引的时候需要指定父辈的ID,同样地,父辈的ID和类型会作为子辈的路由值,这在查询的时候非常有益,能够自动地使用这个路由值来查询父辈的分片并获得子辈,或者在查询子辈的分片来获得其父辈。

curl -s -XPOST "$ADDRESS/product" -d@$(dirname $0)/mapping.json
#vim mapping.json
{
  "settings" : {
    "number_of_shards" : 2,
    "number_of_replicas" : 1,
  "mappings" : {
      "spus": {
      "_source" : {
        "enabled" : true
      },
      "_all" : {
        "enabled" : true
      },
      "properties" : {
        .....
      }
    },
    "skus" : {
      "_source" : {
        "enabled" : true
      },
      "_all" : {
        "enabled" : true
      },
      "_parent" : {
        "type" : "spus"
      },
      "properties" : {
        .....
      }
    }
  }
}

这也意味着在查询,更新,删除子文档的时候需要指定_parent字段,比如sku就是spu的子文档。

比如在索引一个子文档时,需要通过_parent字段显示指明父文档ID。

curl -s -XPOST "$ADDRESS/product/skus/100000001?parent=100000" -d'{
  "sku_id": "100000001",
  "is_hidden": false,
  "price": 200.0,
  ....//子文档字段
  "tags":["红色","欧式","皮制"]
}'

父子文档的查询可以独立查询,也可以通过子文档字段查询父文档,或者父文档字段查询所属的子文档。

比如,一个spu对应多个sku,我们可以通过父文档中spu_name=“舒适欧风四人沙发”获得所有sku文档。或者从子文档中sku_tag="红色"的所有父文档。

举个简单明了的例子,比如:

spu_id

spu

sku_id

sku

1

舒适欧风四人沙发

10

红色,欧式

1

舒适欧风四人沙发

11

黄色,欧式

假设我们搜索舒适欧风四人沙发,会返回10和11两个子文档,从父文档到子文档的搜索,而搜索红色会返回1一个父文档。

子文档搜索父文档使用has_child查询,父文档的_score是在子文档的基础上计算上的,可以设置score_mode,比如max, avg等。

curl 'host:port/index_name/spus/_search?size=20&pretty' -d 
'{
          "query":
                {
                   "has_child":
                        {
                           "type": "skus", 
                           "score_mode":"max", 
                           "query":
                           {
                                "tags":"红色"    
                            }
                        }
                }
}'
curl 'host:port/index_name/spus/search?pretty' -d '
{
  "query": {
    "has_child": {
      "type":       "skus",
      "score_mode": "max",
      "query": {
        "match": {
          "tag": "红色"
        }
      }
    }
  }
}'

父文档查询子文档使用has_parent查询

curl 'host:port/index_name/skus/search?pretty' -d '
{
  "query": {
    "has_parent": {
      "type":       "spus",
      "score_mode": "max",
      "query": {
        "match": {
          "sup_name": "舒适欧风四人沙发"
        }
      }
    }
  }
}'

利用父子文档处理1对多关系存在一些优点,比如

  • 更新父文档,不需要重新索引子文档。
  • 创建,修改或者删除子文档时,不影响父文档和其他子文档,在子文档数量较多时尤其适用。
  • 子文档可以单独作为搜索结果返回。

官方文档在使用父子关系时,给出了下面几点建议

  • 尽量少地使用父子关系,仅在子文档远多于父文档时使用。
  • 避免在一个查询中使用多个父子联合语句。
  • 在 has_child 查询中使用 filter 上下文,或者设置 score_mode 为 none 来避免计算文档得分。
  • 保证父 IDs 尽量短,以便在 doc values 中更好地压缩,被临时载入时占用更少的内存。

反规范化,应用层连接(多对多关系)

针对多对多的关系,冗余大量的数据可能会成为比较好的解决方法,将多对多的关系,在其中一个方向上冗余数据从而变成一对多的关系,然后在根据数据特点和对查询性能,索引性能的需求选择嵌套类型或者时父子关系。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年05月17日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档