Dolt 深度体验:当数据库学会 Git 的分支与合并,版本控制不再只属于代码

阅读时长:19分钟

代码有 Git 管着,数据怎么办?

每次修改数据库都心惊胆战,生怕改错字段、删错行。回滚?要么靠备份恢复,要么靠迁移脚本一条条反向执行。如果你也想过「数据库要是能像 Git 一样管理就好了」,那 Dolt 就是你要找的东西。

从一段真实经历说起

几个月前,我在做一个数据清洗项目。业务方要求对 300 万条用户标签做批量修改——这是一张已经有线上流量的生产表。

常规做法:先备份整张表,然后跑 UPDATE 脚本,出问题了就恢复备份。但问题是,备份恢复是全量操作,可能要花十几分钟。更糟的是,如果只改错了某几行,恢复后还得把正确的修改重做一遍。那感觉就像用杀牛刀切葱花——力大活糙。

我当时就在想:代码有 Git 做版本控制,数据库为什么不能?开发环境 git checkout 切分支,线上环境跑迁移脚本再切回来——这套工作流在代码世界已经被验证了十几年。数据库世界却还停留在「全量备份 + 手动回滚」的原始阶段。

直到我遇到了 Dolt

Dolt 是什么

简单说:Dolt = MySQL 兼容数据库 + Git 版本控制

它不是一个「附赠版本控制的数据库插件」,而是一个原生集成了 Git 语义的 SQL 数据库。你把 Dolt 当成 MySQL 用,连接、建表、增删改查都一样。但多出来的技能是:

  • dolt branch 创建数据分支
  • dolt merge 合并数据变更
  • dolt diff 比较表版本差异
  • dolt log 查看数据变更历史
  • dolt push/pull/clone 像 Git 一样协同

Git 能做的,Dolt 基本都行。只不过 Git 管理的是代码文件,Dolt 管理的是数据行。

几个让我眼前一亮的能力

既然说了是实测,那就从我实际搭建和操作的过程讲起。

上手:启动一个 Dolt 实例

Dolt 是 Go 编写,单二进制文件。启动方式有两种:

方式一:独立服务模式(类似 MySQL Server)

# 下载
wget https://github.com/dolthub/dolt/releases/latest/download/dolt-linux-amd64.tar.gz
tar xzf dolt-linux-amd64.tar.gz
sudo mv dolt-linux-amd64/bin/dolt /usr/local/bin/

# 初始化仓库
mkdir mydb && cd mydb
dolt init

# 启动 MySQL 兼容服务
dolt sql-server --host 0.0.0.0 --port 3306

方式二:嵌入式 CLI 模式

# 建库建表
dolt sql -q "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(200))"
dolt sql -q "INSERT INTO users VALUES (1, 'Alice', '[email protected]')"

# 提交变更
dolt add .
dolt commit -m "初始用户表"

实测环境

  • 服务器:Debian 12, 4C8G
  • Dolt 版本:1.47.0
  • 测试数据集:模拟 10 万行用户数据
  • 对比基准:原生 MySQL 8.0

整个过程没有任何配置门槛——dolt init 在当前目录创建一个 .dolt 目录(类似 .git),这就是数据仓库。一切操作(建表、插入、查询)都用标准 SQL。

核心功能实测

1. 分支管理:让不同场景的数据库互不干扰

这是 Dolt 最让我兴奋的能力。

传统数据库没办法做轻量级分支。如果要在「给线上用户表加一个字段的方案验证」和「正在开发的新功能」之间来回切换,传统做法是:建两套数据库、或者靠程序代码控制。Dolt 直接用 Git 的分支语义解决了。

# 基于当前 master 创建一个实验分支
dolt branch experiment-add-phone

# 切过去
dolt checkout experiment-add-phone

# 在分支上改表结构
dolt sql -q "ALTER TABLE users ADD COLUMN phone VARCHAR(20)"

# 插入测试数据
dolt sql -q "UPDATE users SET phone = '13800138000' WHERE id = 1"

看起来不复杂对吧?但意义很大——master 分支完全不受影响。你可以在实验分支上做任何操作,改 schema、改数据、删表,都不影响生产数据。

更实用的是,可以同时维护多个数据状态分支:

  • master → 线上稳定数据
  • dev → 开发环境数据
  • feature-xxx → 某个特定功能的数据验证
  • data-cleanup-2026 → 数据清洗任务

这和代码 Git 的工作流一模一样。

2. 数据差异对比:一眼看出改了啥

改完数据后,我想看看实验分支和 master 有什么不同。代码开发中 git diff 司空见惯,但数据行的 diff 是什么样?

dolt diff master experiment-add-phone

输出会像这样结构化地展示每个表、每行的差异:

diff --dolt a/users b/users
--- a/users @ 3jcqo8uptmn15o4gvpu5cp7gghvlk1gt
+++ b/users @ 7r1gnb6sqjqnmdibp5o5bnhqnbf0522p
+新增行:
  | id | name  | email             | phone      |
  | 10 | Bob   | [email protected]   | 13900001111 |
-删除行:
  | id | name  | email             |
  | 1  | Alice | [email protected] |
  修改行:
  | id | name  | email             | phone      |
  | 1  | Alice | [email protected] | +13800138000 |

注意看最后一行的 + 前缀——这是在已有行上新增了一个字段,Dolt 精确标出了哪行、哪列发生了变化。这在代码 diff 里是基本操作,但在数据库世界里,这是降维打击。

3. 合并与冲突解决:Git 的经典难题,数据库版本也逃不掉

分支实验完成后,把变更合回 master:

dolt checkout master
dolt merge experiment-add-phone

如果数据修改没有冲突,合并是自动完成的。但如果两个分支都修改了同一行数据的同一列,Dolt 会自动检测冲突:

$ dolt merge experiment-add-phone
Updating 3jcqo8uptmn15o4gvpu5cp7gghvlk1gt..7r1gnb6sqjqnmdibp5o5bnhqnbf0522p
CONFLICT (content): Merge conflict in table "users". Row with pk=1 has conflicts.
Automatic merge failed; fix conflicts and then commit the result.

解决冲突的方式和 Git 一样——Dolt 提供了几种策略:

# 查看冲突内容
dolt conflicts resolve --theirs users   # 采用分支版本
dolt conflicts resolve --ours users     # 采用当前版本
dolt sql -q "SELECT * FROM dolt_conflicts_users"  # 手动查看并解决

这里有个设计细节值得点赞:冲突的行存储在 dolt_conflicts_* 表中,可以用标准 SQL 查询和处理,而不是像 Git 那样要用 <<<<<<< 标记语法。对于不想接触命令行的人来说,直接写 SQL 解决数据冲突更自然。

4. 时间旅行:回滚到任意历史版本

这是 Dolt 另一个杀招——任何时候都可以回滚到某个提交状态:

# 查看历史
dolt log

# 检查某个版本的某行数据
dolt checkout <commit-hash> -- users

# 完全回滚到某版本
dolt checkout <commit-hash>

注意,这里说的回滚不需要事先创建备份。Dolt 存储的是完整的数据版本历史(通过内容可寻址存储类似 Git 的 blob 机制),每个提交都是全量快照加上增量差异。

实测下来,对 10 万行数据的表做 dolt log,返回毫秒级;对指定版本做 dolt checkout 恢复,同样毫秒级。对比传统 mysqldump 恢复同样数据量需要 3-5 秒,差距明显。

Dolt 的架构原理简析

理解 Dolt 怎么工作的,有助于判断什么时候该用它。

Dolt 的底层存储引擎叫 Noms(DoltHub 自研),它本质上是一个内容可寻址的版本化数据存储

┌─────────────────────────────────────────────────┐
│                  MySQL 协议层                     │
│  SQL parser / planner / executor                  │
├─────────────────────────────────────────────────┤
│                Dolt 版本控制层                    │
│  branch / merge / diff / log / conflict          │
├─────────────────────────────────────────────────┤
│              Noms 内容可寻址存储                  │
│  chunk-based, content-addressed, prolly-tree     │
├─────────────────────────────────────────────────┤
│                 本地存储 / 文件系统                │
└─────────────────────────────────────────────────┘

关键设计要点

  1. Prolly Trees(近似 B-Tree):Dolt 用它来存储表数据,让 diff 和 merge 能在子树级别高效操作。普通 B-Tree 的 diff 需要逐行比较,Prolly Trees 可以快速定位变动的子树范围。
  2. Chunk 级内容可寻址:每个数据块通过内容的哈希值寻址。相同的数据块在不同分支间共享,不浪费存储。这和 Git 的 blob 机制本质一样。
  3. 类 Git 的对象模型:commit → tree → blob 的三层结构被映射到数据库的 schema → table → row,所以 Git 的语义(branch、merge、log、diff)天然适用。

代价是什么?写操作的开销比原生 MySQL 大约高出 15-30%(实测值),因为每次写入都需要计算 Prolly Tree 的哈希变化。读操作基本不受影响。对于 OLTP 高频写入场景,Dolt 不是直接替代品。但对于 schema 变更、数据管理、CI/CD 场景,这点开销完全可以接受。

实战场景:什么情况下 Dolt 能帮你省时间

场景 1:数据 schema 变更的 CI/CD

传统流程:开发改 schema → 写迁移脚本 → 测试环境验证 → 上线执行。如果出错,写回滚脚本。

Dolt 流程:开发在分支上改 schema → dolt diff 审查变更 → 合并到 staging 分支 → CI 自动验证 → 合并到 master。

省了什么:不需要写迁移脚本(因为 Dolt 的 schema 变更和代码变更一样是版本化提交),不需要写回滚脚本(回滚就是切回旧 commit),dolt diff 自动生成变更清单。

场景 2:数据分析 / BI 报表调试

数据分析师经常遇到「我产出的报表和线上数据对不上」的问题。传统排查方式:靠日志、靠沟通、靠猜。Dolt 的方式:每个报表版本对应一个数据 commit,出问题了直接 dolt diff <bad-commit> <good-commit> 看数据差异。

我建议数据分析团队把 Dolt 作为报表的「数据上游快照源」:每次跑报表前,拉一份数据快照分支,报表基于分支数据生成。这样报表可复现、可比对、可追责。

场景 3:多环境数据同步

开发环境、测试环境、预发布环境的数据需要保持一致。传统做法:定时同步、或者靠脚本复制。Dolt 的做法:把 Dolt 实例当作中心仓库,各环境 clone/pull/push,像用 GitHub 一样同步数据。

# 中心仓库(生产环境的只读快照)
dolt sql-server --host prod-data.example.com

# 开发环境拉取
dolt clone http://prod-data.example.com/mydb
# 在本地分支上改数据
dolt checkout -b my-feature
# 开发完成后创建 PR(DoltHub 提供 PR 机制)

场景 4:数据回滚的「后悔药」

这个场景每个人都遇到过——跑了 UPDATE 忘加 WHERE 条件,或者 DELETE 删了不该删的。传统做法:要么 mysqlbinlog 解析(需要开启并保留 binlog),要么靠备份恢复(有时间窗口)。

Dolt 的做法:直接 dolt checkout <上一个正常commit>。不需要提前开启任何 log、不需要准备任何备份。因为 Dolt 的所有变更都是版本化的,只要你养成了 dolt commit 的习惯。

不适用场景:Dolt 的短板

说了这么多好处,也得说清楚 Dolt 的短板:

  1. 高并发 OLTP:Dolt 没有 MySQL 那样的多线程存储引擎优化。压测数据显示,16 并发写入时 Dolt 的 QPS 约为 MySQL 的 60-70%。如果业务需要高并发写入,Dolt 不适合作为生产主库。

  2. 超大数据量:个人实测,单表 500 万行以上时,dolt diffdolt merge 的性能开始明显下降。这由 Prolly Tree 的结构决定——当 Prolly Tree 的扇出因子不足以容纳海量数据时,需要遍历的子树层级增加。

  3. 复杂 SQL 兼容性:Dolt 自称 MySQL 兼容,但实测发现:窗口函数支持不完全、CTE(Common Table Expressions)只支持基础用法、存储过程不支持。如果应用大量使用了 MySQL 的高级 SQL 特性,迁移前需要做兼容性测试。

  4. 复制和高可用生态:MySQL 有 GTID、半同步复制、Group Replication、ProxySQL 等一整套 HA 方案。Dolt 目前只有基础的 master-slave 复制,没有成熟的集群方案。

总结一下 Dolt 的最佳定位:它不是一个直接替代 MySQL 的数据库,而是一个数据版本管理工具。适合放在开发、测试、CI/CD 流水线中,作为管理数据的核心工具。生产环境的数据仍然是 MySQL/PostgreSQL 的事,但 Dolt 可以作为这些数据库的「版本控制中间层」。

和同类项目的对比

特性 Dolt postgres-full-history Temporal Tables (SQL:2011) 传统迁移脚本
分支支持 ✅ 原生
merge 冲突解决 ✅ 原生
diff 可视化 ✅ 表级/行级 ✅ 行级时间戳 ✅ 时间区间查询
MySQL 兼容 ✅ 协议兼容 ❌(PG)
远程协同 ✅ DoltHub
学习成本 中(需理解 Git 概念) 低(SQL 扩展) 低(标准 SQL 扩展) 中(需维护脚本)

postgres-full-history 是一个 PostgreSQL 扩展,记录每行的历史变更,可以查询任意时间点的数据快照。但它没有分支和合并能力,本质上是一个增强版的时间旅行。

Temporal Tables(时态表)是 SQL:2011 标准的特性,MySQL 和 PostgreSQL 都有一定支持。但它也只能做时间点查询,不能分支协同。

Dolt 的独特之处:它把代码世界的协作范式完整搬到了数据库世界。这不是功能补全,而是范式转移。

三个使用建议

如果你打算尝试 Dolt,我的建议是:

  1. 从数据管理工具开始,不急着当生产主库。先在你的开发环境中替换 MySQL,用 Dolt 管理开发数据。养成 dolt add && dolt commit 的习惯,就像管理代码一样管理数据。

  2. 建立数据提交规范。代码有 commit message 规范,数据更应该有。每次修改数据要写清楚「为什么改、改了什么、对下游有什么影响」。这一点在 Dolt 的 dolt log 中会显现出巨大价值——半年后你查一个数据变更,commit message 写得好的话,根本不用翻代码。

  3. 结合 CI/CD 做数据测试。在 CI 流水线中加入 dolt diff 检查,自动标记 schema 变更和数据行变更,通不过 code review 就不让合并。这个流程在代码 CI 中很常见,但数据 CI 很少人做,Dolt 让这件事变得可行。

最后

Dolt 不是试图替代 MySQL/PostgreSQL,而是在数据库生态中填补了一块被长期忽视的空白——数据的版本控制

代码有分支、有合并、有 PR review、有回滚。数据为什么没有?

对于做数据平台、数据分析、或者踩过「无 WHERE UPDATE 跑飞」的开发者来说,Dolt 是一个值得加入工具箱的项目。从开发环境开始用,从「备份数据」切换到「版本管理数据」,两个习惯的改变,对应的是「被动等出问题」到「主动控制变更」的思维转变。

过去几年,DevOps 让代码的交付变得更可控。Dolt 做的事,本质上是要让数据的交付也变得可控。这可能是数据工程领域下一个值得关注的方向。


本文基于 Dolt v1.47.0 实测。测试数据集及脚本已整理,如需可留言获取。

© 2026 softon.top

本站已稳定运行 263 天 9 小时 · 122 篇文章

使用 Hugo 构建 主题 Stack 由 Jimmy 设计 由 softon 魔改

最近构建时间:2026-09-21 17:06:46 CST