[元数据/数据治理] OpenMetadata 使用指南
1 概述:OpenMetadata 使用指南
[元数据/数据资产/数据治理] OpenMetadata:面向数据与 AI 的开源【统一元数据】、上下文层平台、【数据治理平台】 - 博客园/千千寰宇
2 安装部署
前置要求
- 最小规格资源: 4C / 6GB
- Docker 20.10+(或 Docker Compose v2)与足够的 RAM(官方建议 ≥ 8GB);生产环境需规划 MySQL/Postgres 与 Elasticsearch 资源。
- Python 3.8+(用于本地运行采集/
metadataCLI,仅安装 Docker 时非必需)。
方式1:Docker Compose 快速开始(推荐 / 亲测)
部署
- 拉取仓库或直接下载 docker-compose 文件:
git clone https://github.com/open-metadata/OpenMetadata.git
cd OpenMetadata
# 切换到指定版本Tag
git checkout 2.0.2
git pull
# 或直接下载最新版 compose 文件(以 2.0.1 为例):
# curl -L -o docker-compose-openmetadata.yml \
# https://github.com/open-metadata/OpenMetadata/releases/download/2.0.1-release/docker-compose-openmetadata.yml
- 启动全部服务(Server + Ingestion/Airflow + 数据库 + 搜索索引):
cd F:\Codes-Minor\Github\OpenMetadata\
# 使用 MySQL 作为元数据仓库
docker compose -f docker/docker-compose-quickstart/docker-compose.yml up -d
# 或使用 自己调整后的策略: (笔者的最终选择,基于mysql、且调小了部分组件的资源配置)
cp docker/docker-compose-quickstart/docker-compose.yml docker/docker-compose-quickstart/my-docker-compose-mysql.yml
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml up -d
# 或使用 PostgreSQL 的 compose 文件: docker compose -f docker/docker-compose-postgres.yml up -d
如果资源吃紧,可以尝试调小关键组件的资源:
elasticsearch(堆降到 512m,容器上限 1G):
elasticsearch: container_name: openmetadata_elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0 environment: - discovery.type=single-node # - ES_JAVA_OPTS=-Xms1024m -Xmx1024m - ES_JAVA_OPTS=-Xms512m -Xmx512m - xpack.security.enabled=false deploy: resources: limits: memory: 1g reservations: memory: 512m # …其余(ports/healthcheck/volumes/networks)保持不变
mysql(容器上限 1G,顺手把 InnoDB 缓冲池调小)
mysql: container_name: openmetadata_mysql image: ${OPENMETADATA_DB_IMAGE:-docker.getcollate.io/openmetadata/db:2.0.0} # command: "--sort_buffer_size=10M" command: "--sort_buffer_size=10M --innodb_buffer_pool_size=64M --innodb_log_buffer_size=8M --max_connections=50" deploy: resources: limits: memory: 1g reservations: memory: 512m # …其余保持不变
deploy.resources.limits在 Compose v2(docker compose)下生效;如果你用的是旧的docker-composev1,就改用mem_limit: 1g这个兼容写法。
三条必须知道的边界:
ES 堆别压到 256m 以下。512m 对 quickstart / 测试够用,但元数据量大时搜索会变慢、甚至集群转 red。生产仍建议 1G+。
limit 必须 ≥ 堆 + off-heap 开销:ES 512m 堆配 1g limit 安全;若堆设 1g 而 limit 只给 768m,容器会被内核 OOM-Kill,症状和你现在很像(ES 反复重启)。
调小是「分蛋糕」不是「变出蛋糕」。它只是把有限内存合理分配给 server/ingestion,让整体峰值降下来。如果宿主机(或 Docker Desktop 配额)本身真的低于 6~8GB,单靠压容器救不回来 ——首选还是把 Docker 内存调到 ≥8GB,压参只是锦上添花。
访问 Web UI(OpenMetadata-Server)
- 访问 Web UI:
- OpenMetadata-Server : http://localhost:8585 (默认端口)
默认账号
admin@open-metadata.org/admin。
一个docker compose up会拉起 4 类容器:openmetadata-server、openmetadata-ingestion(Airflow)、MySQL/Postgres、Elasticsearch。
- 登录页

- 主页
访问 Web UI(OpenMetadata-Ingestion := Airflow)
- openmetadata_ingestion: http://localhost:8080/
本质上是 Airflow 的二次封装 | 默认账密: admin / admin


常用运维命令
- 常用运维命令
- 查看运行状态
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml ps

- 查看指定容器的日志
docker compose -f docker/docker-compose-quickstart/my-docker-compose-mysql.yml logs --tail 10 openmetadata-server

- 进入容器 / 容器执行shell
//进入指定的容器
docker exec -it openmetadata_server bash
或 docker exec -it openmetadata_server sh
//执行shell命令
docker exec openmetadata_server ls /opt/openmetadata
- 停止运行
//样例命令
docker compose -f docker/docker-compose-quickstart/docker-compose.yml down -v
样例输出:
$ docker compose -f docker/docker-compose-quickstart/docker-compose.yml down -v time="2026-09-23T15:29:09+08:00" level=warning msg="F:\\Codes-Minor\\Github\\OpenMetadata\\docker\\docker-compose-quickstart\\docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion" [+] down 10/10 ✔ Container openmetadata_ingestion Removed 0.6s ✔ Container openmetadata_server Removed 0.4s ✔ Container execute_migrate_all Removed 0.5s ✔ Container openmetadata_mysql Removed 4.9s ✔ Container openmetadata_elasticsearch Removed 5.9s ✔ Volume docker-compose-quickstart_ingestion-volume-tmp Removed 1.2s ✔ Volume docker-compose-quickstart_ingestion-volume-dags Removed 0.5s ✔ Volume docker-compose-quickstart_ingestion-volume-dag-airflow Removed 0.8s ✔ Volume docker-compose-quickstart_es-data Removed 1.5s ✔ Network docker-compose-quickstart_app_net Removed 1.8s
方式2:Linux(Ubuntu/CentOS)生产部署
# 1) 安装 Docker 与 Docker Compose(略)
# 2) 下载指定版本 compose 文件
curl -sL -o docker-compose.yml \
https://github.com/open-metadata/OpenMetadata/releases/download/1.10.3-release/docker-compose.yml
curl -sL -o docker-compose-postgres.yml \
https://github.com/open-metadata/OpenMetadata/releases/download/1.10.3-release/docker-compose-postgres.yml
# 3) 后台启动
docker compose up -d
# 4) 查看日志 / 状态
docker compose logs -f openmetadata-server
国内网络拉取镜像可配置 Docker 镜像加速,或使用镜像托管(docker.getcollate.io / 阿里云加速)。
方式3:源码开发 / 手动构建(Windows / Linux)
- 需 Java(JDK 17+)、Maven、Node.js/Yarn、Python 3.8+。
- 遵循仓库
DEVELOPER.md:make generate生成模型 → 构建后端(Maven reactor)与前端(Yarn)→ 本地启动openmetadata-service。
3 使用指南 for WEB-UI(OpenMetadata-Server)
假设通过 docker compose 安装部署完成后:
主页

添加服务(postgresql 数据源) + 采集元数据后:

设置
服务:配置【连接器】 + 从不同【数据源】提取【元数据】
- 服务: 设置连接器并从不同数据来源提取元数据

- 支持的数据源:
APIs / 数据库 / 消息队列 / 仪表盘 / 工作流 / 机器学习模型 / 存储 / 搜索引擎 / 元数据 / 驱动器 / 数据观测
APIs


数据库(必读)
- 数据库服务

- 添加新服务:

下面以添加 Postgresql 数据库为例
- 配置数据源的连接信息

由于笔者的第三方数据源 postgresql 在本地宿主机的另一 docker 容器内,所以我将
Host and Port配置项 改为了host.docker.internal:5432
- 测试连接

- 配置元数据的采集策略


配置完毕后(笔者选择:默认,除了系统库以外的全部库、全部schema、全部表、全部存储过程),选择:创建并部署
http://localhost:8585/service/databaseServices/postgresql.mydb/insights

图:人工点击/触发【Tab:代理】-【运行】按钮后
- 连接信息
- 洞察
- 数据库
- 代理
http://localhost:8585/service/databaseServices/postgresql.mydb/agents/metadata
人工点击/触发【运行】按钮:
查看【日志】 (支持复制、下载等操作)

运行完毕后,元数据即采集完成
再去看数据资产,即有数据了。
搜索引擎

消息队列

机器学习模型

探索



血缘关系
观测-数据质控
概要
测试用例
治理-术语库/glossary

治理-本体浏览器/ontology

治理-分类/Tags

治理-列批量操作
治理-指标/Metrics
治理-工作流/Workflow
数据市场-概览

数据市场-域

数据市场-数据产品

上下文中心-仪表盘
上下文中心-文章
上下文中心-文件
上下文中心-记忆
上下文中心-归档
Z FAQ for OpenMetadata 部署 & 使用
Q: 数据源适配与自定义连接器开发? (必读)
Q: 基于OpenMetadata SDK开发企业内部门户连接器的自定义数据源采集? (必读)
pip install openmetadata-ingestion-sdk- 编写核心的数据采集代码
# 导入OpenMetadata SDK的核心客户端,负责和OpenMetadata服务交互
from metadata.ingestion.ometa.ometa_api import OpenMetadata
from metadata.ingestion.api.models import Database, Table, Column, ColumnType, DatabaseSchema
# --------------------------
# 1. 配置OpenMetadata连接信息(替换成你自己的实际地址和账号)
# --------------------------
# OpenMetadata服务的访问地址,本地测试一般是http://localhost:8585
OPENMETADATA_HOST = "http://your-openmetadata-address:8585"
# OpenMetadata的管理员账号,建议使用专门的服务创建账号
OPENMETADATA_USER = "admin"
OPENMETADATA_PASSWORD = "your-admin-password"
# --------------------------
# 2. 模拟内部考勤系统的元数据(实际项目中这里替换为真实数据源的获取逻辑)
# --------------------------
internal_db_name = "internal_attendance_system"
# 3张核心表,每张表都加了业务相关的描述,方便后续使用时理解
sample_tables = [
{
"table_name": "employee",
"table_description": "存储公司所有员工的基本身份信息,包含工号、姓名、所属部门等",
"columns": [
{"name": "emp_id", "type": ColumnType.STRING, "description": "全局唯一的员工工号"},
{"name": "emp_name", "type": ColumnType.STRING, "description": "员工的姓名"},
{"name": "dept", "type": ColumnType.STRING, "description": "员工所属的部门名称"}
]
},
{
"table_name": "attendance_record",
"table_description": "存储员工每日的考勤打卡数据,包含上下班时间、考勤状态等",
"columns": [
{"name": "attendance_id", "type": ColumnType.STRING, "description": "考勤记录的唯一ID"},
{"name": "emp_id", "type": ColumnType.STRING, "description": "关联员工表的工号,外键字段"},
{"name": "checkin_time", "type": ColumnType.DATETIME, "description": "上班打卡的时间"},
{"name": "checkout_time", "type": ColumnType.DATETIME, "description": "下班打卡的时间"},
{"name": "status", "type": ColumnType.STRING, "description": "考勤状态,取值为正常、迟到、早退、旷工"}
]
},
{
"table_name": "overtime_apply",
"table_description": "存储员工的加班申请记录,包含申请时长、审批状态等",
"columns": [
{"name": "apply_id", "type": ColumnType.STRING, "description": "加班申请的唯一ID"},
{"name": "emp_id", "type": ColumnType.STRING, "description": "关联员工表的工号,外键字段"},
{"name": "apply_hours", "type": ColumnType.FLOAT, "description": "申请的加班时长,单位为小时"},
{"name": "approval_status", "type": ColumnType.STRING, "description": "审批状态,取值为待审批、已通过、已拒绝"}
]
}
]
# --------------------------
# 3. 初始化OpenMetadata客户端,建立连接
# --------------------------
try:
# 使用基础认证方式初始化客户端,负责后续的元数据提交
ometa_client = OpenMetadata(
host=OPENMETADATA_HOST,
auth_provider="basic",
username=OPENMETADATA_USER,
password=OPENMETADATA_PASSWORD
)
print("成功连接到OpenMetadata服务")
except Exception as e:
print(f"连接OpenMetadata失败,错误信息:{str(e)}")
exit(1)
# --------------------------
# 4. 上传元数据到OpenMetadata
# --------------------------
try:
# 创建数据源服务,作为内部考勤系统的容器,名称建议用业务相关的标识
attendance_service = ometa_client.create_or_update_database_service(
name="internal_attendance_system",
description="公司内部考勤管理系统的数据源服务"
)
print(f"成功创建/更新数据源服务:{attendance_service.name}")
# 在该服务下创建对应的数据库,存储考勤相关的所有表
attendance_db = ometa_client.create_or_update_database(
service=attendance_service.id,
name=internal_db_name,
description="存储考勤、加班相关的所有业务元数据"
)
print(f"成功创建/更新数据库:{attendance_db.name}")
# 遍历模拟的表数据,逐个创建表和字段
for table_info in sample_tables:
# 构建表对象,关联到刚才创建的数据库
table = Table(
name=table_info["table_name"],
description=table_info["table_description"],
columns=[
Column(
name=col["name"],
column_type=col["type"],
description=col["description"]
) for col in table_info["columns"]
],
database_id=attendance_db.id
)
# 提交表对象到OpenMetadata
created_table = ometa_client.create_or_update_table(table)
print(f"成功创建表:{created_table.name},包含{len(table_info['columns'])}个业务字段")
except Exception as e:
print(f"上传元数据失败,错误信息:{str(e)}")
exit(1)
print("恭喜!自定义连接器的元数据采集已完成,你可以登录OpenMetadata平台查看刚才创建的考勤系统元数据")
Q: openmetadata 如何维护、采集 表级、列级元数据的血缘关系的?有哪几种方式?
血缘在 OpenMetadata 中如何存储(数据模型)
OpenMetadata 把血缘建模为一个有向图,节点是各类资产实体(表、管道、仪表盘、ML 模型等),边是血缘关系,基于 W3C PROV-O 标准:
- 表级血缘:一条
Edge,fromEntity → toEntity(实体之间的上下游边)。 - 列级血缘:挂在表级边上的
lineageDetails.columnsLineage,结构为fromColumns[] → toColumn + function,即「一个或多个源列,经某个变换函数,生成目标列」;同时可附带该变换的sqlQuery、所属pipeline、description。 - 来源可追溯:每条血缘边都带一个
source标记(标识这条血缘是怎么来的,见下文 11 种枚举)以及createdBy / createdAt / updatedAt。 - 物理存储:落在元数据库(MySQL/PostgreSQL)的
entity_lineage表中,通过 REST APIGET/PUT /api/v1/lineage读写。
有哪几种血缘采集方式?——官方定义了 11 种血缘来源(source 枚举)
| # | source 枚举 | 含义 | 典型产生途径 |
|---|---|---|---|
| 1 | Manual |
手动血缘 | UI 无代码血缘编辑器 / 直接调 API 添加 |
| 2 | ViewLineage |
视图血缘 | 采集时解析数据库视图定义 |
| 3 | QueryLineage |
SQL 查询血缘 | 解析查询日志/查询历史里的 SQL |
| 4 | PipelineLineage |
管道血缘 | Airflow / Dagster / Prefect 等任务输入输出 |
| 5 | DashboardLineage |
仪表盘血缘 | Tableau / Looker / Metabase 等数据源关系 |
| 6 | DbtLineage |
dbt 血缘 | 解析 dbt manifest.json 的模型依赖与列映射 |
| 7 | SparkLineage |
Spark 血缘 | 分析 Spark 执行计划 / Databricks notebook lineage |
| 8 | OpenLineage |
OpenLineage 标准事件 | 接收符合 OpenLineage 规范的事件 |
| 9 | ExternalTableLineage |
外部表血缘 | 湖仓外部表(如 Unity Catalog、Iceberg、Glue) |
| 10 | CrossDatabaseLineage |
跨库血缘 | 跨数据库/跨服务的数据流 |
| 11 | ChildAssets |
子资产聚合血缘 | 容器/父资产聚合其子资产(如存储桶内对象)的边 |
按采集通道看它们怎么工作
① SQL 解析与查询日志(QueryLineage / ViewLineage)—— 最主流的自动方式
- 数据库连接器(Snowflake、BigQuery、Redshift、PostgreSQL 等)采集查询日志/查询历史(Snowflake query history、BigQuery audit logs、Redshift 系统表、PG
pg_stat_statements)。 - 内置 SQL Parser 解析
SELECT / INSERT / CREATE VIEW / MERGE等语句,自动推导出表级边(读哪些表、写哪张表)和列级映射(SELECT 列表中的列表达式 → 目标列,含 JOIN key、聚合、表达式变换)。 - 采集时可配置查询日志窗口时长、解析超时、结果行数上限、是否启用血缘解析等。
② 管道与调度(PipelineLineage / OpenLineage)
- Airflow:启用官方 lineage backend(
airflow_provider_openmetadata.lineage.backend.OpenMetadataLineageBackend),DAG 任务运行时自动上报「输入表 → 任务 → 输出表」及列级映射。 - Dagster、Prefect、Fivetran、Airbyte 等管道连接器从任务元数据提取血缘。
- 通用 OpenLineage 事件接入,Flink/Spark 等发射的事件可直接进入血缘图。
③ 转换工具(DbtLineage / SparkLineage)
- dbt:读取
manifest.json,得到模型依赖(ref/source)与列级转换映射,这是列级血缘质量最高的来源之一。 - Spark / Databricks:分析执行计划或 notebook lineage,产出任务级与列级血缘。
④ BI(DashboardLineage)
- Tableau / Looker / Metabase / Power BI 等连接器提取「仪表盘 → 图表 → 底层表/字段」关系,打通「源表 → 管道 → 数仓 → 报表」的端到端血缘。
⑤ 手动维护(Manual)
- UI 上「Lineage 标签页 → Edit」可视化拖拽连线(表级);列级可编辑列映射。
- 或编程式
PUT /api/v1/lineage直接写入边 +columnsLineage。 - 适合补充自动解析不到的(存储过程、动态 SQL、遗留系统)。
⑥ 其他(ExternalTable / CrossDatabase / ChildAssets)
- 湖仓外部表、跨库数据流、容器/子资产聚合场景下的自动血缘。
表级与列级血缘的维护机制
-
自动采集是增量 upsert:每次采集管道运行,解析出的边与列映射会被写入/合并(表级边按实体 ID 去重,列级
columnsLineage覆盖该边的最新映射),血缘图随调度周期持续更新。 -
人工修正与治理:可删除错误边、补充缺失边、为边加描述;
createdBy/updatedAt保留审计痕迹。 -
血缘深度控制:UI 可设置上游/下游查询深度(如 3 层),用于影响分析。
-
最佳实践:
- dbt + Airflow + 查询日志三路并用,配合手动补充;
- 对 PII/核心指标列强制开启列级血缘;
- 定期抽验血缘与实际数据流是否一致(自动解析在存储过程、复杂动态 SQL、临时表跳转等场景可能漏判,必要时可外接第三方解析器补全)。
下面这张图汇总了整个「采集 → 解析 → 存储 → 消费」链路与两种粒度:

- 总结:自动采集靠「SQL 解析 + 查询日志 + 管道/转换工具集成」三路主力,人工用 UI/API 兜底;表级存边、列级存
columnsLineage映射,每条边都带source标记以便追溯血缘来源。
Q: 如何通过请求API的方式手动配置血缘关系?(必读)
需求描述
- 假设 postgresql 数仓中有这么一条 ETL SQL,如何通过请求API的方式手动注册血缘?
INSERT INTO dwd_elective (
id, student_id, course_id, score, semester, create_time
, student_no, student_name, student_major_no
, course_no , course_name, course_credit
)
select
e.id , student_id , course_id, score, semester, e.create_time
, student_no, stu.name as student_name, stu.major_no as student_major_no
, course_no , course_name, cr.credit as course_credit
from ods_elective e
left join dim_student stu on e.student_id = stu.id
left join dim_course cr on e.course_id = cr.id ;
- 官方标准的精确接口格式:
手动注册血缘用
PUT /api/v1/lineage,请求体是{ "edge": { fromEntity, toEntity, description, lineageDetails: { source, sqlQuery, columnsLineage[] } } }
其中fromEntity/toEntity用实体 UUID,columnsLineage的fromColumns/toColumn用列的 全限定名(FQN)。
下面针对上面的这条 ETL SQL 给出完整可执行方案:
这条 ETL 形成的血缘(表级 + 列级)
表级:3 条上游 → 1 条下游
ods_elective ─┐
dim_student ─┼─► dwd_elective
dim_course ─┘
列级映射(fromColumns[] → toColumn, function):
| 源列 | 目标列(dwd_elective) | 变换 |
|---|---|---|
| ods_elective.id | id | DIRECT |
| ods_elective.student_id | student_id | DIRECT |
| ods_elective.course_id | course_id | DIRECT |
| ods_elective.score | score | DIRECT |
| ods_elective.semester | semester | DIRECT |
| ods_elective.create_time | create_time | DIRECT |
| dim_student.student_no | student_no | JOIN |
| dim_course.course_no | course_no | JOIN |
| dim_course.course_name | course_name | JOIN |
| dim_student.name | student_name | JOIN |
| dim_student.major_no | student_major_no | JOIN |
| dim_course.credit | course_credit | JOIN |
⚠️ 你的 SQL 里
student_no、course_no、course_name是未限定前缀的列。若这些列在dim_student/dim_course中不存在,则按 SQL 语义归属ods_elective(我按此处理);若实际上取自维表,请把对应fromColumns改成维表列。
API 请求体结构(PUT /api/v1/lineage)
- 一条边一个请求(上游表 → dwd_elective 各一次),
columnsLineage只放该上游表真正贡献的列。
FQN: 形如服务名.库名.schema名.表名.列名。
步骤与命令
1. 登录拿 Token
ENCODED_PASSWORD=$(echo -n 'admin' | base64)
例如: YWRtaW4=
TOKEN=$(curl -X POST http://localhost:8585/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@open-metadata.org","password":"YWRtaW4="}' \
| sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p' )
注: curl 的样例输出:
{
"accessToken":"eyJraWQiOiJHYjM4OWEtOWY3Ni1nZGpzLWE5MmotMDI0MmJrOTQzNTYiLCJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJvcGVuLW1ldGFkYXRhLm9yZyIsInN1YiI6ImFkbWluIiwicm9sZXMiOlsiQWRtaW4iXSwiZW1haWwiOiJhZG1pbkBvcGVuLW1ldGFkYXRhLm9yZyIsImlzQm90IjpmYWxzZSwidG9rZW5UeXBlIjoiT01fVVNFUiIsInVzZXJuYW1lIjoiYWRtaW4iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTc5MDE3NjQ0MCwiZXhwIjoxNzkwMTgwMDQwfQ.LUz8R8tZcjkNHAnKnkQY3O4t5MsBnJqBMsTaitILThYfnrsffHy_-51_MOgZzkq8vn8ixjuGgjb9swQnLGXTXN_JnlgPaE_jKy_VWNa1BdxjDDt8e76fqbj5rv3z_kGNgi4qAB4Y8dBfXa-98rgUcxysg5Ndq-261eU_gWovDxKNkMr9nKY9UlheWetHZQX8KJIR_bBbWxYiWsyVlZuWuvjbaTsJ339Jy-coqGo_4t2TCY3Vufu6_Kezx_9F-A-v4snZ7Q7Q4V4tepvg1dkLTPH5DSbjFcZGXMjWy4ta_rWG7Xt79eZuU0Ng4_siDChdBBuB_I75s_sTakaLGpeLQA"
,"refreshToken":"bff2e2e6-2ffc-4a97-b721-5f702908540d"
,"tokenType":"Bearer"
,"expiryDuration":1790180040093
}
echo $TOKEN
2. 查4张表的 UUID(API 要求 fromEntity/toEntity 用 UUID)
- 获取表的UUID
即 curl api 响应中的 第1个"id"字段的值
for t in ods_elective dim_student dim_course dwd_elective; do
curl -s "http://localhost:8585/api/v1/tables/name/%22postgresql.mydb%22.mydb.public.$t" \
-H "Authorization: Bearer $TOKEN" | jq -r '"\(.fullyQualifiedName): \(.id)"'
done
或:
for t in ods_elective dim_student dim_course dwd_elective; do
id=$(curl -s "http://localhost:8585/api/v1/tables/name/%22postgresql.mydb%22.mydb.public.$t" \
-H "Authorization: Bearer $TOKEN" \
| grep -oP '"id":\s*"\K[^"]+' | head -1)
echo "$t : $id"
done
%22即"英文双引号- 服务名 =
postgresql.mydb- 库名 =
mydb- schema名 =
public
(把 pg.dw.public 换成你 OpenMetadata 里实际的 FQN,可从 WEB UI 面包屑可看到,如: http://localhost:8585/table/"postgresql.mydb".mydb.public.dim_course 。)
样例输出
curl的样例输出: ods_elective 表
{"id":"b172ad01-4e80-499d-8923-7dc1692448b5","name":"ods_elective","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective","description":"选修事实表","version":0.1,"updatedAt":1790172961000,"updatedBy":"ingestion-bot","href":
"http://localhost:8585/api/v1/tables/b172ad01-4e80-499d-8923-7dc1692448b5","tableType":"Regular","columns":[{"name":"id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"代理主键ID","fullyQualifiedName":"\"pos
tgresql.mydb\".mydb.public.ods_elective.id","tags":[],"constraint":"PRIMARY_KEY","children":[]},{"name":"student_id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"关联学生表的代理主键ID","fullyQualifiedName
":"\"postgresql.mydb\".mydb.public.ods_elective.student_id","tags":[],"constraint":"NOT_NULL","children":[]},{"name":"course_id","dataType":"BIGINT","dataLength":1,"dataTypeDisplay":"bigint","description":"关联课程表的代理主键ID","fullyQ
ualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.course_id","tags":[],"constraint":"NOT_NULL","children":[]},{"name":"score","dataType":"NUMERIC","dataLength":1,"precision":5,"scale":2,"dataTypeDisplay":"numeric(5,2)","descrip
tion":"成绩","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.score","tags":[],"constraint":"NULL","children":[]},{"name":"semester","dataType":"VARCHAR","dataLength":20,"dataTypeDisplay":"character varying(20)","descri
ption":"学期 (示例维度扩展)","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.semester","tags":[],"constraint":"NULL","children":[]},{"name":"create_time","dataType":"TIMESTAMP","dataLength":1,"dataTypeDisplay":"timesta
mp without time zone","description":"选课时间","fullyQualifiedName":"\"postgresql.mydb\".mydb.public.ods_elective.create_time","tags":[],"constraint":"NULL","children":[]}],"databaseSchema":{"id":"d515164e-93b5-46ed-a36b-3e6bf3515676","t
ype":"databaseSchema","name":"public","fullyQualifiedName":"\"postgresql.mydb\".mydb.public","description":"standard public schema","displayName":"public","deleted":false,"href":"http://localhost:8585/api/v1/databaseSchemas/d515164e-93b5
-46ed-a36b-3e6bf3515676"},"database":{"id":"c2a36e8a-2fa8-43c2-8c9a-e92f9c4736ba","type":"database","name":"mydb","fullyQualifiedName":"\"postgresql.mydb\".mydb","displayName":"mydb","deleted":false,"href":"http://localhost:8585/api/v1/d
atabases/c2a36e8a-2fa8-43c2-8c9a-e92f9c4736ba"},"service":{"id":"0cd13192-ff8c-4aed-b4c0-fa19384dc0fa","type":"databaseService","name":"postgresql.mydb","fullyQualifiedName":"\"postgresql.mydb\"","description":"<p>demo service</p>","disp
layName":"postgresql.mydb","deleted":false,"href":"http://localhost:8585/api/v1/services/databaseServices/0cd13192-ff8c-4aed-b4c0-fa19384dc0fa"},"serviceType":"Postgres","deleted":false,"sourceHash":"35ffccbe530592bc8c88c425d1a447c5","pr
ocessedLineage":false,"entityStatus":"Unprocessed"}
最终的样例输出:
ods_elective : b172ad01-4e80-499d-8923-7dc1692448b5
dim_student : d4812bea-5697-4fd6-9a20-1928b1f06e5f
dim_course : 9630d23b-6106-487f-acd2-ddba3c1f7bf2
dwd_elective : 8df87fa3-147c-40b9-abef-f9c939db5853
3. 注册血缘(表级血缘 or 列级血缘) —— 每个上游表一个 PUT
边①:ods_elective → dwd_elective
- 创建表级血缘 (示例)
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "<ods_elective uuid>", "type": "table" },
"toEntity": { "id": "<dwd_elective uuid>", "type": "table" }
}
}'
UI上查看血缘关系: http://localhost:8585/table/"postgresql.mydb".mydb.public.ods_elective/lineage
- 创建列级血缘 (示例)
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": "<ods_elective uuid>",
"toEntity": "<dwd_elective uuid>",
"description": "ETL: dwd_elective : based on ods_elective table generated(LEFT JOIN dim tables)",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (id, student_id, course_id, score, semester, create_time, student_no, student_name, student_major_no, course_no, course_name, course_credit) select e.id, student_id, course_id, score, semester, e.create_time, student_no, stu.name as student_name, stu.major_no as student_major_no, course_no, course_name, cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.student_id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.course_id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.score"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.score"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.semester"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.semester"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.create_time"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.create_time"}
]
}
}
}'
形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "b172ad01-4e80-499d-8923-7dc1692448b5", "type": "table" },
"toEntity": { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
"description": "ETL: dwd_elective : based on ods_elective table generated(LEFT JOIN dim tables)",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (id, student_id, course_id, score, semester, create_time, student_no, student_name, student_major_no, course_no, course_name, course_credit) select e.id, student_id, course_id, score, semester, e.create_time, student_no, stu.name as student_name, stu.major_no as student_major_no, course_no, course_name, cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.student_id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.course_id"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_id"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.score"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.score"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.semester"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.semester"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.ods_elective.create_time"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.create_time"}
]
}
}
}'
- 特别注意: description 等字段,尽量别用中文,目前 openmetadata v2.0.0 此 api 有字符集兼容性方面的 bug,会导致报 400 错误:
{"code":400,"message":"Invalid request format"}
UI查看列级血缘:
http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage
边②:dim_student → dwd_elective
列级血缘
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "<dim_student uuid>", "type": "table" },
"toEntity": { "id": "<dwd_elective uuid>", "type": "table" },
"description": "ETL: associate dim_student of name , major",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (...) select ... stu.name as student_name, stu.major_no as student_major_no ... from ods_elective e left join dim_student stu on e.student_id = stu.id ...",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.student_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_no", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_name", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.major_no"],"toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_major_no","function": "JOIN"}
]
}
}
}'
形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "d4812bea-5697-4fd6-9a20-1928b1f06e5f", "type": "table" },
"toEntity": { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
"description": "ETL: associate dim_student of name , major",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (...) select ... stu.name as student_name, stu.major_no as student_major_no ... from ods_elective e left join dim_student stu on e.student_id = stu.id ...",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.student_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_no", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_name", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_student.major_no"],"toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.student_major_no","function": "JOIN"}
]
}
}
}'
UI上查看血缘: http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage
边③:dim_course → dwd_elective
- 添加列级血缘
curl -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "<dim_course uuid>", "type": "table" },
"toEntity": { "id": "<dwd_elective uuid>", "type": "table" },
"description": "ETL: associate dim_course of credit field",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (...) select ... cr.credit as course_credit ... from ods_elective e left join dim_course cr on e.course_id = cr.id",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_no", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_name", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.credit"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_credit", "function": "JOIN"}
]
}
}
}'
形如:
curl -v -X PUT http://localhost:8585/api/v1/lineage \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"edge": {
"fromEntity": { "id": "9630d23b-6106-487f-acd2-ddba3c1f7bf2", "type": "table" },
"toEntity": { "id": "8df87fa3-147c-40b9-abef-f9c939db5853", "type": "table" },
"description": "ETL: associate dim_course of credit field",
"lineageDetails": {
"source": "Manual",
"sqlQuery": "INSERT INTO dwd_elective (...) select ... cr.credit as course_credit ... from ods_elective e left join dim_course cr on e.course_id = cr.id",
"columnsLineage": [
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_no"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_no", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.course_name"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_name", "function": "JOIN"},
{"fromColumns": ["\"postgresql.mydb\".mydb.public.dim_course.credit"], "toColumn": "\"postgresql.mydb\".mydb.public.dwd_elective.course_credit", "function": "JOIN"}
]
}
}
}'
UI上查看血缘: http://localhost:8585/table/"postgresql.mydb".mydb.public.dwd_elective/lineage

4.(可选/未亲测)用 Python requests 一次跑三条
import requests, json
BASE, TOKEN = "http://localhost:8585/api/v1", "<你的accessToken>"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
P = "pg.dw.public" # 你的 FQN 前缀
def tid(t):
r = requests.get(f"{BASE}/tables/name/{P}.{t}", headers=H); return r.json()["id"]
ups = {"ods_elective": tid("ods_elective"),
"dim_student": tid("dim_student"),
"dim_course": tid("dim_course")}
dwd = tid("dwd_elective")
edges = {
"ods_elective": [("id","id"),("student_id","student_id"),("course_id","course_id"),
("score","score"),("semester","semester"),("create_time","create_time"),
("student_no","student_no"),("course_no","course_no"),("course_name","course_name")],
"dim_student": [("name","student_name"),("major_no","student_major_no")],
"dim_course": [("credit","course_credit")],
}
SQL = "INSERT INTO dwd_elective (...) select e.id, ..., cr.credit as course_credit from ods_elective e left join dim_student stu on e.student_id = stu.id left join dim_course cr on e.course_id = cr.id"
for src, cols in edges.items():
body = {"edge": {
"fromEntity": ups[src], "toEntity": dwd,
"description": f"ETL: dwd_elective from {src}",
"lineageDetails": {"source": "Manual", "sqlQuery": SQL,
"columnsLineage": [
{"fromColumns": [f"{P}.{src}.{c}"], "toColumn": f"{P}.dwd_elective.{t}", "function": "DIRECT" if src=="ods_elective" else "JOIN"}
for c, t in cols]}}}
r = requests.put(f"{BASE}/lineage", headers=H, data=json.dumps(body))
print(src, "->", r.status_code)
5. 验证与删除
# 查 dwd_elective 的血缘图
curl -s "http://localhost:8585/api/v1/lineage/table/<表的UUID>?upstreamDepth=3" -H "Authorization: Bearer $TOKEN"
形如: curl -s "http://localhost:8585/api/v1/lineage/table/8df87fa3-147c-40b9-abef-f9c939db5853?upstreamDepth=3" -H "Authorization: Bearer $TOKEN"
# 删除某条边(可选)
curl -X DELETE "http://localhost:8585/api/v1/lineage/table/name/<上游FQN>/table/name/<下游FQN>" -H "Authorization: Bearer $TOKEN"
形如:curl -X DELETE "http://localhost:8585/api/v1/lineage/table/name/%22postgresql.mydb%22.mydb.public.ods_elective/table/name/%22postgresql.mydb%22.mydb.public.dwd_elective" -H "Authorization: Bearer $TOKEN"
关键注意点
- FQN 必须与库内完全一致:
服务名.库名.schema.表名.列名,一个字符都不能差,否则列级血缘对不上。建议先GET /api/v1/tables/name/...确认各表及列的确切 FQN。 - fromEntity/toEntity 用 UUID:不能直接用 FQN,先查 id(第二步)。
- 列必须已存在:列级血缘要求目标列(及源列)在 OpenMetadata 中已采集到;若 dwd_elective 尚未采集,先跑一次它的元数据采集。
- 一条边一个请求:多上游时每个上游→目标各 PUT 一次,
columnsLineage只放该上游贡献的列。 function建议值:直传用DIRECT,JOIN 取字段用JOIN,有表达式变换用真实函数名(如CONCAT、LOWER),便于影响分析时看到变换类型。- 身份认证:默认 basic 认证用登录返回的
accessToken;若开了 OIDC/LDAP 或使用其他 authorizer,按你的配置获取 token。 - source 字段:手动注册一律填
Manual;若这条 SQL 实际挂在已登记的 Airflow 管道上,可补充pipeline引用并改用PipelineLineage。
Y 推荐文献
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!




























浙公网安备 33010602011771号