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_tSTRATEGY {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 对应的数据版本控制闭环。当数据像代码一样频繁迭代——可观测、可控制的变更路径,才能让数据管理更安全、可审计、易协作。
