es联查方案汇总
一、方案汇总
根据《Elasticsearch权威指南》以及官网中的介绍,ES针对关联关系的处理主要有如下方式:
1.应用层关联
2.非规划化数据
3.嵌套对象
4.父子关系文档
5.Terms lookup跨索引查询
二、具体方案解析
1.应用层关联
实际上通过多次查询实现数据的关联查询。在第一个索引中查询出结果集,然后将结果集作为查询条件在第二个条件中查询
2.非规范化数据Object fileds
为了获得较好的检索性能,最好的方法是在索引建模时进行非规范化数据存储,通过对文档数据字段的冗余保存避免访问时进行关联查询。
比如下面的例子,希望通过用户姓名找到他写的博客文章。
常规的方法索引结构如下,在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": "老万"}}
]
}
}
}
优缺点分析:
数据非规范化的优点是速度快。因为每个文档都包含了所需的所有信息,当这些信息需要在查询进行匹配时,并不需要进行昂贵的联接操作。
缺点是由于对大量数据进行了冗余存储,会占用更大的存储空间,且对关联数据的更新操作会更复杂。
3.嵌套对象nested fileds
通过nested构建嵌套数据类型,也可以实现数据的关联关系。
在上面的非规范化数据中,已经演示了通过Object字段类型来冗余保存关联数据避免数据关联查询。
那么两者有什么区别呢?
Object fileds 和nested fileds的区别:
官方说明: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 ,都是在同一条记录中保存数据的关联关系。
4.父子关系文档
通过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 查询都会对您的查询性能造成重大影响。
5.Terms lookup跨索引查询
①什么是 Terms lookup?
Terms lookup 将获取现有文档的字段值。 然后,Elasticsearch 将这些值用作搜索词。 搜索大量术语时,这将很有帮助。 由于术语查找从文档中获取值,因此必须启用 _source映射字段以使用术语查找。 _source 字段默认情况下处于启用状态。
注意:默认情况下,Elasticsearch 将字词查询限制为最多 65,536 个字词。 这包括使用术语查找获取的术语。 你可以使用 index.max_terms_count 设置更改此限制。
②要执行术语查找,请使用以下参数
- index:(必需,字符串)从中获取字段值的索引的名称。
- id:(必需,字符串)要从中获取字段值的文档的ID。
- path:(必需,字符串)要从中获取字段值的字段名称。 Elasticsearch 使用这些值作为查询的搜索词。 如果字段值包含嵌套的内部对象的数组,则可以使用点表示法语法访问这些对象。
- routing:(可选,字符串)从中获取术语值的文档的自定义路由值。 如果在为文档建立索引时提供了自定义路由值,则此参数是必需的。
③Terms lookup 例子
若要查看术语查找的工作原理,请尝试以下示例。
我们按照如下的方法来创建两个不同的索引:
PUT my-index-000001
{
"mappings": {
"properties": {
"color": { "type": "keyword" }
}
}
}
PUT my-index-000002
{
"mappings": {
"properties": {
"favorite_color": { "type": "keyword" }
}
}
}
我们使用如下的方法来创建上面两个索引的内容:
POST _bulk
{ "index" : { "_index" : "my-index-000001", "_id" : "1" } }
{ "color" : ["blue", "green"] }
{ "index" : { "_index" : "my-index-000001", "_id" : "2" } }
{ "color" : ["blue"] }
{ "index" : { "_index" : "my-index-000002", "_id" : "1" } }
{ "favorite_color" : "blue" }
在上面,我们为 my-index-000001 索引创建了两个文档,为 my-index-000002 索引创建了一个文档。
按照正常的搜索,我们想搜索 my-index-000001 中 color 为 blue 的所有文档,那么我可以使用如下的命令:
GET my-index-000001/_search
{
"query": {
"match": {
"color": "blue"
}
}
}
GET my-index-000001/_search
{
"query": {
"term": {
"color": {
"value": "blue"
}
}
}
}
在上面,我们使用了一个固定的 blue 关键字在搜索的命令中。假如有一种情况是,我的这个 blue 不是硬编码,而是需要动态地变化。它可以从另外一个索引中搜索到,那么我们该怎么进行这个搜索呢?我们可以使用 terms lookup query 来实现这个。它的写法是这样的:
GET my-index-000001/_search
{
"query": {
"terms": {
"color": {
"index": "my-index-000002",
"id": "1",
"path": "favorite_color"
}
}
}
}
在上面,我们使用了 my-index-000002 索引来搜索,查询 id 为 “1” 的文档,并使用 favorite_color 来作为 path。我们知道在这个 favorite_color,id 为 "1" 的文档中,它的值是 blue,也即我们使用 blue 来进行查询。
④局限性
查询的时候只支持id,对应的是索引中的_id,而且id的值只能是一个,别的条件是不支持的,所以为了使用联查,需要额外维护一张表,表的id是你期望的作为查询条件的值,再维护一个其他字段对应path里的字段,这样就能实现联查
⑤java实现
BoolQueryBuilder boolQuery = new BoolQueryBuilder();
String index = "user_skus";
String id = "100000";
String path = "skus";
boolQuery.filter(QueryBuilders.termsLookupQuery("skuCode", new TermsLookup(index, id, path)));
https://developer.aliyun.com/article/1054493