4. 元数据存储与索引:关系型、图数据库与搜索引擎

元数据存哪儿、怎么查得快,这问题我琢磨了好几年。说实话,没有一种存储能包打天下。关系型、图数据库、搜索引擎,各有各的脾气。今天咱们就掰开揉碎了聊聊,这三种存储到底该怎么选、怎么搭。

核心观点:元数据存储没有银弹。关系型扛事务,图数据库管关系,搜索引擎做全文检索。三驾马车各司其职,才是成熟架构。

4.1 关系型存储:MySQL vs PostgreSQL

关系型数据库是元数据治理的"压舱石"。大部分技术元数据——表结构、字段定义、分区信息——天生就是二维表结构,用关系型存最自然。

MySQL 和 PostgreSQL,我选哪个?

我个人习惯用 PostgreSQL 做元数据主存储。原因有三:

  • JSONB 支持:元数据字段经常变,今天加个标签,明天多个属性。PostgreSQL 的 JSONB 类型可以灵活扩展,不用频繁改表结构。我在项目中遇到过,业务方突然要加"数据敏感等级"字段,用 JSONB 直接塞进去,零停机。
  • 数组类型:比如一个表有多个标签,用数组存比建关联表简单得多。
  • CTE 递归查询:查数据血缘时,虽然不如图数据库专业,但轻量场景够用。

MySQL 呢?如果你团队对 MySQL 更熟,或者基础设施已经重度依赖 MySQL,用它也没问题。但要注意:MySQL 的 JSON 支持比 PostgreSQL 弱一些,复杂查询性能差一截。

特性 MySQL 8.0+ PostgreSQL 15+
JSON 支持 有,但索引有限 JSONB + GIN 索引,强
数组类型 不支持原生 原生支持
递归查询 CTE 支持有限 CTE 递归成熟
分区表 支持,但管理复杂 声明式分区,更简洁
生态成熟度 极高 高

我的建议:新项目直接上 PostgreSQL。如果历史包袱是 MySQL,也别急着迁移,做好 JSON 字段的索引规划就行。

4.2 图数据库:Neo4j 与血缘关系

数据血缘,说白了就是"谁生了谁"。表 A 产出表 B,字段 X 来源于字段 Y。这种多对多的网状关系,用关系型存会写出又臭又长的 JOIN 语句,性能还差。

为什么是 Neo4j?

图数据库的核心优势是"关系即存储"。在 Neo4j 里,一个节点代表一个数据实体(表、字段、ETL 任务),一条边代表一个依赖关系。查"表 A 的上游有哪些表"这种问题,Neo4j 只需要一次图遍历,而关系型可能要递归 JOIN 十几次。

我记得有一次,用户要查"某个字段的完整血缘链路,包括跨了三层 ETL 任务的中间表"。用 PostgreSQL 写递归 CTE,跑了 8 秒。换成 Neo4j 的 Cypher 查询,0.2 秒出结果。差距就是这么大。

// Neo4j Cypher 查询字段血缘
MATCH (f:Field {name: 'order_amount'})<-[:DERIVED_FROM*1..5]-(upstream:Field)
RETURN upstream.name, upstream.table_name

避坑指南:我曾经犯过一个错——把所有元数据都塞进 Neo4j。结果发现,元数据的属性查询(比如"查所有创建时间在昨天的表")在图数据库里反而慢。后来学乖了:只有关系密集的场景才用图数据库,属性查询还是交给关系型或搜索引擎。

注意:Neo4j 的事务能力不如关系型数据库。如果元数据变更需要强一致性(比如同时更新表和字段的归属信息),建议用关系型做主存储,Neo4j 做关系查询的"加速层"。

4.3 搜索引擎:Elasticsearch 的全文检索

元数据治理里,最头疼的需求往往是"搜索"。用户记不清表名,只记得字段里有个"金额";或者想找所有包含"客户"关键词的注释。这时候,Elasticsearch 就派上用场了。

ES 在元数据场景的核心能力:

  • 全文检索:对表名、字段名、描述、注释建立倒排索引,支持模糊搜索、拼音搜索、同义词扩展。
  • 聚合分析:比如统计"哪个数据域的表最多",或者"最近一周新增了多少字段"。
  • 近实时:元数据变更后,秒级可见,适合做元数据发现和探索。

我习惯把 ES 作为元数据的"查询入口"。用户在前端搜索框里输入关键词,ES 返回匹配的表和字段,然后点击进去看详情——详情数据再从关系型或图数据库里拿。这样分工明确:ES 只管"找得快",不管"存得稳"。

// Elasticsearch 索引映射示例
{
  "mappings": {
    "properties": {
      "table_name": { "type": "text", "analyzer": "ik_smart" },
      "field_name": { "type": "text", "analyzer": "ik_smart" },
      "description": { "type": "text", "analyzer": "ik_smart" },
      "domain": { "type": "keyword" },
      "created_at": { "type": "date" }
    }
  }
}

小技巧:ES 的索引不要照搬关系型表结构。把多个相关表的数据"扁平化"成一个索引文档,搜索时一次命中,不用关联。比如把表信息和它的字段列表合并成一个文档,用户搜字段名时直接返回表信息。

4.4 三种存储的协同架构

说了这么多,三种存储到底怎么配合?我画了一张架构图,你看完就明白了。

元数据存储协同架构 元数据应用层 查询路由层 PostgreSQL 元数据主存储 Neo4j 血缘关系存储 Elasticsearch 全文检索索引 CDC 同步 / 定时任务 / 事件驱动 元数据采集层(爬取/解析/上报) 查询策略 • 搜索类 → ES • 血缘类 → Neo4j • 详情/事务 → PG

你看,架构分三层:

  1. 查询路由层:根据查询类型,把请求分发到不同的存储。搜关键词走 ES,查血缘走 Neo4j,查详情走 PostgreSQL。
  2. 存储层:各司其职。PostgreSQL 做主存储,保证数据一致性和完整性;Neo4j 存血缘关系,提供毫秒级图遍历;ES 建全文索引,支撑搜索和聚合。
  3. 同步层:元数据变更后,通过 CDC(比如 Debezium)或定时任务,把变更同步到 Neo4j 和 ES。注意同步延迟,我一般控制在 1 分钟以内。

曾经踩过的坑:同步链路断了没发现,导致 ES 里的元数据比 PostgreSQL 旧了三天。用户搜不到新表,以为是系统 bug。后来加了同步监控和告警,延迟超过 5 分钟就报警。这个一定要做。

4.5 索引策略与性能调优

存储选好了,索引没做好,照样慢。我分享几个实战经验。

PostgreSQL 索引:

  • 对经常查询的字段(表名、数据域、创建时间)建 B-tree 索引。
  • JSONB 字段用 GIN 索引,支持 @>、? 等操作符。
  • 如果经常做模糊查询(比如 LIKE '%xxx%'),考虑 pg_trgm 扩展。

Neo4j 索引:

  • 对节点属性建索引,比如节点标签 + 名称的组合索引。
  • 关系类型不需要索引,图遍历天然高效。
  • 注意:Neo4j 的索引是"辅助"的,真正快的是图遍历本身。

Elasticsearch 索引:

  • 分片数不要太多,我一般一个索引 3-5 个分片。
  • 使用 keyword 类型做精确匹配,text 类型做全文检索。
  • 如果元数据量不大(百万级),甚至可以不建 ES,直接用 PostgreSQL 的全文检索功能。

我的习惯:先上线,再优化。元数据量刚开始不会太大,PostgreSQL 单库就能扛。等用户多了、查询复杂了,再逐步引入 Neo4j 和 ES。别一开始就搞三套存储,运维成本不低。

好了,这一章就聊到这儿。三种存储怎么选、怎么搭、怎么优化,心里有数了吧?下回咱们聊聊元数据采集的实战技巧。