赞
踩
本节主要介绍在ES中关联关系的处理方式。
根据《Elasticsearch权威指南》以及官网中的介绍,ES针对关联关系的处理主要有如下方式:
Join、Nested、Object、Flattened字段类型对比
对索引数据不进行特殊处理,而是在应用程序中通过多次查询实现数据的关联查询。
例如,比方下面的例子,一个问题会有多个答案,且问题数据和答案数据在不同的索引中。
1、创建问题索引question_index
PUT question_index { "mappings": { "properties": { "id":{"type": "keyword"}, "text":{"type": "keyword"} } } } PUT question_index/_doc/1?refresh { "id":"1", "text": "我是第一个问题" } PUT question_index/_doc/2?refresh { "id":"2", "text": "我是第二个问题" }
2、创建答案索引question_index
说明:其中pid是问题id。
PUT answer_index { "mappings": { "properties": { "pid":{"type": "keyword"}, "text":{"type": "keyword"} } } } PUT answer_index/_doc/1?refresh { "pid":"1", "text": "问题一的答案1" } PUT answer_index/_doc/2?refresh { "pid":"1", "text": "问题一的答案2" }
3、业务场景
现在需要查询第一个问题的对应答案信息,我们可以这样做:
首先,根据问题名称查出对应记录的id。
GET question_index/_search
{
"query": {
"term": {
"text": {
"value": "我是第一个问题"
}
}
}
}
然后,将第一个查询得到的结果将填充到 terms 过滤器中,从answer_index中查询出答案数据。
GET answer_index/_search
{
"query": {
"terms": {
"pid": [
"1"
]
}
}
}
优缺点分析:
采用应用层关联的主要优点是简单,不需要对数据结构做额外的处理,可以对任意两个不同的索引进行关联查询。
缺点是必须进行多次查询。
适用场景:
应用层关联适用于关联数据较少的情况,原因是terms对大量数据的进行多值匹配查询性能会比较差。
使用terms可以进行多值查询, 只要目标文档匹配terms查询中的一个值, 此文档就会被标记为查询结果中的一个, 但terms的参数值是有限制的, 默认65535个元素, 你可以通过设置index.max_terms_count来进行更改。
还可以通过terms lookup语法来解决terms参数元素过多的情况。
为了获得较好的检索性能,最好的方法是在索引建模时进行非规范化数据存储,通过对文档数据字段的冗余保存避免访问时进行关联查询。
比如下面的例子,希望通过用户姓名找到他写的博客文章。
常规的方法索引结构如下,在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": "老万"}}
]
}
}
}
优缺点分析:
数据非规范化的优点是速度快。因为每个文档都包含了所需的所有信息,当这些信息需要在查询进行匹配时,并不需要进行昂贵的联接操作。
缺点是由于对大量数据进行了冗余存储,会占用更大的存储空间,且对关联数据的更新操作会更复杂。
通过nested构建嵌套数据类型,也可以实现数据的关联关系。
在上面的非规范化数据中,已经演示了通过Object字段类型来冗余保存关联数据避免数据关联查询。
那么两者有什么区别呢?
官方说明: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 ,都是在同一条记录中保存数据的关联关系。
通过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 查询都会对您的查询性能造成重大影响。
目前,只发现通过Terms lookup可以实现跨索引的关联查询。如果有其他方面,欢迎留言交流。
说明:
Terms lookup查询通过id获取现有文档的字段值,然后使用这些值作为搜索词进行二次查询。
1、创建参数索引,并添加数据
PUT params_index/_doc/1
{
"group" : "fans",
"name" : [
"老万", "小明"
]
}
2、创建博客索引,并添加数据
## DELETE blog_index PUT blog_index { "mappings": { "properties": { "title":{"type": "keyword"}, "body":{"type": "keyword"}, "user_name":{"type": "keyword"} } } } PUT blog_index/_doc/1?refresh { "title": "老万的第一篇博客", "body":"开始es学习的第一天……", "user_name": "老万" } PUT blog_index/_doc/2?refresh { "title": "老万的第二篇博客", "body":"学习ES的关联查询……", "user_name": "老万" } PUT blog_index/_doc/3?refresh { "title": "三亚旅游日记", "body":"海边打卡", "user_name": "小明" } PUT blog_index/_doc/4?refresh { "title": "王者日记", "body":"今天5杀上王者", "user_name": "小王" }
3、根据参数索引params_index查询blog_index中的记录
GET blog_index/_search
{
"query": {
"terms": {
"user_name" : {
"index" : "params_index",
"id" : "1",
"path" : "name"
}
}
}
}
本文主要对ES中关联关系处理方式进行了汇总说明。
主要有如下方式:
根据每种方式的优缺点和适用场景,在实际项目中正确选用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。