H2 数据库将 SQL 语句转换为磁盘存储的全过程可以分为以下 8 个关键步骤,我们以 INSERT INTO users (id, name) VALUES (1, 'Alice')
为例详细说明:
1. SQL 解析与语法树生成
- 词法分析:拆分语句为
INSERT
、INTO
、users
等 Token - 语法分析:构建抽象语法树(AST),验证表结构是否存在
- 语义分析:确认
id
和name
字段类型匹配
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%)
- 事务提交时设置
- 持久化过程:
- 暂停新事务
- 将内存中的 B+Tree 序列化为二进制格式
- 追加写入
.h2.db
文件
7. 物理存储结构
数据库文件结构
users.h2.db # 主数据文件
users.temp.db # 临时文件(合并操作时使用)
users.lock.db # 文件锁控制
users.trace.db # WAL 日志文件
8. 崩溃恢复机制
当异常关闭后重启时:
- 检查
.trace.db
日志文件 - 回放未提交的事务(Rollback)
- 验证
.h2.db
文件的 checksum - 重建内存中的 B+Tree 索引
技术亮点
- MVCC 实现:通过
TRANSACTION_ID
字段实现多版本控制 - 压缩优化:使用 LZF 算法压缩数据页(默认关闭)
- 加密支持:可选 AES-128 加密整个数据文件
通过这种分层处理机制,H2 既保证了 ACID 特性,又能达到 50,000+ TPS 的写入性能(内存模式)。实际存储格式会根据 MVStore
或 PageStore
引擎的选择有所不同,以上流程以 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();
// 持久化步骤:
- 将 MVStore 的脏页标记为待写入
- 通过 FileStore.write() 异步写入磁盘
- 更新文件头部的元数据块
// 关键写入类
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. **技术关键点
-
内存与磁盘的桥梁:
Row
对象同时存在于内存(Java Heap)和堆外内存(DirectByteBuffer)- 通过
Value#getBytes()
实现快速序列化
-
索引加速:
// 索引查询示例
Cursor cursor = primaryIndex.find(1); // 二分查找 B+Tree
Page page = readPage(cursor.getPageId());
Row row = page.getRow(cursor.getOffset()); -
压缩与加密:
// 文件存储配置
MVStore.Builder()
.compress() // 启用 LZF 压缩
.encryptionKey(“AES”, “password”.toCharArray());
完整流程示例
- 用户发送
INSERT
SQL - 生成
Insert
命令对象 - 创建
Row
并填充Value
数据 - 更新内存中的
MVMap
(B+Tree) - 事务提交时触发:
- 写入 WAL 日志(
.trace.db
) - 标记脏页
- 异步线程执行磁盘写入
- 写入 WAL 日志(
- 数据最终以分块形式持久化到
.mv.db
文件
通过这种设计,H2 在保证 ACID 的同时,内存模式可达 50,000 TPS,持久化模式性能约为 8,000 TPS(取决于磁盘速度)。开发者可通过 MVStore
API 直接操作底层存储结构。