赞
踩
关系型数据库中的范式化:
反范式化:
ElasticSearch就是使用的反范式。ES并不擅长处理关联关系,如果出现了一般会采取以下几种方式:
文档中使用一个字段,该字段保存的值value是一个object。这种方式不适用于 value为对象数组的场景,查询会不准确。
如下所示,案例一是适用场景,案例二为不适用场景
博客作者信息变更
对象类型:
DELETE /blog # 创建一个blog索引,其中的user字段保存的是一个object PUT /blog { "mappings": { "properties": { "content": { "type": "text" }, "create_time": { "type": "date", "format": ["yyyy-MM-dd HH:mm:ss"] }, "user": { # user字段中保存的是一个对象,通过properties关键字定义对象中的字段 "properties": { "userid": { "type": "long" }, "username": { "type": "text" }, "age": { "type": "long" } } } } } } # 插入一条 blog信息 PUT /blog/_doc/1 { "content": "I like Elasticsearch", "create_time": "2024-01-01 00:00:00", "user": { # user字段中保存的是一个对象 "userid": 1, "username": "hushang", "age": 24 } } # 查询,可以使用使用user.field 对象内的字段查询 GET /blog/_search { "query": { "bool": { "must": [ { "match": { "content": "Elasticsearch" } }, { "match": { "user.username": "hushang" } } ] } } }
user字段如果保存的是一个对象数组,在搜索时添加两个查询条件,数组中两个对象分别满足一个条件。
我使用bool must 关键字,表示两个查询条件都需要满足,才会显示文档。但是上方中数组中两个对象分别满足一个条件 这也查询出来了。
DELETE /blog # 创建一个blog索引,其中的user字段保存的是一个object PUT /blog { "mappings": { "properties": { "content": { "type": "text" }, "user": { # user字段保存 姓 和 名 两个字段 "properties": { "first_name": { "type": "text" }, "last_name": { "type": "text" } } } } } } # 写入一条数据 张三和李四 PUT /blog/_doc/1 { "content": "speed", "user": [ { "first_name":"zhang", "last_name":"san" }, { "first_name":"li", "last_name":"si" } ] } # 查询,此时我的性查询的是zhang 名查询的是si 而且还是采用的bool must方式。我期望的结果是应该查询不到数据 # 但实际上此时能查询到数据 GET /blog/_search { "query": { "bool": { "must": [ { "match": { "user.first_name": "zhang" } }, { "match": { "user.last_name": "si" } } ] } } }
造成这种情况的原因是
ES在存储文档数据时,内部对象的边界并没有考虑在内,JSON格式被处理成扁平式键值对结构。当对多个字段进行查询时,导致了意外结果。
"content":"speed"
"user".first_name: ["zhang","li"]
"user".last_name: ["san","si"]
可以使用nested data type
查询解决这个问题
nested数据类型,它允许对象数组中的对象被独立索引
使用nested和properties关键字,将上方案例中所有user索引到多个分隔的文档。
在内部,Nested文档会被保存在两个Lucene文档中,在查询时做join处理
DELETE /blog # 创建一个blog索引,其中的user字段保存的是一个object # 并且使用了type:nested PUT /blog { "mappings": { "properties": { "content": { "type": "text" }, "user": { "type": "nested", # 使用了type:nested "properties": { "first_name": { "type": "keyword" }, "last_name": { "type": "keyword" } } } } } } # 写入一条数据 张三和李四 PUT /blog/_doc/1 { "content": "speed", "user": [ { "first_name":"zhang", "last_name":"san" }, { "first_name":"li", "last_name":"si" } ] } # 查询 GET /blog/_search { "query": { "nested": { # 使用nested关键字 "path": "user", # user字段是nested类型 这里指定nestred类型的字段,并且下面就是对这个字段进行查询 "query": { # 之后就是正常的查询query语句 "bool": { "must": [ { "match": { "user.first_name": "zhang" } }, { "match": { "user.last_name": "si" } } ] } } } } }
nested类型的字段,直接进行aggs聚合操作是没有数据的
# user字段是nested类型 直接聚合操作是没有数据的 GET /blog/_search { "size": 0, "aggs": { "hs_first_name": { "terms": { "field": "user.first_name" } } } } # 需要添加nestred关键字,并指定user这个字段 GET /blog/_search { "size": 0, "aggs": { "hs_agg": { "nested": { # 添加nestred关键字 "path": "user" }, "aggs": { # 再进行聚合操作 "hs_first_name": { "terms": { "field": "user.first_name" } } } } } }
使用ES时,大部分的场景都不会频繁更新操作,父子关联关系了解即可。
再更新操作时,对象类型和嵌套对象nested方式有一个问题,因为根对象和嵌套对象本质上它们还是存在一个文档中的,每次更新时就可以需要重新索引整个文档。
ES提供了父子关联关系,通过维护parent/child的关系,分离它们,使父文档和子文档是两个独立的文档,更新其中一个文档不会影响另一个文档。
设定 Parent/Child Mapping
# 设定 Parent/Child Mapping映射关系 # 指定我们定义的hs_blog_comments_relation字段类型为join # 并在relations下指定两个关联的自定义字符串值 其中hs_blog为parent名称,hs_comment为child名称 PUT /my_blogs { "settings": { "number_of_shards": 2 }, "mappings": { "properties": { "hs_blog_comments_relation": { "type": "join", "relations": { "hs_blog": "hs_comment" } }, "content": { "type": "text" }, "title": { "type": "keyword" } } } }
索引父文档
# 索引两个父文档 ,指定文档id为blog1 和 blog2 # 同时指定文档的类型为hs_blog父文档 PUT /my_blogs/_doc/blog1 { "content": "learning ELK ", "title": "Learning Elasticsearch", "hs_blog_comments_relation": { "name": "hs_blog" } } PUT /my_blogs/_doc/blog2 { "content": "learning Hadoop ", "title": "Learning Hadoop", "hs_blog_comments_relation": { "name": "hs_blog" } }
索引子文档
创建子文档时,必须通过routing指定父文档id,保证父子文档在一个shard中,提高join查询性能。
当指定子文档时,必须指定父文档id
# 索引三个子文档,指定文档id、同时指定routing 让父子文档在相同的shard中 # 指定文档的类型为子文档,同时必须指定它的父文档id PUT /my_blogs/_doc/comment1?routing=blog1 { "comment":"I am learning ELK", "username":"Jack", "hs_blog_comments_relation": { "name": "hs_comment", "parent": "blog1" } } PUT /my_blogs/_doc/comment2?routing=blog2 { "comment":"I like Hadoop!!!!!", "username":"Jali", "hs_blog_comments_relation": { "name": "hs_comment", "parent": "blog2" } } PUT /my_blogs/_doc/comment3?routing=blog2 { "comment":"Hello Hadoop", "username":"Bob", "hs_blog_comments_relation": { "name": "hs_comment", "parent": "blog2" } }
测试查询
# 查询所有文档,就是正常的查询,能查询到5个文档,因为父子文档都是独立的文档 POST /my_blogs/_search #根据父文档ID查看,也就是正常的查询 GET /my_blogs/_doc/blog2 # has_child 查询,返回这个子文档对应的父文档 GET /my_blogs/_search { "query": { "has_child": { "type": "hs_comment", "query": { "match": { "username": "Jack" } } } } } # has_parent 查询,返回相关的子文档 GET /my_blogs/_search { "query": { "has_parent": { "parent_type": "hs_blog", "query": { "match": { "content": "ELK" } } } } }
GET /my_blogs/_search #通过ID ,访问子文档,会发现查询不到数据,返回404 # 但是通过上方直接查询全部是能查询到comment3这个文档的 GET /my_blogs/_doc/comment3 # 通过query语句能查询到到这个子文档 GET /my_blogs/_search { "query": { "match": { "comment": "Hello" } } } #通过ID和routing ,这种方式也能查询到这个子文档 GET /my_blogs/_doc/comment3?routing=blog2 #更新子文档,因为使用的是PUT 全量更新,所以在文档中还是需要指定子文档类型和父文档id PUT /my_blogs/_doc/comment3?routing=blog2 { "comment": "Hello Hadoop??", "blog_comments_relation": { "name": "hs_comment", "parent": "blog2" } }
Nested Object | Parent / Child | |
---|---|---|
优点 | 文档存储在一起,读取性能高 | 父子文档可以独立更新 |
缺点 | 更新嵌套的子文档时,需要更新整个文档 | 需要额外的内存维护关系。读取性能相对差 |
适用场景 | 子文档偶尔更新,以查询为主 | 子文档更新频繁 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。