Git for Data:数据分支管理

在日常数据开发中,你一定遇到过这样的场景:

  • 风控团队要修改规则表,运营团队同时要调整活动数据,两边都怕改坏对方的数据

  • 测试环境需要一份和生产一模一样的数据,但复制 TB 级大表又慢又贵

  • 数据口径调整后,想知道 “到底改了哪几行”,却只能靠人肉比对

传统做法是 “先拷一份”——复制库、复制表、跑脚本。但拷贝慢、成本高,最关键的是:你很难说清楚改了什么,更难把改动安全地带回去。

MatrixOne 的 **Data Branch(数据分支)**功能,把数据变更管理变成了一套类似 Git 的工程化流程。你可以用 SQL 完成:创建分支、查看差异、合并变更、处理冲突——就像管理代码一样管理数据。

本教程将带你从零开始,通过一个完整的实战场景,学会 Data Branch 的全部核心操作。

版本要求:Data Branch 功能适用于 MatrixOne v3.0 及以上版本。

核心概念

在开始之前,先了解 5 个关键动作:

动作

SQL 语法

类比 Git

作用

快照

CREATE SNAPSHOT

git tag

给数据打一个时间点标记,作为安全锚点

创建分支

DATA BRANCH CREATE

git branch + git checkout

从主表/快照拉出一张独立的试验表

查看差异

DATA BRANCH DIFF

git diff

逐行比较两个分支之间的数据差异

合并

DATA BRANCH MERGE

git merge

把分支上的变更合并回目标表

删除分支

DATA BRANCH DELETE

git branch -d

清理不再需要的分支,保留审计元数据

与 Git 管理代码类似,Data Branch 的典型工作流是:

主表 → 打快照 → 创建分支 → 独立修改 → Diff 审阅 → Merge 回主表

备注

Data Branch 基于 Copy-on-Write(写时复制)机制。创建分支时不会真正复制数据,只有修改时才分配新存储空间,因此创建分支非常快,几乎不占额外存储。

开始前准备

在你开始之前,确认:

  • 已有可连接的 MatrixOne v3.0 及以上实例

  • 已安装 MySQL 客户端,并能连接到 MatrixOne

连接到 MatrixOne:

mysql -h 127.0.0.1 -P 6001 -u root -p111

实战场景:订单表的双线并行修改

我们模拟一个真实场景:一张订单主表,风控团队和运营团队需要同时修改,互不干扰,最后把各自的改动合并回主表。

第一步:准备主表数据

-- 创建演示数据库
DROP DATABASE IF EXISTS demo_branch;
CREATE DATABASE demo_branch;
USE demo_branch;

-- 创建订单主表
CREATE TABLE orders (
    order_id    INT PRIMARY KEY,
    customer    VARCHAR(20),
    amount      DECIMAL(10,2),
    risk_flag   TINYINT DEFAULT 0,
    promo_tag   VARCHAR(20)
);

-- 插入初始数据
INSERT INTO orders VALUES
    (1001, 'Alice',   99.90,  0, NULL),
    (1002, 'Bob',    199.00,  0, NULL),
    (1003, 'Charlie', 10.00,  0, NULL),
    (1004, 'Diana',  350.00,  0, NULL),
    (1005, 'Eve',     75.50,  0, NULL);

-- 确认数据
SELECT * FROM orders ORDER BY order_id;

结果:

+----------+----------+--------+-----------+-----------+
| order_id | customer | amount | risk_flag | promo_tag |
+----------+----------+--------+-----------+-----------+
|     1001 | Alice    |  99.90 |         0 | NULL      |
|     1002 | Bob      | 199.00 |         0 | NULL      |
|     1003 | Charlie  |  10.00 |         0 | NULL      |
|     1004 | Diana    | 350.00 |         0 | NULL      |
|     1005 | Eve      |  75.50 |         0 | NULL      |
+----------+----------+--------+-----------+-----------+

第二步:打快照,建立安全锚点

在任何修改之前,先给主表打一个快照。这是你的 “安全网”——无论后续怎么操作,都能回到这个点。

CREATE SNAPSHOT sp_orders_v1 FOR TABLE demo_branch orders;

第三步:创建两个分支

从主表创建两个独立的分支表,分别给风控团队和运营团队使用:

-- 风控分支:用于标记高风险订单
DATA BRANCH CREATE TABLE orders_risk FROM orders;

-- 运营分支:用于打活动标签、调整价格
DATA BRANCH CREATE TABLE orders_promo FROM orders;

此时三张表的数据完全一致。验证一下:

SELECT * FROM orders_risk ORDER BY order_id;
SELECT * FROM orders_promo ORDER BY order_id;

两个查询的结果都和主表一样。

备注

你也可以从快照创建分支:DATA BRANCH CREATE TABLE orders_risk FROM orders{SNAPSHOT='sp_orders_v1'};,这样即使主表在创建分支前被修改了,分支仍然基于快照时刻的数据。

第四步:两个分支独立修改

现在两个团队各自在自己的分支上工作,互不干扰。

风控团队orders_risk 上操作:

-- 标记 Bob 的订单为高风险
UPDATE orders_risk SET risk_flag = 1 WHERE order_id = 1002;

-- 删除 Charlie 的异常小额订单
DELETE FROM orders_risk WHERE order_id = 1003;

-- 新增一笔需要审查的订单
INSERT INTO orders_risk VALUES (1006, 'Frank', 500.00, 1, NULL);

运营团队orders_promo 上操作:

-- 给 Alice 和 Bob 的订单打上活动标签,并打九折
UPDATE orders_promo SET promo_tag = 'summer_sale', amount = amount * 0.9
WHERE order_id IN (1001, 1002);

-- 新增一笔活动订单
INSERT INTO orders_promo VALUES (1007, 'Grace', 39.90, 0, 'summer_sale');

此时主表完全不受影响:

SELECT * FROM orders ORDER BY order_id;
-- 仍然是最初的 5 行数据,没有任何变化

第五步:用 DIFF 查看差异

在合并之前,先看看每个分支相对于主表改了什么。

查看风控分支 vs 主表的差异:

DATA BRANCH DIFF orders_risk AGAINST orders;

结果:

+----------------------------+--------+----------+----------+--------+-----------+-----------+
| diff orders_risk against orders | flag   | order_id | customer | amount | risk_flag | promo_tag |
+----------------------------+--------+----------+----------+--------+-----------+-----------+
| orders_risk                | UPDATE |     1002 | Bob      | 199.00 |         1 | NULL      |
| orders_risk                | DELETE |     1003 | Charlie  |  10.00 |         0 | NULL      |
| orders_risk                | INSERT |     1006 | Frank    | 500.00 |         1 | NULL      |
+----------------------------+--------+----------+----------+--------+-----------+-----------+

可以清楚地看到:1 行更新、1 行删除、1 行新增。

查看运营分支 vs 主表的差异:

DATA BRANCH DIFF orders_promo AGAINST orders;

结果:

+-----------------------------+--------+----------+----------+--------+-----------+-------------+
| diff orders_promo against orders | flag   | order_id | customer | amount | risk_flag | promo_tag   |
+-----------------------------+--------+----------+----------+--------+-----------+-------------+
| orders_promo                | UPDATE |     1001 | Alice    |  89.91 |         0 | summer_sale |
| orders_promo                | UPDATE |     1002 | Bob      | 179.10 |         0 | summer_sale |
| orders_promo                | INSERT |     1007 | Grace    |  39.90 |         0 | summer_sale |
+-----------------------------+--------+----------+----------+--------+-----------+-------------+

2 行更新、1 行新增。

也可以直接比较两个分支之间的差异:

-- DATA BRANCH DIFF orders_risk AGAINST orders_promo;

这会显示两个分支之间所有不同的行,帮助你在合并前预判可能的冲突。

小技巧

对于大表,可以先用 OUTPUT COUNT 了解差异规模:

-- DATA BRANCH DIFF orders_risk AGAINST orders OUTPUT COUNT;

也可以用 OUTPUT LIMIT 10 只看前 10 行差异。

将差异导出为补丁文件:

如果你需要把变更带到另一个环境(比如从预发同步到生产),可以在合并前把 DIFF 结果导出为文件:

-- 导出到本地目录(在 merge 前执行,确保补丁反映的是分支原始变更)
-- DATA BRANCH DIFF orders_risk AGAINST orders OUTPUT FILE '/tmp/diff_output/';

系统会生成一个 .sql 文件(增量场景)或 .csv 文件(全量场景),并告诉你文件路径和使用方式。

在目标环境回放补丁:

# 回放 SQL 补丁
mysql -h <目标主机> -P 6001 -u root -p111 demo_branch < /tmp/diff_output/diff_xxx.sql

也可以导出到对象存储(通过 Stage):

-- 创建 Stage 指向 S3
-- CREATE STAGE my_stage URL = 's3://my-bucket/diffs/?region=us-east-1&access_key_id=<ak>&secret_access_key=<sk>';

-- 导出到 Stage
-- DATA BRANCH DIFF orders_risk AGAINST orders OUTPUT FILE 'stage://my_stage/';

第六步:合并分支到主表

先合并风控分支(无冲突场景):

DATA BRANCH MERGE orders_risk INTO orders;

验证主表:

SELECT * FROM orders ORDER BY order_id;

结果:

+----------+----------+--------+-----------+-----------+
| order_id | customer | amount | risk_flag | promo_tag |
+----------+----------+--------+-----------+-----------+
|     1001 | Alice    |  99.90 |         0 | NULL      |
|     1002 | Bob      | 199.00 |         1 | NULL      |
|     1004 | Diana    | 350.00 |         0 | NULL      |
|     1005 | Eve      |  75.50 |         0 | NULL      |
|     1006 | Frank    | 500.00 |         1 | NULL      |
+----------+----------+--------+-----------+-----------+

风控的修改已经生效:Bob 被标记为高风险,Charlie 的订单被删除,Frank 的订单被新增。

第七步:处理合并冲突

现在合并运营分支。注意,运营分支也修改了 order_id = 1002(Bob 的订单),而风控分支已经先合并了对同一行的修改,这就产生了冲突。

默认行为 — 遇到冲突报错:

-- DATA BRANCH MERGE orders_promo INTO orders;
-- ERROR: conflict on pk(1002)

系统会告诉你哪些行有冲突,不会静默覆盖数据。

三种冲突处理策略:

策略

语法

行为

适用场景

FAIL

默认

遇到冲突立即报错

需要人工审查的场景

SKIP

WHEN CONFLICT SKIP

跳过冲突行,保留主表数据

主表数据优先

ACCEPT

WHEN CONFLICT ACCEPT

用分支数据覆盖主表

分支数据优先

在本场景中,风控标记比运营折扣更重要,所以我们选择 SKIP(保留主表中风控的修改):

DATA BRANCH MERGE orders_promo INTO orders WHEN CONFLICT SKIP;

验证最终结果:

SELECT * FROM orders ORDER BY order_id;

结果:

+----------+----------+--------+-----------+-------------+
| order_id | customer | amount | risk_flag | promo_tag   |
+----------+----------+--------+-----------+-------------+
|     1001 | Alice    |  89.91 |         0 | summer_sale |
|     1002 | Bob      | 199.00 |         1 | NULL        |
|     1004 | Diana    | 350.00 |         0 | NULL        |
|     1005 | Eve      |  75.50 |         0 | NULL        |
|     1006 | Frank    | 500.00 |         1 | NULL        |
|     1007 | Grace    |  39.90 |         0 | summer_sale |
+----------+----------+--------+-----------+-------------+
  • Alice 的订单:运营的折扣和标签生效(无冲突)

  • Bob 的订单:保留了风控的 risk_flag = 1(冲突被 SKIP)

  • Grace 的订单:运营新增的订单成功合并

第八步:删除分支

分支使用完毕后,及时清理:

DATA BRANCH DELETE TABLE orders_risk;
DATA BRANCH DELETE TABLE orders_promo;

DATA BRANCH DELETE 要求目标是 active branch object,并将其元数据标记为已删除。对 branch lineage 中对象执行普通 DROP TABLE 时,也会参与元数据标记与 branch protection 清理。

第九步:回滚(如果需要)

如果合并后发现问题,可以用快照回到初始状态:

RESTORE TABLE sys.demo_branch.orders{SNAPSHOT='sp_orders_v1'};

说明:以上为 v3.0 之后版本的语法。v3.0 版本请使用以下语法:

-- v3.0 语法
-- RESTORE ACCOUNT sys DATABASE demo_branch TABLE orders FROM SNAPSHOT sp_orders_v1;

这就是快照的价值——让 “回退” 从高风险操作变成常规动作。

清理环境

DROP SNAPSHOT sp_orders_v1;
DROP DATABASE demo_branch;

Diff 与 Merge 场景

上面的标准工作流覆盖了多团队并行修改的常见场景。本节探索 DIFF 验证和 MERGE 的边界情况,这些模式对稳健的数据管理同样重要。

使用 DIFF 进行精确修复

快照可以作为可靠的基线。当你误修改了原始表的某些行时,可以使用从快照创建的分支 作为未修改的参照。对分支(快照基线)和已修改的原始表执行 DIFF,可以精确显示 哪些行发生了变化。然后你可以逐行恢复这些行,并再次执行 DIFF 确认数据已完全还原。

关键模式:

  1. 在风险操作前创建快照。

  2. 从该快照创建一个分支作为纯净副本。

  3. 如果原始表被误修改,用 DIFF 比较分支与原始表,查看改动范围。

  4. 逐行恢复到快照时的值。

  5. 再次 DIFF 验证零差异 —— 修复完成。

基于快照的 MERGE

将快照源合并到当前表时,如果快照自 LCA 以来没有变更,合并是无操作的 —— 当前端的数据保持不变。这可以作为安全检查在复杂操作前执行。

当分支有独立的变更(与原始表不重叠)时,将其合并回原始表会应用这些变更, 同时保持原始表未触及的行不变。

以下示例演示了精确修复和分支合并两种模式:

DROP DATABASE IF EXISTS demo_diff;
CREATE DATABASE demo_diff;
USE demo_diff;

CREATE TABLE inventory (
    id INT PRIMARY KEY,
    item VARCHAR(50),
    qty INT,
    status VARCHAR(20)
);
INSERT INTO inventory VALUES
    (1, 'widget', 100, 'active'),
    (2, 'gadget', 200, 'active'),
    (3, 'gizmo', 300, 'active'),
    (4, 'tool',  400, 'active');

-- 创建快照作为安全基线
CREATE SNAPSHOT sp_baseline FOR TABLE demo_diff inventory;

-- 从快照创建分支,作为未修改的参照
DATA BRANCH CREATE TABLE inventory_branch FROM inventory{SNAPSHOT='sp_baseline'};

-- 意外:修改了原始表的两行
UPDATE inventory SET qty = 9999 WHERE id IN (1, 2);

-- DIFF:分支(快照基线)vs 原始表 —— 显示两行被修改
DATA BRANCH DIFF inventory_branch AGAINST inventory;

-- 精确修复:逐行恢复到快照时的值
UPDATE inventory SET qty = 100 WHERE id = 1;
UPDATE inventory SET qty = 200 WHERE id = 2;

-- 再次 DIFF:值已恢复,无剩余差异
DATA BRANCH DIFF inventory_branch AGAINST inventory;

-- 在分支上做独立变更(与原始表修改不重叠)
UPDATE inventory_branch SET status = 'reviewed' WHERE id = 4;

-- 合并前 DIFF:分支有一个新变更
DATA BRANCH DIFF inventory_branch AGAINST inventory;

-- 将分支变更合并回原始表
DATA BRANCH MERGE inventory_branch INTO inventory;

-- 验证:分支变更已应用,原始表数据保留完整
SELECT * FROM inventory ORDER BY id;

DROP SNAPSHOT sp_baseline;
DROP DATABASE demo_diff;

进阶用法

数据库级分支

除了表级分支,你还可以对整个数据库创建分支,一次性复制所有表:

-- DATA BRANCH CREATE DATABASE dev_db FROM prod_db;

-- 在 dev_db 中自由修改,不影响 prod_db
-- 修改完成后合并回去

从快照创建分支

指定一个历史时间点创建分支,适合 “回到昨天的数据做分析”:

-- CREATE SNAPSHOT sp_yesterday FOR TABLE mydb mytable;
-- ... 时间过去,数据发生了变化 ...
-- DATA BRANCH CREATE TABLE mytable_analysis FROM mytable{SNAPSHOT='sp_yesterday'};

多级分支

分支可以继续创建分支,形成多级结构:

-- DATA BRANCH CREATE TABLE branch_v1 FROM main_table;
-- 在 branch_v1 上修改...
-- DATA BRANCH CREATE TABLE branch_v2 FROM branch_v1;
-- 在 branch_v2 上继续修改...

最佳实践

  1. 先打快照再操作:任何批量修改前,先 CREATE SNAPSHOT,给自己留退路

  2. 表强烈建议有主键:DIFF 和 MERGE 依赖主键定位行,虽然无主键表也支持(系统会使用内部隐藏主键),但有主键时结果更可控、冲突识别更精确

  3. 合并前先 DIFF:用 DATA BRANCH DIFF ... OUTPUT COUNT 了解变更规模,避免盲目合并

  4. 统一命名规范:分支表和快照建议带上用途和日期,如 orders_risk_20260226sp_orders_v1

  5. 及时清理分支:用完的分支及时 DATA BRANCH DELETE,保持环境整洁

  6. 冲突策略提前约定:团队内约定好 FAIL/SKIP/ACCEPT 的使用规则,不要靠口头沟通

语法速查

操作

语法

创建表分支

DATA BRANCH CREATE TABLE new_table FROM source_table

创建库分支

DATA BRANCH CREATE DATABASE new_db FROM source_db

从快照创建

DATA BRANCH CREATE TABLE t FROM src{SNAPSHOT='sp_name'}

查看差异

DATA BRANCH DIFF target AGAINST base

差异计数

DATA BRANCH DIFF target AGAINST base OUTPUT COUNT

按两侧和操作汇总差异

DATA BRANCH DIFF target AGAINST base OUTPUT SUMMARY

仅显示指定列

DATA BRANCH DIFF target AGAINST base COLUMNS (c1, c2)

差异限制行数

DATA BRANCH DIFF target AGAINST base OUTPUT LIMIT 10

导出差异文件

DATA BRANCH DIFF target AGAINST base OUTPUT FILE '/path/'

将差异持久化为普通表

DATA BRANCH DIFF target AGAINST base OUTPUT AS diff_table

按主键拣取

DATA BRANCH PICK source INTO dest KEYS (1, 2)

拣取快照区间 (from, to]

DATA BRANCH PICK source INTO dest BETWEEN SNAPSHOT sp1 AND sp2

合并(默认报错)

DATA BRANCH MERGE source INTO dest

合并(跳过冲突)

DATA BRANCH MERGE source INTO dest WHEN CONFLICT SKIP

合并(接受覆盖)

DATA BRANCH MERGE source INTO dest WHEN CONFLICT ACCEPT

删除表分支

DATA BRANCH DELETE TABLE table_name

删除库分支

DATA BRANCH DELETE DATABASE db_name

参考文档