H2 数据库将 SQL 语句转换为磁盘存储的全过程可以分为以下 8 个关键步骤,我们以 INSERT INTO users (id, name) VALUES (1, 'Alice') 为例详细说明:


1. SQL 解析与语法树生成

  • 词法分析:拆分语句为 INSERTINTOusers 等 Token
  • 语法分析:构建抽象语法树(AST),验证表结构是否存在
  • 语义分析:确认 idname 字段类型匹配

2. 事务管理与日志写入(WAL)

– 隐式事务开始
INSERT INTO users VALUES (1, ‘Alice’);
– COMMIT 触发日志落盘

  • 预写日志:先将操作记录写入 .trace.db 文件

  • 日志格式

    INSERT INTO PUBLIC.USERS(ID, NAME) VALUES (1, ‘Alice’)


3. 内存数据结构操作

  • MVStore 引擎(默认):
    • 将数据暂存内存中的 B+Tree 结构

    • Key-Value 存储格式:

      {
      “key”: 1,
      “value”: {
      “id”: 1,
      “name”: “Alice”
      }
      }


4. 页式存储管理(PageStore)

  • 数据文件users.h2.db

  • 页结构(假设页大小 4KB):

    Page Header (16 bytes)

    | PageID | NextPage | RecordCount | Checksum |
    | 0x01 | 0x02 | 1 | 0xA3F1 |

    Data Section:
    | 0x0001 | ‘Alice’ (UTF-8 bytes) |


5. 索引维护

  • 主键索引:自动创建 PRIMARY_KEY_INDEX

    // B+Tree 结构示例
    BPlusTree index = new BPlusTree();
    index.put(1, new PagePointer(0x01, 128)); // 指向页0x01的128字节偏移


6. 检查点(Checkpoint)触发

  • 触发条件
    • 事务提交时设置 FORCE_WRITE=true
    • 内存中脏页比例超过阈值(默认 50%)
  • 持久化过程
    1. 暂停新事务
    2. 将内存中的 B+Tree 序列化为二进制格式
    3. 追加写入 .h2.db 文件

7. 物理存储结构

数据库文件结构

users.h2.db # 主数据文件
users.temp.db # 临时文件(合并操作时使用)
users.lock.db # 文件锁控制
users.trace.db # WAL 日志文件


8. 崩溃恢复机制

当异常关闭后重启时:

  1. 检查 .trace.db 日志文件
  2. 回放未提交的事务(Rollback)
  3. 验证 .h2.db 文件的 checksum
  4. 重建内存中的 B+Tree 索引

技术亮点

  1. MVCC 实现:通过 TRANSACTION_ID 字段实现多版本控制
  2. 压缩优化:使用 LZF 算法压缩数据页(默认关闭)
  3. 加密支持:可选 AES-128 加密整个数据文件

通过这种分层处理机制,H2 既保证了 ACID 特性,又能达到 50,000+ TPS 的写入性能(内存模式)。实际存储格式会根据 MVStorePageStore 引擎的选择有所不同,以上流程以 MVStore 为主。


**user**: 

重新生成一遍,sql ,解析成java 对象,用了什么数据结构,索引,如何存储到磁盘的


**assistant**: 

以下是 H2 数据库将 SQL 语句转换为 Java 对象并最终存储到磁盘的完整技术过程,结合 INSERT INTO users (id, name) VALUES (1, 'Alice') 示例:


1. **SQL 解析与 Java 对象生成

解析过程

// 使用 H2 的解析器生成 Command 对象
Parser parser = new Parser(connection);
String sql = “INSERT INTO users (id, name) VALUES (1, ‘Alice’)”;
Command command = parser.parseCommand(sql); // 返回 Insert 对象

// Insert 对象结构 (org.h2.command.dml.Insert)
class Insert {
Table table; // users 表的元数据
Column[] columns; // [id, name]
Expression[] values; // [1, ‘Alice’]
int rowId; // 内存中的临时行号
}

关键数据结构

// 内存中的行数据表示 (org.h2.result.Row)
Row row = table.getTemplateRow();
row.setValue(0, ValueInt.get(1)); // id 列
row.setValue(1, ValueString.get(“Alice”)); // name 列

// 值对象的内存结构 (org.h2.value.Value)
class ValueInt {
int value;
byte[] memoryBytes; // 序列化后的字节缓存(用于快速存储)
}


2. **内存数据结构与索引

表数据存储(MVStore 引擎)

// MVStore 的 B+Tree 实现 (org.h2.mvstore.MVMap)
MVMap<Integer, Row> tableMap = store.openMap(“users”);

// 插入数据到内存树
tableMap.put(1, row); // key=id, value=Row对象

// Row 的内存结构
class Row {
Value[] data; // [1, “Alice”]
long version; // MVCC 版本号
int transactionId; // 关联的事务ID
}

索引结构(B+Tree 实现)

// 主键索引 (org.h2.index.PageBtreeIndex)
PageBtreeIndex primaryIndex = new PageBtreeIndex(…);

// 索引节点结构
class BTreeNode {
Object[] keys; // [1]
long[] children; // 子节点页号(内存地址或磁盘页ID)
boolean isLeaf;
}

// 索引条目
class IndexEntry {
int key; // id=1
PagePointer pointer;// 指向数据页的位置 (pageId=0x01, offset=128)
}


3. **磁盘存储细节

MVStore 文件格式(默认引擎)

数据文件 users.mv.db 的二进制结构

[File Header] [Chunk 1] [Chunk 2] … [Chunk N]

Chunk 结构示例

Chunk {
int chunkId; // 递增的块ID
long blockCount; // 占用的块数
byte[] mapData; // 序列化的 MVMap 数据
byte[] btreeNodes; // B+Tree 节点二进制数据
}

单行存储的二进制格式

// Row 的序列化过程
ByteBuffer buf = ByteBuffer.allocate(128);
DataUtils.writeVarInt(buf, 1); // id
DataUtils.writeString(buf, “Alice”); // name
byte[] rowBytes = buf.array(); // 最终字节数组

// 磁盘存储示例(十六进制):
01 00 00 00 // id=1 (4字节)
41 6C 69 63 65 // “Alice” (UTF-8 bytes)

页式存储(PageStore 引擎)

// 数据页结构 (org.h2.store.Page)
class Page {
int pageId; // 0x01
byte[] data; // 页内容(包含多行数据)
int type; // 页类型(数据页/索引页)
}

// 页内数据组织
DataPage {
Row[] rows; // 行数据数组
int freeSpace; // 剩余空间计数
int nextPage; // 下一页指针
}


4. **持久化触发机制

事务提交时的写入

// 事务提交操作 (org.h2.engine.Session)
session.commit();

// 持久化步骤:

  1. 将 MVStore 的脏页标记为待写入
  2. 通过 FileStore.write() 异步写入磁盘
  3. 更新文件头部的元数据块

// 关键写入类
FileStore fileStore = new FileStore();
fileStore.write(ByteBuffer data, long position); // 直接操作文件通道

检查点(Checkpoint)示例

// 检查点线程 (org.h2.engine.CheckpointThread)
public void run() {
if (needCheckpoint()) {
store.sync(); // 强制同步内存数据到磁盘
}
}

// 触发条件:
// - 超过 checkpointInterval (默认 30秒)
// - 日志文件超过 16MB


5. **技术关键点

  1. 内存与磁盘的桥梁

    • Row 对象同时存在于内存(Java Heap)和堆外内存(DirectByteBuffer)
    • 通过 Value#getBytes() 实现快速序列化
  2. 索引加速

    // 索引查询示例
    Cursor cursor = primaryIndex.find(1); // 二分查找 B+Tree
    Page page = readPage(cursor.getPageId());
    Row row = page.getRow(cursor.getOffset());

  3. 压缩与加密

    // 文件存储配置
    MVStore.Builder()
    .compress() // 启用 LZF 压缩
    .encryptionKey(“AES”, “password”.toCharArray());


完整流程示例

  1. 用户发送 INSERT SQL
  2. 生成 Insert 命令对象
  3. 创建 Row 并填充 Value 数据
  4. 更新内存中的 MVMap(B+Tree)
  5. 事务提交时触发:
    • 写入 WAL 日志(.trace.db
    • 标记脏页
    • 异步线程执行磁盘写入
  6. 数据最终以分块形式持久化到 .mv.db 文件

通过这种设计,H2 在保证 ACID 的同时,内存模式可达 50,000 TPS,持久化模式性能约为 8,000 TPS(取决于磁盘速度)。开发者可通过 MVStore API 直接操作底层存储结构。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐