The SQLVibe Repo, Need your ✨ github.com/cyw0ng95/sq…
特别感谢 MonkeyCode AI 提供免费的计算环境,开发过程采用模型:Claude + GLM + MiniMax
第一部分:时机——为什么现在是重写的时间窗口
硬件架构的范式转移
SQLite 诞生于 2000 年,时代的硬件特征是:单核 CPU、256MB 内存、机械硬盘。这些物理约束塑造了 SQLite 的核心架构——B-Tree 页面管理、LRU 缓存淘汰、磁盘 I/O 优化。
二十五年后的今天,硬件已经面目全非:
| 硬件特性 | 2000 年 | 2026 年 | 架构含义 |
|---|---|---|---|
| CPU 核心 | 1 核 | 8-16 核 (消费级) | 向量化、多线程并行成为可能 |
| SIMD 指令 | 无 | 512 位 (AVX-512) | 一次处理 8 个 64 位整数 |
| 内存容量 | 256MB | 16-32GB (消费级) | 空间换时间、列式存储可行 |
| 存储介质 | 磁盘为主 | 内存优先 | 抛弃磁盘优化遗产 |
最关键的变化是存储介质:越来越多的数据库场景是"纯内存"——嵌入式设备、端侧 AI、隐私敏感应用。SQLite 的 :memory: 模式只是机械地把文件映射到内存,而不是真正为内存访问模式设计的数据库。
这意味着:为我们从架构层面为内存优化,可以完全抛弃 B-Tree 页面管理的磁盘 I/O 思维,获得数量级的性能提升。
AI 辅助编程的成熟
重写 SQLite 是一个约 20000 行代码的工程:
- SQL 解析器:约 2000 行
- 存储引擎:约 5000 行
- 执行器(200+ 指令):约 8000 行
- 查询优化器:约 3000 行
- 工具链:约 2000 行
这个量级对人类工程师是巨大挑战,对 AI 来说是"大型项目"而非"不可能项目"。
AI 的核心价值在于"放大"人类能力:
- 我负责架构设计、边界验证、语义判断——这些需要领域知识和系统思维
- AI 负责代码生成、模式补全、测试扩展——这些需要记忆力和执行力
这种分工让我在七天内完成了原本需要数周的工作。
但 AI 也有明确的边界:
- 它不擅长"理解需求背后的意图"——当我告诉它"实现 MVCC"时,它给出一个教科书式的实现,却忽略了事务回滚时的版本链清理
- 它不擅长"系统级的权衡"——面对一致性 vs 性能的抉择,它给出的是"平庸的平衡"而非"明确的立场"
- 它不擅长"破坏性测试"——它生成的测试用例是"快乐路径",而真正的测试需要"如何让系统崩溃"的思维
我们的机会是:用更现代的架构(纯内存 + 向量化 + Go 语言),做得更快。
第二部分:架构设计——AI 能教给我们什么?
整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ sqlvibe │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ SQL CLI │ │ HTTP API │ │ Go SDK (pkg) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Query Processing (QP) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌──────────┐ │
│ │ Tokenizer │→ │ Parser │→ │ Optimizer │→ │ Compiler │ │
│ └────────────┘ └────────────┘ └────────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Execution Engine (QE/VM) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Vectorized Execution Engine │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ Plan Cache │ │Result Cache │ │ Predicate Push │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐│ │
│ │ │ Register-based VM (200+ ops) ││ │
│ │ └─────────────────────────────────────────────────────┘│ │
│ └──────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Storage Engine (DS) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Hybrid Store (Row/Columnar Adaptive) │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ B-Tree │ │ ColumnStore│ │ RoaringBitmap │ │ │
│ │ │ (Row) │ │ (Columnar) │ │ Index │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ │ WAL Log │ │ Compression │ │ Backup │ │ │
│ │ │ (MVCC) │ │ (LZ4/ZSTD) │ │ (Incremental)│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────┤
│ Transaction Manager (TM) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ WAL Mode │ │ MVCC │ │ Lock Manager │ │
│ │ │ │ Snapshot │ │ (Deadlock Detection) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
存储引擎:混合行/列式架构
这是 sqlvibe 与 SQLite 最大的架构差异。
SQLite 的存储架构:
┌─────────────────────────────────────────┐
│ SQLite Database File │
├─────────────────────────────────────────┤
│ ┌─────────────────────────────────┐ │
│ │ B-Tree Leaf Page │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┐│ │
│ │ │Row 1│Row 2│Row 3│Row 4│Row 5││ │
│ │ │id=1 │id=2 │id=3 │id=4 │id=5 ││ │
│ │ │name │name │name │name │name ││ │
│ │ │age │age │age │age │age ││ │
│ │ └─────┴─────┴─────┴─────┴─────┘│ │
│ └─────────────────────────────────┘ │
│ │
│ * 每页 4KB-64KB │
│ * 行数据连续存储 │
│ * 适合点查询和写入 │
└─────────────────────────────────────────┘
sqlvibe 的混合存储架构:
┌─────────────────────────────────────────────────────────────┐
│ Hybrid Store │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Row Store (B-Tree) - 热数据 / 写入 │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │Row 1│Row 2│Row 3│Row 4│Row 5│ ← 适合点查询 │ │
│ │ └─────┴─────┴─────┴─────┴─────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Column Store - 分析查询 / 聚合 │ │
│ │ │ │
│ │ id: [1] [2] [3] [4] [5] [6] [7] [8] ... │ │
│ │ └───┴───┴───┴───┴───┴───┴───┴───┘ │ │
│ │ │ │
│ │ name: ["A"] ["B"] ["C"] ["D"] ["E"] ["F"] ... │ │
│ │ └─────┴─────┴─────┴─────┴─────┴─────┘ │ │
│ │ │ │
│ │ age: [20] [25] [30] [35] [40] [45] ... │ │
│ │ └───┴───┴───┴───┴───┴───┴───┘ │ │
│ │ │ │
│ │ * 列数据连续存储,SIMD 友好 │ │
│ │ * 一次读取整列,无需行转换 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ RoaringBitmap - 索引过滤 │ │
│ │ │ │
│ │ age > 30: [0,0,0,1,1,1,0,1,0,0,1,1,...] │ │
│ │ ↓ RoaringBitmap 压缩 │ │
│ │ [3,4,5,7,10,11,...] │ │
│ │ │ │
│ │ * O(1) 复杂度的位图交集/并集 │ │
│ │ * 配合列式存储实现谓词下推 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
自适应切换策略:
Write/Point Query → Row Store (B-Tree)
↓ 访问模式分析
↓ 冷数据自动迁移
↓
Analysis/Aggregate → Column Store (Vectorized)
执行引擎:向量化与 VM 的混合模式
传统火山模型(Volcano Model):
┌─────────────────────────────────────────┐
│ Volcano Execution │
├─────────────────────────────────────────┤
│ │
│ SELECT SUM(age) FROM users │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Table Scan │ ← 逐行处理 │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Filter (age > 30) │ ← 逐行判断 │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Aggregate (SUM) │ ← 逐行累加 │
│ └─────────────────────┘ │
│ │ │
│ ▼ │
│ Result │
│ │
│ * 函数调用开销大 │
│ * 分支预测失败多 │
│ * CPU 缓存命中率低 │
└─────────────────────────────────────────┘
sqlvibe 向量化执行:
┌─────────────────────────────────────────────────────────────┐
│ Vectorized Execution │
├─────────────────────────────────────────────────────────────┤
│ │
│ SELECT SUM(age) FROM users WHERE age > 30 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Predicate Evaluation (Vectorized) │ │
│ │ │ │
│ │ age: [20, 25, 30, 35, 40, 45, 50, 55, ...] │ │
│ │ mask: [0, 0, 0, 1, 1, 1, 1, 1, ...] │ │
│ │ ↓ SIMD 批量比较 │ │
│ │ ↓ 生成过滤位图 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Columnar Projection │ │
│ │ │ │
│ │ filtered_age: [35, 40, 45, 50, 55, ...] │ │
│ │ └───┴───┴───┴───┴───┴───┘ │ │
│ │ * 连续内存,CPU 预取器高效工作 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Vectorized Aggregate │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ for i := 0; i < len(col); i += 4 { │ │ │
│ │ │ sum += col[i] + col[i+1] │ │ │
│ │ │ + col[i+2] + col[i+3] │ │ │
│ │ │ } // 手动循环展开,SIMD 友好 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Result: 225 │
│ │
│ * 一次处理 1024 行 │
│ * 缓存命中率高 │
│ * 分支预测失败少 │
└─────────────────────────────────────────────────────────────┘
多级缓存体系
sqlvibe 实现了三级缓存,这是性能提升的关键:
┌─────────────────────────────────────────────────────────────┐
│ Query Processing Pipeline │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 1: Result Cache (FNV-1a 哈希键) │ │
│ │ │ │
│ │ Query: "SELECT * FROM users WHERE id = 1" │ │
│ │ ↓ FNV-1a 哈希 │ │
│ │ Key: 0x8a3f2b1c... │ │
│ │ ↓ 缓存命中? │ │
│ │ Result: 直接返回 (< 2 µs) │ │
│ │ │ │
│ │ * 相同 SQL 文本直接返回缓存 │ │
│ │ * LRU 淘汰策略 │ │
│ │ * 命中率 > 70% 时性能提升 95x │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ miss │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 2: Plan Cache (解析后执行计划复用) │ │
│ │ │ │
│ │ SQL 文本 → Tokenize → Parse → Optimize → Plan │ │
│ │ │ │ │
│ │ │ 缓存命中 │ │
│ │ ▼ │ │
│ │ 跳过 Tokenize/Parse,直接 Optimize │ │
│ │ │ │
│ │ * 复用 AST 和执行计划 │ │
│ │ * 减少重复的解析开销 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ miss │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 3: Predicate Pushdown (Go 层预过滤) │ │
│ │ │ │
│ │ WHERE age > 30 AND city = 'Beijing' │ │
│ │ ↓ 在 Go 层评估 │ │
│ │ 只返回满足条件的行到 VM │ │
│ │ │ │
│ │ * 减少 VM 的执行负担 │ │
│ │ * 配合 RoaringBitmap 实现 O(1) 过滤 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
内存管理:Arena 分配器
Go 的 GC 一直是性能敏感型应用的痛点。我们采用了 Arena 分配器模式:
┌─────────────────────────────────────────────────────────────┐
│ Traditional Go Memory Allocation │
├─────────────────────────────────────────────────────────────┤
│ │
│ Query 1: func Exec() { │
│ row := new(Row) // 每次分配 heap │
│ rows = append(rows, row) │
│ } │
│ // GC 需要扫描所有对象 │
│ │
│ Query 2: func Exec() { │
│ row := new(Row) // 又一次 heap 分配 │
│ } │
│ // GC 压力累积 │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Arena Allocation (sqlvibe) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Arena Allocator │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Pre-allocated: 64MB memory block │ │ │
│ │ │ │ │ │
│ │ │ [████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] │ │ │
│ │ │ ↑ used │ │ │
│ │ │ │ │ │
│ │ │ Query 1: ptr = arena.Alloc(64) │ │ │
│ │ │ Query 2: ptr = arena.Alloc(128) │ │ │
│ │ │ Query 3: ptr = arena.Alloc(32) │ │ │
│ │ │ │ │ │
│ │ │ Query End: arena.Reset() // 一键释放 │ │ │
│ │ │ ↓ │ │ │
│ │ │ GC 压力降为 0 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ * 一次分配大块内存 │
│ * 查询期间从 arena 切分使用 │
│ * 查询结束后整体释放 │
│ * GC 扫描时间降为 O(1) │
└─────────────────────────────────────────────────────────────┘
第三部分:性能对比——数据说话
测试环境
- CPU:AMD EPYC 7763 (64 cores @ 2.45GHz)
- 内存:256GB
- Go 版本:1.21+
- 数据集:1,000 行,内存数据库 (
:memory:) - 基准方法:两端迭代所有结果行进行端到端对比
查询性能对比
| 操作 | sqlvibe | SQLite Go | 加速比 |
|---|---|---|---|
| SELECT 全表扫描 | 22 µs | 590 µs | 27x |
| SELECT WHERE (过滤) | 101 µs | 120 µs | 1.2x |
| ORDER BY | 72 µs | 328 µs | 4.5x |
| COUNT(*) | 4.4 µs | 28 µs | 6.4x |
| SUM (聚合) | 3.9 µs | 100 µs | 26x |
| GROUP BY | 50 µs | 564 µs | 11x |
| JOIN (两表) | 182 µs | 275 µs | 1.5x |
DML 性能对比
| 操作 | sqlvibe | SQLite Go | 加速比 |
|---|---|---|---|
| INSERT 单行 | 9.6 µs | 50 µs | 5x |
| INSERT 100 行 (批量) | 311 µs | 741 µs | 2.4x |
特殊场景性能
| 操作 | sqlvibe | SQLite Go | 加速比 |
|---|---|---|---|
| 结果缓存命中 | 2 µs | 190 µs | 95x |
| TOP-N LIMIT 10 (10K 行) | 571 µs | 1.23 ms | 2.2x |
| COUNT(*) via PK index | 84 µs | 30 µs | SQLite 胜 |
| 全表扫描 + 过滤 (10K) | 1.87 ms | 1.52 ms | SQLite 胜 |
性能数据解读
sqlvibe 胜出的场景:
- 27 倍的 SELECT 加速:向量化执行 + 列式存储 + SIMD 友好循环展开的组合优势。当扫描全表时,连续的列数据使 CPU 预取器充分发挥作用。
- 95 倍的缓存加速:应用层结果缓存对系统层缓存的"降维打击"。查询完全命中缓存时,甚至不需要触达存储层。
- 11 倍的 GROUP BY 加速:列式存储避免了行数据转换,向量化聚合一次处理 1024 行。
SQLite 胜出的场景:
- 2.8 倍的 PK COUNT(*):SQLite 的 B-Tree 索引实现经过二十年的打磨极其高效,我们的实现还有优化空间。
- 1.2 倍的全表扫描 + 过滤:对于超大数据集(10K+ 行),SQLite 的"低每行开销"反而成为优势。
结论:sqlvibe 是"内存优先"的数据库,优势在中小规模数据(KB-GB 级别)的分析型工作负载。SQLite 是"磁盘优先"的数据库,优势在随机 I/O 场景。
第四部分:挑战——重写一个"活着的传奇"
挑战一:SQL 兼容性的深渊
SQLite 最大的陷阱是:它看起来简单,实际上深不可测。
第一轮测试:31 / 84 失败。
问题包括:
- IN 子查询:
WHERE id IN (SELECT id FROM t2)解析错误 - GROUP_CONCAT:SEPARATOR 默认值处理错误
- 除以零:返回 NULL 还是报错?(SQLite 选择返回 NULL)
- 字符串比较:二分法还是 ICU 规则?(SQLite 选择 BINARY)
我们花了三天时间,一个一个修复测试用例。 这让我深刻体会到:SQLite 的兼容性是二十多年无数用户反馈和边界用例的打磨形成的"事实标准",不是"实现一个 SQL 标准"那么简单。
挑战二:事务一致性的复杂性
实现 MVCC 时,AI 给出的版本链清理方案存在一个致命 bug:事务回滚时没有正确释放旧版本。
这个错误在单线程测试中完全无法发现——每个事务都成功提交了。只有当 20 个 goroutine 并发执行读写事务时,才触发了一个极其隐蔽的数据竞争。而这样的用例生成,对 AI 而言,只是一些 tokens.
挑战三:性能优化的"最后一公里"
初版性能数据让人心凉了一半:SELECT 只快了 5 倍,GROUP BY 基本持平,且当前仍在较为初级的阶段。
问题出在"数据转换开销":虽然设计了列式存储,但执行层读取数据时,需要先将列数据"解压"成行格式,然后再"压缩"成向量化格式。这一来一回,开销巨大。
解决方案是"统一内存布局":让列数据在存储层和执行层之间零拷贝流动。
优化后的结果:SELECT 快了 27 倍,GROUP BY 快了 11 倍。
结语:站在巨人的肩膀上
第一,"重写 The Old Guys"是一个真实的需求。 AI 将告诉你在现代的语境下应如何开发。
第二,AI 辅助编程正在改变标准程序的开发方式。 一个人 + 一个 AI,可以在七天内完成一个"可用"的数据库原型。这种开发效率的提升是革命性的。
第三,性能优化需要回归硬件本质。 当摩尔定律放缓,我们不能再依赖 CPU 主频提升,而是需要精细地适配现代处理器的微架构特性。这需要更深入的 AI 编程能力——也正是人类正在为其训练和磨合的技能。