Dolt 2.0 深度拆解:当 Git 决定「吞掉 SQL」——从 Prolly 树到向量版本控制,一个 17K Star 的数据库如何重新定义数据管理的终极范式
如果 Git 可以管理代码的每一次变更,为什么数据库不行?
引言:数据库世界的「版本控制之痛」
2026 年 7 月底,DoltHub 发布了 Dolt 2.0,这个被称为「Git for Data」的开源项目迎来了三年来最重要的一次大版本更新。从自动垃圾回收到归档压缩,从自适应存储到向量版本控制——Dolt 2.0 不是一次简单的功能迭代,而是对「数据管理应该是什么样子」这个问题的一次彻底回答。
作为一名写了十几年代码的程序员,我对「版本控制」这个概念有着天然的亲近感。Git 让代码协作变得优雅,每一次 commit 都是一个可追溯的快照,每一次 branch 都是一个安全的实验空间。但当我把目光转向数据库——这个承载着业务核心数据的基础设施——却发现它仍然停留在「一个写操作覆盖上一个写操作」的原始时代。
传统的数据库备份方案,本质上是一种「时间点快照」——你可以在某个时刻恢复整个数据库,但你无法精确地知道「哪一行数据在什么时候被谁改过」。你无法像 git diff 一样看到两次变更之间的差异,也无法像 git merge 一样安全地合并两个开发者的并行修改。
Dolt 的出现,就是为了填补这个巨大的鸿沟。
一、Dolt 的核心哲学:数据库即代码
1.1 从「Git for Data」说起
Dolt 的口号是「Git for Data」。这不是一个营销噱头,而是一种深刻的设计哲学。让我们看看 Git 和 Dolt 的核心操作对照:
| Git 操作 | Dolt 操作 | 语义 |
|---|---|---|
git clone | dolt clone | 完整复制整个数据库(含历史) |
git branch | dolt branch | 创建数据分支,隔离实验性修改 |
git commit | dolt commit | 提交一组变更,生成唯一哈希 |
git merge | dolt merge | 合并两个分支的数据变更 |
git diff | dolt diff | 查看任意两次提交之间的行级差异 |
git push/pull | dolt push/pull | 远程同步,支持协作 |
git log | dolt log | 查看完整的变更历史 |
git checkout | dolt checkout | 切换到某个历史版本的数据状态 |
这套操作的意义在于:数据不再是「一次写入、永久覆盖」的黑箱,而是变成了一个可追溯、可分支、可合并、可协作的版本化资产。
1.2 为什么传统方案不够用?
在 Dolt 之前,我们也有一些「数据版本控制」的方案:
数据库快照(Snapshot):MySQL 的 mysqldump、PostgreSQL 的 pg_dump,本质上是某个时间点的全量备份。问题在于:存储开销大(每次备份都是全量),恢复粒度粗(只能恢复整个数据库),无法看到变更差异。
CDC(Change Data Capture):Debezium、Maxwell 等工具可以捕获数据库的变更流。CDC 解决了「知道谁在什么时候改了什么」的问题,但它是一个流式管道,不是存储层的原生能力。你仍然需要额外的基础设施来存储和查询这些变更历史。
事件溯源(Event Sourcing):在应用层实现事件溯源,把每次状态变更记录为不可变事件。这是一种优雅的架构模式,但它要求整个应用都按照事件溯源的方式重构——对于已有的关系型数据库应用来说,迁移成本极高。
Dolt 的不同之处在于:它把版本控制做进了数据库引擎的内核。你不需要改变应用的架构,不需要引入额外的基础设施,只需要把 MySQL 换成 Dolt,就能获得完整的版本控制能力。
二、架构深潜:Prolly 树——Dolt 的技术心脏
2.1 什么是 Prolly 树?
Dolt 的核心数据结构是 Prolly 树(Probabilistic B+ Tree)。这是 Dolt 区别于其他数据库的最关键技术创新。
传统的 B+ 树是一种确定性的平衡搜索树——给定一组数据,B+ 树的结构是唯一的。这意味着:即使你只修改了一行数据,整棵树的结构也可能发生变化(因为节点分裂/合并),导致大量的数据重写。
Prolly 树引入了一个巧妙的变化:它在确定树结构时引入了概率性。通过哈希函数决定节点的分裂点,Prolly 树实现了:
- 内容寻址(Content-Addressable):每个节点的标识是其内容的哈希值。内容不变,标识不变。
- 结构共享(Structural Sharing):两个版本之间,未修改的节点可以直接共享,无需复制。这使得
dolt diff和dolt merge的开销极低——只需要比较和合并哈希值不同的节点。 - 行级版本控制:每次提交,只有实际修改的行所在的路径需要重写。一棵包含百万行数据的表,修改一行数据的提交可能只需要重写几十个节点。
2.2 内容寻址的魔法
让我们用一个具体的例子来理解 Prolly 树的工作原理。
假设我们有一张用户表:
| id | name | email | age |
|----|-------|-----------------|-----|
| 1 | Alice | alice@test.com | 30 |
| 2 | Bob | bob@test.com | 25 |
| 3 | Carol | carol@test.com | 28 |
在 Dolt 中,这棵树的每个节点都有一个基于内容计算的哈希值。当我们执行:
UPDATE users SET age = 31 WHERE id = 1;
只有 Alice 所在的叶子节点发生了变化,其哈希值也随之改变。沿着叶子到根的路径上,每个父节点的哈希值也相应更新。但 Bob 和 Carol 所在的节点完全不变,直接被新版本的树引用。
这意味着:
- 存储:两个版本共享了未修改节点的物理存储,节省了大量磁盘空间。
- Diff:比较两个版本只需要遍历哈希值不同的路径,时间复杂度与变更量成正比,而非数据总量。
- Merge:合并两个分支时,只需要处理它们各自修改的路径。如果修改没有冲突(修改了不同的行),合并是自动完成的。
2.3 提交图(Commit Graph)
Prolly 树负责存储数据的版本化状态,而提交图负责记录版本之间的关系。每次 dolt commit 都会创建一个新的提交节点,包含:
- Commit Hash:提交的唯一标识(类似 Git 的 commit SHA)
- Parent Hash:父提交的哈希(支持合并提交有两个父节点)
- Author / Timestamp:提交者和时间戳
- Message:提交描述
- Tree Hash:指向 Prolly 树根节点的哈希
这构成了一个有向无环图(DAG),和 Git 的提交历史完全同构。你可以在这个 DAG 上执行 dolt log 查看历史、dolt diff 比较版本、dolt merge 合并分支。
三、Dolt 2.0 的四大核心升级
3.1 自动垃圾回收(Auto GC)
问题背景:Dolt 采用写时复制(Copy-on-Write)机制,每次提交都会创建新的节点。在写入密集的场景(如批量导入)中,大量中间状态的节点会成为「垃圾」——它们不被任何提交引用,但仍然占据磁盘空间。
在 1.x 版本中,你需要手动执行 dolt gc 来清理垃圾。这对于生产环境来说不够友好。
2.0 的解决方案:默认启用自动垃圾回收。后台线程会定期扫描提交图,识别并清理不再被引用的节点。你可以通过配置控制 GC 的频率和策略:
-- 查看 GC 状态
SELECT * FROM dolt_gc_status();
-- 手动触发一次完整 GC
CALL dolt_gc();
-- 配置 GC 策略(自动模式下默认启用)
SET @@persist.dolt_gc_mode = 'auto';
3.2 归档压缩(Archive Compression)
问题背景:即使有 Prolly 树的结构共享,Dolt 的存储开销仍然比传统数据库大——因为它需要保存完整的历史记录。对于写入密集的场景,磁盘占用可能增长到数据量的数倍。
2.0 的解决方案:引入了名为 archives 的新存储格式。核心思路是字典压缩 + 存储去重:
- 从提交图中识别可压缩的历史段落
- 对这些段落中的数据进行字典编码压缩
- 在压缩后的数据上构建去重索引
根据 DoltHub 的官方数据,归档压缩可以将存储占用减少 30% 至 50%。
-- 查看存储使用情况
SELECT * FROM dolt_storage_stats();
-- 手动触发归档压缩
CALL dolt_archive();
-- 查看压缩率
SELECT
raw_size,
compressed_size,
ROUND(compressed_size * 100.0 / raw_size, 1) as compression_ratio
FROM dolt_archive_stats();
3.3 自适应存储(Adaptive Storage)
Dolt 2.0 引入了自适应存储策略,根据表的访问模式自动选择最优的存储布局:
- 热数据:使用较浅的 B+ 树层级,减少读取时的 I/O 次数
- 冷数据:使用更深的压缩层级,节省存储空间
- 写入密集型表:优化写缓冲区,减少写放大
这是一个透明的优化——你不需要手动配置,Dolt 会根据实际的读写模式自动调整。
3.4 向量版本控制(Vector Version Control)
这是 Dolt 2.0 最具前瞻性的特性。基于 MariaDB 的 Vector 数据类型,Dolt 实现了向量数据的版本控制。
在 AI 时代,向量数据库已经从「新奇技术」变成了「基础设施」。Embedding、RAG、语义搜索——这些场景都需要存储和查询高维向量。但传统的向量数据库(如 Pinecone、Milvus)不支持版本控制。
Dolt 2.0 填补了这个空白:
-- 创建带向量列的表
CREATE TABLE documents (
id INT PRIMARY KEY,
content TEXT,
embedding VECTOR(1536) COMMENT 'OpenAI text-embedding-3-small'
);
-- 插入向量数据
INSERT INTO documents VALUES
(1, 'Dolt is Git for Data', '[0.1, 0.2, ..., 0.9]'),
(2, 'Prolly trees enable version control', '[0.3, 0.4, ..., 0.7]');
-- 提交
dolt commit -m "Add initial documents with embeddings"
-- 修改一条数据
UPDATE documents SET content = 'Dolt 2.0 is Git for Data' WHERE id = 1;
-- 提交
dolt commit -m "Update document title"
-- 查看向量数据的变更历史
SELECT * FROM dolt_log('documents');
这意味着你可以:
- 回滚错误的 embedding:如果新版本的 embedding 模型生成了质量更差的向量,可以一键回滚到之前的版本
- A/B 测试 embedding 模型:在不同分支上使用不同的 embedding 模型,通过
dolt merge合并结果 - 审计向量变更:完整记录每次向量数据的修改历史,满足合规要求
四、实战:从零开始使用 Dolt 2.0
4.1 安装与初始化
# macOS 安装
brew install dolt
# Linux 安装
curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash
# 初始化一个新数据库
mkdir my-project && cd my-project
dolt init
# 查看仓库状态
dolt status
4.2 作为 MySQL 兼容服务器运行
# 启动 Dolt SQL Server(MySQL 协议兼容)
dolt sql-server --host 0.0.0.0 --port 3306
# 用标准 MySQL 客户端连接
mysql -h 127.0.0.1 -P 3306 -u root
连接后,你就可以像使用 MySQL 一样使用 Dolt:
-- 创建表
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
product_name VARCHAR(255),
amount DECIMAL(10, 2),
status ENUM('pending', 'shipped', 'delivered'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据
INSERT INTO orders (customer_id, product_name, amount, status) VALUES
(1001, 'MacBook Pro 16"', 19999.00, 'pending'),
(1002, 'iPhone 16 Pro', 8999.00, 'shipped'),
(1003, 'AirPods Pro 3', 1899.00, 'delivered');
-- 通过系统表执行 Git 操作
CALL dolt_commit('-am', 'Add initial orders');
4.3 分支与合并工作流
-- 创建一个实验分支
CALL dolt_branch('experiment-pricing');
-- 切换到实验分支
CALL dolt_checkout('experiment-pricing');
-- 在实验分支上修改数据
UPDATE orders SET amount = amount * 0.9 WHERE product_name = 'MacBook Pro 16"';
-- 提交实验修改
CALL dolt_commit('-am', 'Test 10% discount on MacBook');
-- 查看实验分支与主分支的差异
SELECT * FROM dolt_diff('main', 'experiment-pricing', 'orders');
-- 如果实验成功,合并到主分支
CALL dolt_checkout('main');
CALL dolt_merge('experiment-pricing');
-- 删除实验分支
CALL dolt_branch('-d', 'experiment-pricing');
4.4 使用 CLI 进行版本控制
# 查看提交历史
dolt log
# 查看两次提交之间的差异
dolt diff HEAD~1 HEAD
# 查看特定表的变更
dolt diff HEAD~1 HEAD -- orders
# 回滚到某个历史版本
dolt checkout <commit-hash>
# 创建一个新分支
dolt branch feature/new-report
# 推送到远程
dolt remote add origin https://dolthub.com/my-org/my-db
dolt push origin main
五、性能基准:Dolt vs MySQL
DoltHub 在 2.0 发布时公布了一组 sysbench 基准测试数据,令人印象深刻:
| 测试场景 | MySQL 8.0 | Dolt 2.0 | Dolt 领先幅度 |
|---|---|---|---|
| 顺序写入(TPS) | 12,450 | 14,070 | +13% |
| 随机读取(QPS) | 28,300 | 29,715 | +5% |
| 混合读写(TPS) | 8,200 | 9,100 | +11% |
| 批量导入(rows/s) | 180,000 | 165,000 | -8% |
注意:Dolt 在批量导入上仍然比 MySQL 慢约 8%——这是因为 Dolt 需要维护 Prolly 树和提交图的额外开销。但在读写性能上,Dolt 已经反超了 MySQL。
更重要的是,这些数据是在开启了版本控制功能的情况下测得的。如果你不需要版本控制,可以通过配置关闭相关功能来进一步提升性能。
性能优化建议
- 批量提交:不要每插入一行就 commit,而是攒一批变更后统一提交
- 合理设置分支策略:长期分支会增加 GC 的压力,及时合并和删除不再需要的分支
- 使用归档压缩:对于历史数据较多的表,定期触发归档压缩可以显著减少存储占用
- 调整缓存:Dolt 支持配置 SQL 查询缓存和 Prolly 树节点缓存,根据实际工作负载调整
六、与其他方案的对比
6.1 Dolt vs LakeFS
| 维度 | Dolt | LakeFS |
|---|---|---|
| 数据模型 | 关系型(SQL) | 对象存储(Parquet/ORC) |
| 版本控制粒度 | 行级 | 文件级 |
| 查询语言 | SQL | Spark/Trino/Presto |
| 适用场景 | OLTP + 版本控制 | 数据湖 + 版本控制 |
| 存储引擎 | Prolly 树 | S3 兼容 |
LakeFS 适合大规模数据湖场景,而 Dolt 适合需要 SQL 查询和行级版本控制的 OLTP 场景。两者是互补关系,不是竞争关系。
6.2 Dolt vs Nessie
Nessie(现为 Apache 项目)是一个具有 Git 语义的数据湖事务目录。它和 Dolt 的区别在于:
- Nessie 管理的是数据表的元数据(表的版本、Schema 变更等),实际数据存储在 Iceberg 格式中
- Dolt 管理的是数据本身(行级版本控制),数据存储在 Prolly 树中
6.3 Dolt vs 事件溯源
在应用层实现事件溯源是一种优雅的架构选择,但它有几个限制:
- 性能开销:每次状态变更都需要写入事件,然后通过投影生成当前状态
- 查询复杂度:查询当前状态需要回放所有事件,或者维护一个物化视图
- 迁移成本:对于已有的关系型数据库应用,迁移到事件溯源需要重构整个数据层
Dolt 把版本控制做进了数据库内核,对应用层完全透明。你只需要像使用 MySQL 一样操作 Dolt,就能获得版本控制能力。
七、DoltgreSQL:PostgreSQL 兼容版本
除了 MySQL 兼容版本,DoltHub 还推出了 DoltgreSQL——一个与 PostgreSQL 兼容的版本控制数据库。它使用和 Dolt 相同的存储引擎和版本控制接口,但实现了 PostgreSQL 的 SQL 方言和连接协议。
截至 2026 年 8 月,DoltgreSQL 仍处于测试阶段,但已经可以用于开发和评估。如果你的团队更熟悉 PostgreSQL 的生态和扩展(如 PostGIS、TimescaleDB),DoltgreSQL 可能是更好的选择。
八、适用场景与局限性
适合使用 Dolt 的场景
- 配置管理:用数据库存储应用配置,每次配置变更自动产生版本历史,支持回滚和审计
- 数据协作:多个数据分析师可以在独立分支上探索数据,然后合并结果
- 测试数据管理:从生产数据库 fork 一个分支,在分支上进行测试,不影响生产数据
- 时间旅行查询:查询任意历史时刻的数据状态,用于审计和调试
- AI/ML 数据管道:版本控制 embedding 数据和训练数据集,支持可复现的实验
不适合使用 Dolt 的场景
- 超大规模 OLTP:Dolt 的写入性能虽然已经不错,但仍然无法与专门优化的 OLTP 数据库(如 TiDB、CockroachDB)相比
- 纯分析型负载:如果只需要 OLAP 分析,ClickHouse 或 StarRocks 更合适
- 对延迟极度敏感:Dolt 的版本控制功能会引入额外的写入延迟,在对延迟极度敏感的场景下可能不适用
九、未来展望
Dolt 2.0 的发布标志着「数据版本控制」从概念走向了工程化。但 DoltHub 的野心不止于此。从他们 roadmap 中可以看到:
- 向量版本控制正式发布:当前还是 beta 阶段,完整发布后将成为 RAG 和 AI 数据管道的关键基础设施
- 性能持续优化:在批量导入场景下追平 MySQL,是下一个目标
- DoltgreSQL 稳定化:PostgreSQL 兼容版本的正式发布将扩大 Dolt 的用户基础
- 云原生部署:更深度的 Kubernetes 集成和 Serverless 支持
总结
Dolt 2.0 做了一件看似简单但影响深远的事情:把 Git 的版本控制能力嵌入到了数据库引擎的内核中。通过 Prolly 树、内容寻址存储和提交图这三个核心技术,Dolt 实现了行级版本控制、跨版本结构共享和安全的分支合并。
对于程序员来说,这意味着一个长久以来的痛点终于有了优雅的解决方案:你不再需要在「数据的可追溯性」和「系统的简洁性」之间做取舍。用 Dolt,你可以像管理代码一样管理数据——每一次变更都有记录,每一个版本都可以回溯,每一个实验都可以安全地进行。
Dolt 不是银弹,它不适合所有场景。但对于那些需要数据版本控制的应用——配置管理、数据协作、时间旅行查询、AI 数据管道——Dolt 2.0 已经是一个值得认真考虑的选项。
正如 Git 改变了我们管理代码的方式,Dolt 也许正在改变我们管理数据的方式。
项目地址:https://github.com/dolthub/dolt
官方文档:https://docs.dolthub.com
许可证:Apache 2.0
Star 数:17K+