CacheSQL(一):手写数据库的工程化重生

CacheSQL(一):手写数据库的工程化重生


写过一套 2000 行的 B+ 树手写数据库,当时觉得"能跑了"。放了一阵子回头看,发现能跑和能交付之间隔了十万八千里。

CacheSQL 就是这段回头路走出来的。同一个 B+ 树内核,但外面长了三层新东西:复制、HTTP、测试。从原型变成了产品。


一、原型长什么样

这是手写数据库那版的核心结构:

复制代码
com.bluepointsoft.database
├── database.java          # 数据库入口,HashMap 管理表
├── praseSql.java          # SQL 解析
├── btree/
│   ├── BPTree.java        # B+ 树封装
│   ├── Node.java          # B+ 树节点(核心,1054 行)
│   └── Tree.java
├── table/
│   ├── Table.java         # 内存表
│   ├── Row.java           # 行数据(软删除)
│   └── rowSet.java        # 行集合
├── container/myList.java  # 自定义 long[] 数组
└── fun/function.java      # SQL 函数桩

这个版本解决了核心问题------B+ 树的插入、分裂、删除、合并、范围查询------但有几个硬伤:

  1. 没有测试。 能跑,但不知道什么情况下会崩。
  2. database 类用 ConcurrentHashMap 是原型遗留思维。 手写数据库那版我开始用 HashMap,后来多线程冲突才换成 ConcurrentHashMap,但当时没有真正理解这意味着什么------不是锁的问题,是一个项目需要从"跑的 Demo"切换到"可交付产品"。
  3. SQL 解析是散装函数,不是引擎。 praseSql.select_items(sql)praseSql.select_table(sql)praseSql.select_where(sql)------每次查都要调三个静态方法,各自解析一次 SQL。
  4. 没有对外 API。 只能内嵌调用 Java,没 HTTP 接口,其他语言用不了。
  5. 没有文档。 别人不知道这玩意怎么用。

二、工程化重生的五个改动

2.1 项目结构从平铺到分层

复制代码
com.browise.database
├── database.java              # 表管理 + 定时刷新
├── SqlQueryEngine.java        # SQL 引擎(独立模块)
├── btree/                     # B+ 树(核心不变)
├── table/                     # Table/Row/rowSet(核心不变)
├── index/CompositeKey.java    # 新增:联合索引
├── server/                    # 新增:HTTP 服务层
├── replication/               # 新增:主从复制
├── core/util/DBUtil.java      # 新增:连接池 + 配置管理
└── core/exception/utilException.java  # 新增:统一异常

核心层(btree + table)基本没动。新东西全加在外面,核心层连复制都不知道------ReplicationManager 对 Table 和 BPTree 完全透明。

2.2 从 HashMap 到 ConcurrentHashMap

手写数据库那版,database.java 用的是 HashMap<String, Table>。AI 审计时指出的第一个问题是"线程不安全"------两个线程同时 load 同一张表会互相覆盖。我换成 ConcurrentHashMap 时,没多想。后来才意识到这个切换意味着什么:

HashMap → ConcurrentHashMap 这个决策本身是"产品意识"的起点------你开始假设这个系统会被并发调用,而不是只在你自己的电脑上跑一个线程。

CacheSQL 里更进一步。tables.putIfAbsent 替代了 tables.put,防止并发 load 时创建重复 Table。refresh 时用 tables.put(name, fresh) 原子替换------先创建新 Table 实例加载最新数据,完成后再替换引用,刷新期间的查询不受影响。

2.3 SQL 解析从散装函数到独立引擎

手写数据库那版 SQL 解析是三个静态函数:

java 复制代码
List<String> items  = praseSql.select_items(sql);
List<String> tables = praseSql.select_table(sql);
String where        = praseSql.select_where(sql);

CacheSQL 拆出了独立的 SqlQueryEngine:SQL 模板化 → 解析 → 缓存执行计划 → 下次同模板直接复用,省掉重复解析。这个设计让后续加 HTTP 接口时天然就支持了 SQL 查询------不需要单独实现一套查询逻辑。

2.4 从零测试到 53 项测试 + 6 项集成

手写数据库那版没有任何测试用例。CacheSQL 加了两层测试:

  • 53 项单元测试:B+ 树 15 项,SQL 引擎 11 项,内存表 17 项,复制模块 8 项,并发 2 项
  • 6 项集成测试:端到端验证主从复制的完整生命周期------写入广播、Slave 转发、幂等性、Master 宕机缓冲、恢复后自动重放

不是说测试越多越好。但一个没有测试的产品,你不敢交付给任何人。

2.5 从零文档到 7 份

功能说明书、部署手册、操作手册、测试报告、性能测试、两版审计报告。加上已有的 4 篇手写数据库博客------这些文档不是给评委看的,是给未来任何一个接手这个系统的人看的。


三、什么没变

B+ 树核心逻辑几乎没变。

  • 256 阶:和原型一样,一次加载 256 个子节点,树高度为 2(100万数据)
  • 软删除 :原型里的 isDelete 标记保留,仍是 O(1),不移动 ArrayList 元素
  • 预留空间:原型里 10% 的预留设计保留,批量插入时减少分裂频率
  • 关键字校准 validate:原型里那个踩了大坑的函数保留------分裂后父节点的 entry key 不更新就会路由出错

不是什么都要重写。核心对了,外围加工程化就好。


四、AI 在这个过程中的角色

手写数据库 4 篇博客提到的 B+ 树算法(分裂、借位合并、二分变体、validate 校准),有一些是我自己写的(软删除、预留都是我的经验),有一些是 AI 辅助生成的(代码框架、文档结构)。

CacheSQL 的工程化升级,AI 承担了大部分"写代码"的工作:项目结构、测试用例、文档框架、异常处理、配置文件解析。我承担的是架构决策:ReplicationManager 对核心层透明、OpLog 用定长环形缓冲区而非 ArrayList、insert 用 upsert 语义保证幂等------这些没有一个是 AI 主动建议的。

用 AI 写代码,用经验做决策。 这就是从原型到产品之间,AI 和人的分工。


下一篇:[CacheSQL(二):主从复制------OpLog 环形缓冲区与故障自动恢复]


系列:CacheSQL 工程化交付实录(共 5 篇,含桥接篇)

相关推荐
MmeD UCIZ1 小时前
MySQL单表存多大的数据量比较合适
数据库·mysql
shjita1 小时前
记录java执行中的一个错误细节
java·开发语言
空中海1 小时前
Docker入门到精通
java·docker·eureka
itzixiao1 小时前
L1-067 洛希极限(10分)[java][python]
java·开发语言·算法
SarL EMEN2 小时前
mysql之联合索引
数据库·mysql
java1234_小锋2 小时前
Spring AI 2.0 开发Java Agent智能体 - Spring AI项目调用本地Ollama模型
java·人工智能·spring·spring ai2.0
二哈赛车手2 小时前
新人笔记---多策略搭建策略执行链实现RAG检索后过滤
java·笔记·spring·设计模式·ai·策略模式
PESS ABIN2 小时前
JavaWeb项目打包、部署至Tomcat并启动的全程指南(图文详解)
java·tomcat
l1t2 小时前
DeepSeek总结的DuckDB anofox-forecast季节调整时间序列预测插件功能
开发语言·数据库