Elasticsearch 关联关系处理指南

Elasticsearch处理关联关系的四种方法:应用层关联(简单但性能低)、数据扁平化(查询高效但冗余)、嵌套对象(精确查询数组对象)、父子关系(双向查询但性能开销大)。选择取决于数据更新频率和查询需求。

作品集: Elastic Stack
作者头像
LumiBee
15 天前 · 84 0
分享

Elasticsearch 关联关系处理指南

当我们将数据从 MySQL 等关系型数据库迁移到 Elasticsearch 时,遇到的第一个挑战往往就是如何处理表与表之间的关联关系。Elasticsearch 的核心优势在于其强大的全文搜索和分析能力,而这背后是它“非规范化”的数据结构设计。它并不擅长像 SQL 那样进行实时的 JOIN 操作。

为了在 Elasticsearch 中高效地处理关联数据,我们需要根据具体的业务场景,选择最合适的数据建模策略。本指南将详细介绍四种主流的方法:应用层关联(Application-side Joins)数据扁平化 (Denormalization)嵌套对象 (Nested Objects)父子关系 (Parent-Join)

1. 应用层关联 (Application-side Joins)

这是最简单、最直观的方法,它将关联的逻辑完全放在应用程序代码中处理。

  • 工作原理:
    1. 应用先向 Elasticsearch 发送一个请求,查询主数据(例如,根据关键词搜索博客文章)。
    2. 从返回结果中,提取出需要关联的 ID(例如,每篇文章的 author_id)。
    3. 应用再向 Elasticsearch (或 MySQL) 发送第二个请求,根据这些 ID 查询关联的数据(例如,获取作者的详细信息)。
    4. 最后,在应用代码中将这两次查询的结果合并,然后展示给前端。
  • 优点:
    • 实现简单:不需要对 Elasticsearch 的数据结构做任何特殊处理,保持了数据的独立性。
    • 数据无冗余:每份数据只存储一次,更新方便。
  • 缺点:
    • 性能开销大:至少需要两次网络往返,查询延迟较高,不适合高性能场景。
    • 无法在一次查询中完成复杂过滤:例如,您无法在一次查询中实现“搜索内容包含‘Elasticsearch’并且作者年龄大于30岁的文章”,因为作者年龄信息不在文章索引中。
  • 适用场景:
    • 对查询性能要求不高的内部后台系统。
    • 关联数据的查询频率非常低。

2. 数据扁平化/反规范化 (Denormalization)

这是在 Elasticsearch 中最常用、最推荐的方法,也是最符合其设计理念的策略。

  • 工作原理:
    在索引数据时,直接将需要关联的数据冗余一份,合并到一个大的文档中。以博客文章为例,不再只存一个 author_id,而是将作者的 name, avatar 等信息也一并存入文章文档中。

  • 示例:

    // 在索引一篇博客文章时,直接包含作者信息  
    POST /blog_posts/_doc/1  
    {  
      "title": "Elasticsearch 关联关系指南",  
      "content": "...",  
      "tags": ["Elasticsearch", "Data Modeling"],  
      "author": { // 直接嵌入作者信息  
        "id": 101,  
        "name": "李四",  
        "avatar_url": "http://example.com/avatar/lisi.jpg"  
      },  
      "publish_date": "2025-07-06T10:00:00Z"  
    }
    
  • 优点:

    • 查询性能极高:所有需要的数据都在一个文档里,只需一次查询即可获取全部信息,完美发挥 ES 的搜索优势。

    • 查询逻辑简单:可以轻松实现复杂的过滤,例如“搜索内容包含‘Elasticsearch’并且作者名是‘李四’的文章”。

      GET /blog_posts/_search  
      {  
        "query": {  
          "bool": {  
            "must": [  
              { "match": { "content": "Elasticsearch" } },  
              { "match": { "author.name": "李四" } }  
            ]  
          }  
        }  
      }
      
  • 缺点:

    • 数据冗余:同一份作者数据(如姓名)会存在于他发表的所有文章文档中。
    • 更新复杂:如果一位作者改了名字,您必须更新他名下所有的文章文档,这会产生额外的开销。
  • 适用场景:

    • 绝大多数的搜索场景。特别是当关联数据的更新频率远低于查询频率时(例如,作者信息很少改变,但文章被搜索的次数非常多)。

3. 嵌套对象 (Nested Objects)

当关联关系是一对多,并且这些“多”的数据需要被精确地作为一个整体来查询时,nested 类型是最佳选择。

  • 工作原理: nested 类型允许将一个对象数组存储在主文档中,并且 Elasticsearch 会将这些对象作为内部独立的文档来索引。这可以防止数组内对象字段之间的关联性丢失。

  • 示例: 博客文章和它的评论列表。我们希望能够精确地查询“由‘王五’发表并且点赞数大于10的评论”。

    // 创建索引时,将 comments 字段定义为 nested 类型  
    PUT /blog_posts_with_comments  
    {  
      "mappings": {  
        "properties": {  
          "comments": {  
            "type": "nested", // 关键在这里  
            "properties": {  
              "username": { "type": "keyword" },  
              "comment_text": { "type": "text" },  
              "votes": { "type": "integer" }  
            }  
          }  
        }  
      }  
    }
    
    // 查询  
    GET /blog_posts_with_comments/_search  
    {  
      "query": {  
        "nested": {  
          "path": "comments",  
          "query": {  
            "bool": {  
              "must": [  
                { "match": { "comments.username": "王五" } },  
                { "range": { "comments.votes": { "gt": 10 } } }  
              ]  
            }  
          }  
        }  
      }  
    }
    

    如果没有使用 nested,ES 会将 comments 数组“压平”,您可能会错误地匹配到“由‘赵六’发表且点赞数大于10的评论”,因为无法保证 username 和 votes 来自同一条评论对象。

  • 优点:

    • 能够对数组内的对象进行精确的、原子性的查询。
    • 数据在物理上依然存储在同一个文档中,查询性能较好。
  • 缺点:

    • nested 对象在内部有额外的性能开销。
    • 默认有数量限制,不适合存储无限增长的数据(如大量的事件日志)。
  • 适用场景:

    • 需要精确查询对象数组的场景,如商品和它的多个 SKU 属性、文章和它的评论列表(当评论数量可控时)。

4. 父子关系 (Parent-Join)

这是 Elasticsearch 提供的最接近传统数据库 JOIN 的功能,它在索引内部维护了明确的父子文档关系。

  • 工作原理:

    1. 在创建索引时,定义一个 join 类型的字段,并声明父子关系(例如,question 是父,answer 是子)。
    2. 索引数据时,子文档必须指定它的父文档 ID。
    3. 查询时,可以使用 has_parent(根据父文档查询子文档)或 has_child(根据子文档查询父文档)来进行关联查询。
  • 示例: 论坛的“问题”与“回答”。

    // 创建索引  
    PUT /qa_forum  
    {  
      "mappings": {  
        "properties": {  
          "qa_relation": {  
            "type": "join",  
            "relations": {  
              "question": "answer" // 定义 question 是 answer 的父  
            }  
          }  
        }  
      }  
    }
    
    // 查询:找到所有包含“Elasticsearch”这个词的回答,并返回它们所属的问题  
    GET /qa_forum/_search  
    {  
      "query": {  
        "has_child": {  
          "type": "answer",  
          "query": {  
            "match": {  
              "content": "Elasticsearch"  
            }  
          }  
        }  
      }  
    }
    
  • 优点:

    • 数据完全分离,更新父或子文档不会相互影响。
    • 可以实现真正的父子关联查询。
  • 缺点:

    • 性能开销是所有方法中最大的
    • 父子文档必须位于同一个分片上,这在索引数据时需要额外处理(通过路由)。
    • 查询语法相对复杂。
  • 适用场景:

    • 关联数据的更新非常频繁。
    • 父子关系非常明确,且确实需要在两者之间进行双向关联查询。

如何选择?

场景描述 推荐方法 核心原因
我的首要目标是搜索性能,关联数据不常变 数据扁平化 (Denormalization) 查询最快,最符合 ES 理念
我想对一个对象列表进行精确的组合查询 嵌套对象 (Nested Objects) 保证数组内对象字段的关联性
我的关联数据更新非常频繁,且需要双向查询 父子关系 (Parent-Join) 数据独立,避免更新风暴
我只是偶尔需要关联一下数据,性能要求不高 应用层关联 实现最简单,无需特殊建模

对于博客/论坛项目,毫无疑问,数据扁平化是构建搜索引擎的最佳选择;而如果要实现文章下的评论功能,并且希望对评论进行精确搜索,那么可以考虑为评论字段使用嵌套对象类型。

阅读量: 84

评论区

登录后发表评论

正在加载评论...
相关阅读

Elasticsearch 入门指南

# Elasticsearch 入门指南:从零到一构建搜索引擎 在成功部署了 Elasticsearch 和 Kibana 后,如果还有不会安装的可以查看这篇[文章](https://hive...

164
1

Elasticsearch 相关性控制的艺术

# Elasticsearch 相关性控制的艺术 我们可以通过控制相关性的得分使得用户最想要的结果排在最前面。本文所举的例子依旧是建设博客的搜索引擎。 ## 1. boosting 查询 ...

104
1

向量检索深入学习

# Elasticsearch 向量检索深入学习 传统的搜索引擎是“字面匹配”的专家,但它们很难理解文字背后的“真实含义”。例如,搜索“苹果手机”,它可能不会返回一篇只写了“iPhone”的文...

82
1

Gemini for Google Workspace 提示工程指南101 报告

# **Gemini for Google Workspace 提示工程指南101 报告** ## **引言** Google Workspace 最初的设计理念便是促进人与人之间的实时协作...

238
2

Redis01_NoSQL和Redis概述

# NoSQL ## 概述 ​ NoSQL("Not Only SQL")是一类非关系型数据库的统称,主要用于存储,管理和查询非结构化或半结构化数据.与传统的关系型数据库(如MySQL, P...

113
0

MyBatis-Plus讲解

# 介绍 ​ [MyBatis-Plus](https://github.com/baomidou/mybatis-plus) 是一个 [MyBatis](https://www.mybati...

139
0