---
title: 从 Fork Table 到 Fork Database：seekdb 给 Agent一套完整的数据版本控制
description: 代码有 git diff，数据呢？seekdb 1.2.0 引入 Fork Database、DIFF TABLE、MERGE TABLE 三条 SQL 原语，在数据库内核层面补齐数据版本控制闭环——从整库克隆、行级差异对比到策略合并，覆盖 Agent 开发测试、实验回收、多团队协作等八大场景。
image: https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*OSPzQ6GUQF4AAAAAQHAAAAgAeiGDAQ/original
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

[AI](https://www.oceanbase.com/obi) 咨询热线

Agent 写完一段业务逻辑，跑出来的结果表有 500 行。预期表也有 500 行。哪些行对了、哪些值错了、多了还是少了？

你打开 SQL 编辑器，开始写 LEFT JOIN + CASE WHEN + UNION ALL……写完发现漏了 NULL 的情况，改。改完发现排序不对，再改。折腾半小时，终于拿到差异，复制粘贴给 Agent，让它改代码。Agent 改完，你再跑一遍这套 SQL……

代码有 git diff，一行命令。数据呢？

DIFF TABLE  actual_​result AGAINST expected_​result;

这是 seekdb 1.2.0 引入的 SQL 原语。

Agent 写代码，用它验证输出是否符合预期；Agent 改数据，用它审核改了哪些行、值对不对，再决定要不要合回主表。思路和 Git 一样——Fork 建分支，DIFF 看差异，MERGE 合回去。

## **你正在经历这些痛点吗？**

| 场景 | 痛点 |
| **AI 写代码测试** | AI 写完业务逻辑，测试时难以验证输出是否符合预期；   有差异时说不清差在哪、怎么反馈给 Agent 改，形成不了「写代码→测试→debug」闭环。 |
| **AI 开发与上线** | Agent 改完数据，自测时说不清改了什么、是否符合预期；   上生产时缺少数据层灰度手段，只能全量切，出问题回滚成本高。 |
| **实验结果回收** | 新功能上线前要做实验（随机分或按标签人群），代码里埋逻辑、实验回收时专门人写一堆 SQL 结合前端埋点分析，流程重、周期长。 |
| **多分支开发** | 代码从 master 拉分支，测试库却共用一套。A 的测试数据污染 B 的环境，B 改的表让 A 的用例挂掉；   CI 跑不通、问题难复现、「本地能跑，一合就乱」。 |
| **多团队协作** | 风控、运营、算法同时改同一张表，「你先改还是我先改」协调成本高，冲突全靠人工对表、拉群沟通。 |
| **数据实验** | 标注、调参、重训直接动主表，风险大；改了多少、该不该合回，说不清，也不敢合。 |
| **跨环境同步** | 远端数据导回来，新增多少、有没有冲突，要手写一堆 SQL 才能对比，容易漏、容易错。 |
| **质量校验** | 预期表 vs 实际表，要写复杂 JOIN 才能发现不一致，回归测试成本高。 |

Agent 参与开发后暴露出的问题，以及多分支开发、多团队协作、实验回收，本质上都是同一个问题：数据变更缺少像代码一样的分支、对比和合并能力。

如果以上有一条戳中你，接着往下看。

seekdb 的做法：先 Fork 出副本，让 Agent 在副本上执行；再用 DIFF 看清差异；确认无误后，用 MERGE 按策略合回。把数据层也变成像 Git 一样可分支、可比对、可合并。

seekdb 1.2.0 带来了三个新的 SQL 原语——Fork Database、DIFF TABLE、MERGE TABLE——在数据库内核层面补齐了数据版本控制的完整闭环。配合此前已有的 Fork Table 能力，seekdb 构建起了从单表分支到整库克隆、从差异对比到策略合并的完整数据版本控制体系。

先看一个最贴近 coding agent 的例子：

```
-- 基线库：输入表 + 结果模板表CREATE TABLE test.orders_​input (id INT PRIMARY KEY, amount DECIMAL(10,2), status VARCHAR(20));INSERT INTO test.orders_​input VALUES(1, 100, 'pending'), (2, 200, 'pending');CREATE TABLE test.result_​template (id INT PRIMARY KEY, final_​amount DECIMAL(10,2));-- 每次测试先 Fork 一份隔离环境FORK DATABASE test TO test_​run;-- Agent 写的业务逻辑跑出来的实际结果FORK TABLE test_​run.result_​template TO test_​run.actual_​result;INSERT INTO test_​run.actual_​resultSELECT id, CASE WHEN amount >= 150 THEN amount * 0.9 ELSE amount ENDFROM test_​run.orders_​input;-- 人工或规则系统给出的预期结果FORK TABLE test_​run.result_​template TO test_​run.expected_​result;INSERT INTO test_​run.expected_​result VALUES(1, 100), (2, 180);-- 对比实际结果和预期结果DIFF TABLE test_​run.actual_​result AGAINST test_​run.expected_​result;
```

DIFF 没有输出，说明 Agent 写的逻辑符合预期；有输出，差异精确到具体行和具体值，可以直接反馈给 Agent 继续修改。同样的思路也适用于 Agent 改数据：先在 Fork 出的副本上执行修改，用 DIFF 审核改动，确认后 MERGE ... STRATEGY THEIRS 合回主表。

下面逐一介绍 Fork Database、DIFF TABLE、MERGE TABLE 三个原语的设计、语法和实战用法。

## **Fork Database：从单表分支到整库克隆**

**回顾 Fork Table**

seekdb 1.1.0 引入了 Fork Table，通过写时复制（Copy-on-Write）和快照隔离，实现了毫秒级的单表数据分支。一条 FORK TABLE t1 TO t1_​fork;，即可在一致性快照点上创建一个逻辑独立、完全可读写的表副本，源表和分支表互不干扰。

关于 Fork Table 的技术原理和使用方式，我们此前已有专文详细介绍，这里不再展开。

Fork Table 解决了单表沙箱问题。但真实业务往往不止一张表。

**Fork Database 解决什么新问题**

假设你有一个业务库 prod，里面有 orders、users、payments 三张表，它们之间通过外键关联。你想基于当前生产数据创建一个隔离的实验环境。

如果逐表 Fork，每张表的快照时刻可能不同——orders Fork 时 payments 还没写入最新一笔，导致实验库中订单和支付记录对不上。外键约束也无法跨表保持。

Fork Database 正是为了解决这个问题。它在指定时间点（系统自动选取的快照时刻）为源数据库创建一个完整的目标数据库副本，所有用户表共享同一快照版本。

**核心特性**

- 库级原子快照：目标库内所有表共享同一 fork 快照版本，多表关联查询、外键约束在 fork 后保持逻辑一致
- 数据库属性继承：目标库继承源库的字符集（charset）、排序规则（collation）等数据库级属性
- 库内外键完整保留：源库中表间外键关系（仅限库内引用）在目标库中完整保留并生效；跨库外键在 fork 时被跳过，不在目标库中创建
- 支持多次 fork：支持对同一源库多次 fork 到不同目标库，每次 fork 使用各自快照时刻，各目标库相互独立

**语法与示例**

```
FORK DATABASE source_​database TO destination_​database;
```

一个最小可执行的流程：

```
-- 创建库副本FORK DATABASE db_​src TO db_​fork;-- 目标库可查询USE db_​fork;SELECT count(*) FROM t1;-- 目标库可写入（写入不会影响源库 db_​src）INSERT INTO t1 VALUES(...);
```

也可以用于整库分支与版本管理：

```
-- 1) 基于主版本创建分支版本（数据基线为 fork 时刻快照）FORK DATABASE db_​prod TO db_​prod_​branch_​v2;-- 2) 在分支库上进行实验性变更-- ... 对 db_​prod_​branch_​v2 中的表执行 DML/业务流程 ...-- 3a) 快速回滚：停止使用分支版本DROP DATABASE db_​prod_​branch_​v2;
```

Fork 之后可以放心地改。但改了哪些行？值对不对？能不能合回主表？

没有 Diff & Merge，这些问题只能靠手写 JOIN 和对比 SQL 来回答。

## **DIFF TABLE 与 MERGE TABLE：看清改了什么，安全合回去**

Fork 出副本 → 在副本上改 → DIFF 看清差异 → MERGE 按策略合回。和 Git 的 branch、diff、merge 一一对应。

先看一个最小、可直接执行的例子：

```
CREATE TABLE demo.student_​base (id INT PRIMARY KEY, name VARCHAR(20), score INT);INSERT INTO demo.student_​base VALUES(1, 'Alice', 80), (2, 'Bob', 90), (3, 'Charlie', 70);FORK TABLE demo.student_​base TO demo.t_​current;FORK TABLE demo.student_​base TO demo.t_​incoming;UPDATE demo.t_​incoming SET score = 85 WHERE id = 1;DELETE FROM demo.t_​incoming WHERE id = 2;INSERT INTO demo.t_​incoming VALUES(4, 'David', 95);-- 先看差异DIFF TABLE demo.t_​incoming AGAINST demo.t_​current;-- 再决定是否合并；这里用 THEIRS 表示冲突时采用 incoming 版本MERGE TABLE demo.t_​incoming INTO demo.t_​current STRATEGY THEIRS;-- 合并后查看结果SELECT * FROM demo.t_​current ORDER BY id;
```

这个例子里会发生什么？

- id=1：两边都有，但 score 不同，属于冲突行
- id=2：只在 t_​current 里有，属于 current 独有
- id=3：两边完全相同，不会出现在 DIFF 结果里
- id=4：只在 t_​incoming 里有，属于 incoming 独有

预期 DIFF 输出：

```
_​_​table      _​_​flag  id  name     scoret_​current    INSERT   1  Alice    80t_​incoming   INSERT   1  Alice    85t_​current    INSERT   2  Bob      90t_​incoming   INSERT   4  David    95

```

执行 MERGE ... STRATEGY THEIRS 后，t_​current 预期变成：

```
id  name     score1   Alice    852   Bob      903   Charlie  704   David    95
```

**DIFF TABLE**

DIFF TABLE incoming_​db. incoming_​t  AGAINST current_​db. current_​t ;

**行为**：

- 语句顺序：固定为 incoming 在前、current 在后；这和结果集中同一主键下的展示顺序是两件事
- 只读：不修改任何表，仅输出差异结果集
- 对比规则：按主键逐行对比 current 与 incoming。主键相同、非主键列值不同视为冲突，两表各输出一行；主键仅在一表存在视为独有；主键和值都相同则不输出
- 结果排序：按主键排序，同一主键下 current 行排在前面
- NULL 比较：使用 <=> 运算符，NULL 与 NULL 视为相等
- 前置条件：两表列定义需完全一致（列名、类型、顺序），且均有主键

| 输出 | 含义 |
| 冲突 | 主键相同、值不同，两表各输出一行 |
| `current` 独有 | 仅 `current` 存在 |
| `incoming` 独有 | 仅 `incoming` 存在 |
| 完全相同 | 不输出 |

**MERGE TABLE**

```
MERGE TABLE incoming_​db.incoming_​t INTO current_​db.current_​t  STRATEGY {FAIL | THEIRS | OURS};
```

**行为**：

- 目标：将 incoming 的变更合并到 current 表，会修改 current 的数据
- 事务：在单个事务中执行，失败则自动回滚（包括 FAIL 策略检测到冲突时）
- 非冲突行（所有策略一致）：incoming 有而 current 无的行，插入到 current；current 有而 incoming 无的行，保留不动。MERGE 不删除任何行
- 冲突行（主键相同、值不同）：按所选策略处理——FAIL 报错回滚，THEIRS 用 incoming 覆盖，OURS 保留 current 不动

限制：暂不支持 schema 不同的表。DIFF 和 MERGE 要求两表列定义完全一致（列名、类型、顺序），否则会报错。

## **典型应用场景**

开篇展示了 Agent 写代码和 Agent 改数据两个场景。实际上，Fork + DIFF + MERGE 的组合能覆盖更多数据工作流。下面逐一展开。

**场景 1：AI Agent 写代码→测试→Debug 闭环**

开篇的 coding agent 示例已经展示了完整流程（参见上文 SQL）。这里补充做法要点：

做法：先准备输入表和结果模板 → 每轮测试 Fork 一份隔离库 → 在隔离库里再 Fork 两张结果表 → 表 A 执行业务逻辑（AI 写的代码）→ 表 B 用 SQL 写入预期结果 → DIFF 对比 A 与 B → 无差异则通过，有差异则把差异点反馈给 Agent 改代码，重新跑测试。

价值：AI 写代码 → 自动化测试 → 差异可定位 → 反馈 Agent 修改 → 再测，完成「写代码→测试→debug」闭环。DIFF 没有输出即通过，有输出则冲突行 = 值不对，独有行 = 多了或少了一条，差异精确到行和值，可直接回传给 Agent。

场景 2：AI Agent 开发与上线（数据修改）

内核：与场景 1 不同，这里 Agent 改的不是代码而是数据本身（如标注、清洗）。需要验证改动是否符合预期，并支持灰度上线。

做法：Agent 在 Fork 出的库或表上改；自测时 DIFF 验证数据变动；上生产时 Fork 灰度副本 → 小流量 → DIFF 检查 → MERGE 切回。

```
-- Agent 开发：先 Fork 一份实验库，在副本上改FORK DATABASE prod TO exp_​labeled_​data_​v2;-- Agent 执行标注逻辑，写入 exp_​labeled_​data_​v2.labeled_​data ...-- 自测：DIFF 验证改动是否符合预期DIFF TABLE exp_​labeled_​data_​v2.labeled_​data AGAINST prod.labeled_​data;-- 上生产：Fork 灰度库，切小流量，跑一段时间后FORK DATABASE prod TO prod_​gray;-- 小流量写入 prod_​gray.labeled_​data ...-- 检查无异常后DIFF TABLE prod_​gray.labeled_​data AGAINST prod.labeled_​data;MERGE TABLE prod_​gray.labeled_​data INTO prod.labeled_​data STRATEGY THEIRS;
```

整个流程和代码的 PR Review → Merge 一一对应：Fork 是建分支，DIFF 是看 diff，MERGE 是合并。数据变更也有了 Code Review 的能力。

**场景 3：实验结果回收与分析**

**内核** ：新功能上线前要做实验（随机分流或按标签人群），传统做法是在代码里埋逻辑，实验回收时专门人写一堆 SQL 结合前端埋点做分析，流程重、周期长。用 DIFF 可以直接对比实验组与对照组的结果表，差异行里带 age、标签等维度，一眼看出「哪个年龄段/人群差异大」。

**做法** ：先基于同一份基线用户集 Fork 出实验组和对照组两张表（结构相同，含 user_​id、age、标签等）；实验流量分别写入这两个副本；DIFF 对比两表，冲突行 = 同一用户在不同组的行为差异，独有行 = 仅在一组出现的用户；差异行里带 age 等信息，可直接分析「点击这个按钮的人哪个年龄段比较多」

```
-- 基线用户集：先落一份对照组数据CREATE TABLE exp.clicks_​base (user_​id INT PRIMARY KEY, age INT, click_​count INT);INSERT INTO exp.clicks_​base VALUES(1, 25, 2), (2, 35, 4), (4, 45, 1);-- 基于同一份基线 Fork 出实验组和对照组FORK TABLE exp.clicks_​base TO exp.control_​clicks;FORK TABLE exp.clicks_​base TO exp.experiment_​clicks;-- 实验组写入新按钮流量后的结果UPDATE exp.experiment_​clicks SET click_​count = 3 WHERE user_​id = 1;UPDATE exp.experiment_​clicks SET click_​count = 5 WHERE user_​id = 2;DELETE FROM exp.experiment_​clicks WHERE user_​id = 4;INSERT INTO exp.experiment_​clicks VALUES(3, 25, 2);-- DIFF 对比：差异行直接带 age，可分析「哪个年龄段在实验组有更多点击」DIFF TABLE exp.experiment_​clicks AGAINST exp.control_​clicks;-- 冲突行 = 同一 user_​id 在两组的 click_​count 不同（如 user_​id=1: 2 vs 3）-- 独有行 = 仅在一组出现的用户（如 user_​id=3 实验组独有，user_​id=4 对照组独有），含 age-- 差异行自带 age，按 age 聚合即可回答「哪个年龄段差异大」
```

**价值** ：实验回收不用再写一堆 JOIN 和对比 SQL，DIFF 直接给出差异，差异行自带维度信息，分析效率提升。

**场景 4：多分支开发——拉分支同时 Fork 新库**

**内核** ：代码从 master 拉分支，测试库却共用一套。A 的测试数据污染 B 的环境，B 改的表让 A 的用例挂掉；CI 跑不通、问题难复现、「本地能跑，一合就乱」。

**做法** ：拉新分支时 Fork 新库，每人独享；随时 DIFF 查看改动；合并代码时 MERGE 合回数据。

```
-- 开发者 A：Fork 并修改FORK DATABASE test_​db TO test_​db_​feature_​a;UPDATE test_​db_​feature_​a.orders SET status = 'shipped' WHERE id = 1;INSERT INTO test_​db_​feature_​a.orders VALUES(10, 100, 99.9, 'pending');-- 合并前查看改动DIFF TABLE test_​db_​feature_​a.orders AGAINST test_​db.orders;-- 确认后合回MERGE TABLE test_​db_​feature_​a.orders INTO test_​db.orders STRATEGY THEIRS;
```

**场景 5：多团队并行改同一份数据**

**内核** ：风控、运营、算法同时改同一张表，「你先改还是我先改」协调成本高，冲突全靠人工对表、拉群沟通。

**做法** ：风控、运营、算法各 Fork 分支，各自改完；DIFF 检查冲突，按业务选 THEIRS 或 OURS 合并。

```
-- 风控线 Fork 并修改FORK DATABASE prod TO prod_​riskfix;UPDATE prod_​riskfix.orders SET risk_​flag = 1 WHERE order_​id = 10002;-- 活动线 Fork 并修改FORK DATABASE prod TO prod_​promo;UPDATE prod_​promo.orders SET amount = amount * 0.9 WHERE order_​id IN (10001, 10002);-- 先合风控MERGE TABLE prod_​riskfix.orders INTO prod.orders STRATEGY THEIRS;-- 再合活动（若有冲突，选 THEIRS 或 OURS）DIFF TABLE prod_​promo.orders AGAINST prod.orders;MERGE TABLE prod_​promo.orders INTO prod.orders STRATEGY THEIRS;
```

每个团队在自己的分支里工作，互不干扰。合并时 DIFF 清楚地列出冲突行，按业务规则选择策略，不再需要人工逐行对比。

**场景 6：AI 模型训练与标注迭代**

**内核** ：与场景 2 类似，但侧重模型训练迭代。标注、调参、重训直接动主表，风险大；改了多少、该不该合回，说不清，也不敢合。

**做法** ：Fork 实验库，在副本上做标注、调参，主表不动；DIFF 量化改动，验证通过后 MERGE 合回。

```
-- Fork 出实验库FORK DATABASE prod TO exp_​training_​data_​v2;-- 在 exp_​training_​data_​v2.training_​data 上做新标注、调参 ...-- 查看改动量DIFF TABLE exp_​training_​data_​v2.training_​data AGAINST prod.training_​data;-- 验证通过后合回MERGE TABLE exp_​training_​data_​v2.training_​data INTO prod.training_​data STRATEGY THEIRS;
```

**场景 7：数据同步与跨环境合并**

**内核** ：远端数据导回来，新增多少、有没有冲突，要手写一堆 SQL 才能对比，容易漏、容易错。

**做法** ：先 Fork 一本地工作库，再把远端快照导入工作库中的隔离表；DIFF 预览新增与冲突，MERGE 按策略合入。

```
-- 先 Fork 一本地工作库FORK DATABASE local TO sync_​workspace;-- 远端数据已导入 sync_​workspace.remote_​snapshot-- 预览差异DIFF TABLE sync_​workspace.remote_​snapshot AGAINST local.main_​table;-- 以远端为准合入MERGE TABLE sync_​workspace.remote_​snapshot INTO local.main_​table STRATEGY THEIRS;-- 或以本地为准，只补新增MERGE TABLE sync_​workspace.remote_​snapshot INTO local.main_​table STRATEGY OURS;
```

**场景 8：数据质量校验与审计**

**内核** ：预期表和实际表的对比是质量校验和回归测试中的常见需求。传统做法是写复杂的 JOIN 和 CASE WHEN 来发现不一致，维护成本高，容易漏。

**做法** ：先 Fork 一份待审数据副本，再用一条 DIFF 发现预期表与实际表的差异。

```
-- 预期表 qa.expected，待审副本 qa.actual_​review 由生产表 Fork 而来FORK TABLE prod.actual TO qa.actual_​review;DIFF TABLE qa.actual_​review AGAINST qa.expected;-- 有输出即表示存在不一致（冲突=值不同，独有=缺失或多余）
```

一条 DIFF 替代一堆 JOIN，差异结果精确到行和值，冲突行代表值不同，独有行代表缺失或多余。

## 落地建议

- 先小表试流程，再扩到核心链路
- 把 DIFF 当评审材料，数据变更也能 Code Review
- 关键表要有主键——DIFF 和 MERGE 依赖主键做逐行对比
- 分支表命名要能一眼看懂用途（如 prod_​riskfix、exp_​training_​data_​v2）

## 告别手写 JOIN，数据变更也能 Code Review

| 以前怎么做 | seekdb 怎么做 |
| 直接在生产表上改，改坏了靠人肉回滚 | Fork 出副本再改，主表不动，改坏了 DROP 即可 |
| 手写一堆 JOIN + CASE WHEN 对比两张表 | 一行 `DIFF TABLE`   输出所有差异，精确到行和值 |
| 冲突靠拉群沟通、人工逐行对表 | `MERGE TABLE`   内置三种策略，冲突处理在一条 SQL 里完成 |
| AI Agent 直接写生产库，出了问题说不清 | Agent 写副本，人工 DIFF 审核后再 MERGE 合回 |

从 seekdb 1.1.0 的 Fork Table，到 1.2.0 的 Fork Database + DIFF TABLE + MERGE TABLE，seekdb 正在系统性地构建数据库内核级的版本控制能力。

这套能力的设计思路很明确：

- Fork（Table / Database）提供分支能力——安全地创建隔离副本
- DIFF TABLE 提供对比能力——精确地看清差异
- MERGE TABLE 提供合并能力——按策略安全地合回

三者组合，形成了和 Git 的 branch / diff / merge 对应的数据版本控制闭环。当数据像代码一样频繁迭代—— **可观测、可控制** 的变更路径，才能让数据管理更安全、可审计、易协作。

![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*5Oy_QZjvTg8AAAAAAAAAAAAADiGDAQ/original)
