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 直接操作底層存儲結構。