当前位置: 首页 > news >正文

Elasticsearch数据建模-关联查询

为什么80%的码农都做不了架构师?>>>   hot3.png

在真实的世界中,关联关系很重要:博客文章有评论,银行账户有交易,客户有银行账户,订单有行项目,目录也拥有文件和子目录。

在关系数据库中,处理关联关系的方式让你不会感到意外:

  • 每个实体(或者行,在关系世界中)可以通过一个主键唯一标识。
  • 实体是规范化了的。对于一个唯一的实体,它的数据仅被存储一次,而与之关联的实体则仅仅保存它的主键。改变一个实体的数据只能发生在一个地方。
  • 在查询期间,实体可以被联接(Join),它让跨实体查询成为可能。
  • 对于单个实体的修改是原子性,一致性,隔离性和持久性的。(参考ACID事务获取更多相关信息。)
  • 绝大多数的关系型数据库都支持针对多个实体的ACID事务。

但是关系型数据库也有它们的局限,除了在全文搜索领域它们拙劣的表现外。在查询期间联接实体是昂贵的 - 联接的实体越多,那么查询的代价就越大。对不同硬件上的实体执行联接操作的代价太大以至于它甚至是不切实际的。这就为在单个服务器上能够存储的数据量设下了一个限制。

ES,像多数NoSQL数据库那样,将世界看作是平的。一个索引就是一系列独立文档的扁平集合。一个单一的文档应该包括用来判断它是否符合一个搜索请求的所有信息。

虽然在ES中改变一份文档的数据是符合ACIDic的,涉及到多份文档的事务就不然了。在ES中,当事务失败后是没有办法将索引回滚到它之前的状态的。

这个扁平化的世界有它的优势:

  • 索引是迅速且不需要上锁的。
  • 搜索是迅速且不需要上锁的。
  • 大规模的数据可以被分布到多个节点上,因为每份文档之间是独立的。

但是关联关系很重要。我们需要以某种方式将扁平化的世界和真实的世界连接起来。在ES中,有4中常用的技术来管理关联数据:

  • 应用端联接(Application-side joins)
  • 数据非规范化(Data denormalization)
  • 嵌套对象(Nested objects)
  • 父子关联关系(Parent/child relationships)

通常最终的解决方案会结合这些方案的几种。

应用端关联(Application-side joins)

我们可以通过在应用中实现联接来(部分)模拟一个关系型数据库。比如,当我们想要索引用户和他们的博客文章时。在关系型的世界中,我们可以这样做:

 

PUT /my_index/user/1  (1)
{
  "name":     "John Smith",
  "email":    "john@smith.com",
  "dob":      "1970/10/24"
}

PUT /my_index/blogpost/2 (2)
{
  "title":    "Relationships",
  "body":     "It's complicated...",
  "user":     1 (3)
}

 

 

(1)(2) 索引,类型以及每份文档的ID一起构成了主键。

(3) 博文通过保存了用户的ID来联接到用户。由于索引和类型是被硬编码到了应用中的,所以这里并不需要。

通过用户ID等于1来找到对应的博文很容易:

GET /my_index/blogpost/_search
{
  "query": {
    "filtered": {
      "filter": {
        "term": { "user": 1 }
      }
    }
  }
}

 

 

为了找到用户John的博文,我们可以执行两条查询:第一条查询用来得到所有名为John的用户的IDs,第二条查询通过这些IDs来得到对应文章:

 

 

GET /my_index/user/_search
{
  "query": {
    "match": {
      "name": "John"
    }
  }
}

GET /my_index/blogpost/_search
{
  "query": {
    "filtered": {
      "filter": {
        "terms": { "user": [1] }  (1)
      }
    }
  }
}

 

 

(1) 传入到terms过滤器的值是第一条查询的结果

 

 

应用端联接最大的优势在于数据是规范化了的。改变用户的名字只需要在一个地方操作:用户对应的文档。劣势在于你需要在搜索期间运行额外的查询来联接文档。

在这个例子中,只有一位用户匹配了第一条查询,但是在实际应用中可能轻易就得到了数以百万计的名为John的用户。将所有的IDs传入到第二个查询中会让该查询非常巨大,它需要执行百万计的term查询。

这种方法在第一个实体的文档数量较小并且它们很少改变时合适(这个例子中实体指的是用户)。这就使得通过缓存结果来避免频繁查询成为可能。

数据非规范化(Denormalizing Your Data)

 

让ES达到最好的搜索性能的方法是采用更直接的办法,通过在索引期间反规范化你的数据。通过在每份文档中包含冗余数据来避免联接。

如果我们需要通过用户的名字来搜索博文,仅需要将作者的名字包含在博文文档中即可。

 

PUT /my_index/user/1
{
  "name":     "John Smith",
  "email":    "john@smith.com",
  "dob":      "1970/10/24"
}

PUT /my_index/blogpost/2
{
  "title":    "Relationships",
  "body":     "It's complicated...",
  "user":     {
    "id":       1,
    "name":     "John Smith" (1)
  }
}

 

 

(1)中用户的部分数据已经非规范化的包含在了博文文档中了。

 

现在我们可以通过一个查询语句来

GET /my_index/blogpost/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title":     "relationships" }},
        { "match": { "user.name": "John"          }}
      ]
    }
  }
}

 

 

查询作者名字中包含john,博文标题包含relationships的博文了。

 

非规范化数据的优势在于查询速度快,以为每个文档中都包含了可以查询的信息,不需要进行额外的联接查询。

嵌套对象(Nested Object)

考虑到在ES里面建立、删除和更新一个单一文本是原子性的,那么僵相关试题保存在同一个文本里面是有意义的。例如,我们可以保存一篇博文以及该博文的评论在一个文档中,博文评论以数组形式提供:

 

PUT /my_index/blogpost/1
{
  "title": "Nest eggs",
  "body":  "Making your money work...",
  "tags":  [ "cash", "shares" ],
  "comments": [  (1)
    {
      "name":    "John Smith",
      "comment": "Great article",
      "age":     28,
      "stars":   4,
      "date":    "2014-09-01"
    },
    {
      "name":    "Alice White",
      "comment": "More like this please",
      "age":     31,
      "stars":   5,
      "date":    "2014-10-22"
    }
  ]
}

因为所有的内容都在同一个文本里面,在查询的时候就没有必要拼接blog,因此检索性能会更好。

父子关联(Parent-Child Relationship)

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/yjwxh/blog/687665

相关文章:

  • Ubuntu/Linux 笔记应用 为知笔记(支持markdown)
  • 使用weave实现跨主机docker容器互联
  • 怎么打开Office 2007 Excel加密文档
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • Unity3D 之3D游戏角色控制器运动
  • linux下smb文件共享服务器详解
  • 几何画板中作函数图像的几种方法
  • HighCharts 详细使用及API文档说明
  • HTML最新标准HTML5小结
  • Docker的镜像、容器和仓库
  • 互联网
  • iOS开发--利用MPMoviePlayerViewController播放视频简单实现
  • Oracle数据库设计第三范式
  • 人月神话阅读笔记之三
  • laravel5入门(三)
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • 【刷算法】求1+2+3+...+n
  • 345-反转字符串中的元音字母
  • CentOS7简单部署NFS
  • co.js - 让异步代码同步化
  • Django 博客开发教程 16 - 统计文章阅读量
  • HashMap ConcurrentHashMap
  • java 多线程基础, 我觉得还是有必要看看的
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JavaScript设计模式系列一:工厂模式
  • MySQL主从复制读写分离及奇怪的问题
  • React-redux的原理以及使用
  • vuex 学习笔记 01
  • 技术胖1-4季视频复习— (看视频笔记)
  • 前端相关框架总和
  • 写代码的正确姿势
  • 新书推荐|Windows黑客编程技术详解
  • 原生JS动态加载JS、CSS文件及代码脚本
  • RDS-Mysql 物理备份恢复到本地数据库上
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • #HarmonyOS:Web组件的使用
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (Python第六天)文件处理
  • (黑马C++)L06 重载与继承
  • (四)Android布局类型(线性布局LinearLayout)
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转载)从 Java 代码到 Java 堆
  • .form文件_SSM框架文件上传篇
  • .NET BackgroundWorker
  • .NET导入Excel数据
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [2669]2-2 Time类的定义
  • [3D游戏开发实践] Cocos Cyberpunk 源码解读-高中低端机性能适配策略
  • [Android Studio 权威教程]断点调试和高级调试
  • [BZOJ1040][P2607][ZJOI2008]骑士[树形DP+基环树]
  • [docker]docker网络-直接路由模式
  • [HAOI2016]食物链