本文按 TinyDbEngine 真实代码路径拆解启动流程,重点回答三个问题:

  1. 新库和旧库是如何区分的?

  2. WAL 恢复何时触发?

  3. 初始化后哪些核心组件会被装配?

1. 构造函数入口

入口在:

  • TinyDbEngine(string f, TinyDbOptions? o = null)

  • internal TinyDbEngine(string f, TinyDbOptions? o, IDiskStream? ds)

核心动作:

_options = o ?? new TinyDbOptions();
_options.Validate();

_pageManager = new PageManager(...);
_writeAheadLog = new WriteAheadLog(..., _options.EnableJournaling, ...);

_pageManager.RegisterWAL(lsn => _writeAheadLog.FlushToLSN(lsn));
_pageManager.RegisterWAL((lsn, ct) => _writeAheadLog.FlushToLSNAsync(lsn, ct));

_flushScheduler = new FlushScheduler(...);
_largeDocumentStorage = new LargeDocumentStorage(...);
_dataPageAccess = new DataPageAccess(...);
_metadataManager = new MetadataManager(this);

InitializeDatabase();

2. InitializeDatabase() 五步走

InitializeDatabase() 是启动核心,代码逻辑可以总结为:

  1. WAL 重放(可选)

  2. 文件结构初始化(新库)或头部读取(旧库)

  3. PageManager 与 Header 状态对齐

  4. 系统页与集合元数据加载

  5. 安全验证(密码保护)

2.1 WAL 重放

EnableJournaling=true 时,启动会调用 WriteAheadLog.Replay(...)


关键点:并不是无脑回放,每条日志都会比较 LSN:

  • walHeader.LSN <= diskHeader.LSN,跳过该日志。

  • 否则 RestorePage 覆盖页面。

这保证了恢复幂等,不会因为重复回放破坏已更新页面。

2.2 新库初始化路径

_diskStream.Size == 0 时:

  • 创建 DatabaseHeader

  • 分配页 1(Header 页)。

  • PageManager.Initialize(1, 0)

意味着数据库首次启动会完成最小页结构建库。

2.3 旧库加载路径

当文件已存在时:

  • 从页 1 读取 Header:ReadHeader()

  • 校验 Header.IsValid()

  • PageManager.Initialize(_header.TotalPages, _header.FirstFreePage)

随后有一个非常关键的同步修正:

  • 如果 PageManager.TotalPages > _header.TotalPages 或空闲页链表状态不一致,会 WriteHeader() 反向回写头部。

这一步专门处理“崩溃发生在 Header 写回前”的状态漂移。

2.4 系统页与集合元信息

初始化系统页:

  • CollectionInfoPage(集合信息)

  • IndexInfoPage(索引信息)

再创建 CollectionMetaStoreLoadCollections()

2.5 安全验证

EnsureDatabaseSecurity() 逻辑:

  • 若未配置密码:数据库若已受保护 -> 拒绝访问。

  • 若配置了密码且数据库已受保护:必须认证成功。

  • 若配置了密码且数据库未受保护:创建安全元数据。

3. 启动后的运行态对象

初始化成功后,引擎至少具备以下能力:

  • 页缓存与分配:PageManager

  • WAL 写前日志:WriteAheadLog

  • 后台持久化策略:FlushScheduler

  • 大文档分离存储:LargeDocumentStorage

  • 数据页读写与扫描:DataPageAccess

  • 集合元数据管理:CollectionMetaStore

  • Schema / 元数据:MetadataManager

  • 事务调度:TransactionManager

4. CompactDatabase() 的重建思路

CompactDatabase() 不是“页内整理”,而是“重写新文件再替换”:

  1. Flush() 先落盘。

  2. 创建临时引擎写新库。

  3. 迁移集合数据与索引定义。

  4. 释放当前组件句柄。

  5. 文件替换后重新初始化组件。

优点:实现简单且稳定,代价是执行期间是重操作。

5. 对业务方的启示

  1. 打开数据库是重路径,避免在每次请求里 new TinyDbEngine

  2. 生产环境建议显式配置 TinyDbOptions,特别是 WriteConcernEnableJournalingTransactionTimeout

  3. 开启密码后,错误密码会在启动阶段失败,不会等到首次查询再报错。

6. 小结

TinyDbEngine 启动逻辑的核心价值是:

  • 通过 WAL + LSN 保障崩溃恢复幂等。

  • 通过 Header 与 PageManager 状态对齐保障结构一致。

  • 在启动期一次性装配核心组件,运行期只做业务读写。

下一篇进入最关键的性能路径:插入、更新、删除如何落盘。