在传统的数据库里面,对数据关系描述无外乎三种,一对一,一对多和多对多的关系,若是有关联关系的数据,一般咱们在建表的时候会添加主外键来创建数据联系,而后在查询或者统计时候经过join来还原或者补全数据,最终获得咱们须要的结果数据,那么转化到ElasticSearch里面,如何或者怎样来处理这些带有关系的数据。数据库
咱们都知道ElasticSearch是一个NoSQL类型的数据库,自己是弱化了对关系的处理,由于像lucene,es,solr这样的全文检索框架对性能要求都是比较高的,一旦出现join这样的操做,性能会很是差,因此在使用搜索框架时,咱们应该避免把搜索引擎当作关系型数据库用。json
固然,现实数据确定是有关系的,那么在es里面是如何处理和管理这些带有关系的数据呢?api
你们都知道,es天生对json数据支持的很是完美,只要是标准的json结构的数据,不管多么复杂,不管是嵌套多少层,都能存储到es里面,进而可以查询和分析,检索。在这种机制上,es处理和管理关系主要有三种方式:数组
这是es默认的机制,也就是咱们并无设置任何mapping,直接向es服务端插入一条复杂的json数据,也能成功插入,并能支持检索,(能这样操做是由于es默认用的是动态mapping,只要插入的是标准的json结构就会自动转换,固然咱们也能控制mapping类型,es里面有动态mapping和静态maping,静态mapping还分严格类型,弱类型,通常类型,在此再也不展开,有兴趣的能够从官网了解下)以下面一条数据:数据结构
{ "name" : "Zach", "car" : [ { "make" : "Saturn", "model" : "SL" }, { "make" : "Subaru", "model" : "Imprezza" } ] }
最终转化成的存储结构是下面这样的:app
{ "name" : "Zach", "car.make" : ["Saturn", "Subaru"] "car.model" : ["SL", "Imprezza"] }
由于es的底层lucene是天生支持多值域的存储,因此在上面看起来像数组的结构,其实在es里面存储的就是这个字段多值域。框架
而后检索的时候.符号就能检索相对应的内容。这样的一条数据,其实已经包含了数据和关系,看起来像一对多的关系,一我的拥有多辆汽车。但实际上并不能算严格意义上的关系,由于lucene底层是扁平化存储的,这样以来多个汽车的数据实际都是存到一块儿的混杂的,你没办法单独获取到这我的某一辆汽车的数据,由于整条数据都是一个总体,不管什么操做整条数据都会返回。curl
在方案一里面,咱们指出了array存储的数组对象,并非严格意义的关系,由于第二层的数据是没有分离的,若是想要分离,就必须使用nested类型来显式定义数据结构。只有这样,第二层的多个汽车数据才是独立的互不影响,也就是说能够单独获取或查询某一辆汽车的数据。elasticsearch
一样的json数据:ide
{ "name" : "Zach", "car" : [ { "make" : "Saturn", "model" : "SL" }, { "make" : "Subaru", "model" : "Imprezza" } ] }
在方案1里面,最终到es里面会存储一条数据,在第二种类型里面,而若是声明了car类型是nested,那么最终存储到es的数量会显示3,这里解释一下3是怎么来的 = 1个root文档+2个汽车文档,nested声明类型,每个实例都是一个新的document,因此在查询的时候才可以独立进行查询,而且性能还不错,由于es底层会把整条数据存在同一个shard的lucene的sengment里面,缺点是更新的代价比较大,每个子文档的更新都要重建整个结构体的索引,因此nested适合不常常update的嵌套多级关系的场景。
nested类型的数据,须要用其指定的查询和聚合方法才能生效,普通的es查询只能查询1级也就是root级的属性,嵌套的属性是不能查的,若是想要查,必须用嵌套查询或者聚合才行。
嵌套应用有两种模式:
第一种:嵌套查询
每一个查询都是单个文档内生效,包括排序,
第二种:嵌套聚合或者过滤
对同一层级的全部文档都是全局生效,包括过滤排序
parent/children 模式与nested很是相似,可是应用场景侧重点有所不一样。
在使用parent/children管理关联关系时,es会在每一个shard的内存中维护一张关系表,在检索时,经过has_parent和has_child过滤器来获得关联的数据,这种模式下父文档与子文档也是独立的,查询性能会比nested模式稍低,由于父文档和子文档在插入的时候会经过route使得他们都分布在同一个shard里面,但并不保证在同一个lucene的sengment索引段里面,因此检索性能稍低,除此以外,每次检索es都须要从内存的关系表里面获得数据关联的信息,也须要花费必定的时间,相比nested的优点在于,父文档或者子文档的更新,并不影响其余的文档,因此对于更新频繁的多级关系,使用parent/children模式,最为合适不过。
父文档的mapping type:
{ "mappings":{ "person":{ "name":{ "type":"string" } } } }
子文档的mapping type:
{ "homes":{ "_parent":{ "type" : "person" }, "state" : { "type" : "string" } } }
插入数据时,须要先插入父文档:
curl -XPUT localhost:9200/test/person/zach/ -d' { "name" : "Zach" }
而后插入子文档时,须要加上路由字段:
$ curl -XPOST localhost:9200/homes?parent=zach -d' { "state" : "Ohio" } $ curl -XPOST localhost:9200/test/homes?parent=zach -d' { "state" : "South Carolina" }
最终,父文档zach就关联上了两个子文档,在查询时候能够经过parent/children特定查询来获取数据。
总结:
方法一:
(1)简单,快速,性能较高
(2)对维护一对一的关系比较擅长
(3)不须要特殊的查询
方法二:
(1)因为底层存储在同一个lucene的sengment里,因此读取和查询性能对比方法三更快
(2)更新单个子文档,会重建整个数据结构,因此不适合更新频繁的嵌套场景
(3)能够维护一对多和多对多的存储关系
方法三:
(1)多个关系数据,存储彻底独立,可是存在同一个shard里面,因此读取和查询性能比方法二稍低
(2)须要额外的内存,维护管理关系列表
(3)更新文档不影响其余的子文档,因此适合更新频繁的场景
(4)排序和评分操做比较麻烦,须要额外的脚本函数支持
参考文档:
https://www.elastic.co/blog/managing-relations-inside-elasticsearch