浅析h2数据库存储引擎-mvStore

设置存储引擎

在1.4以前h2使用的存储引擎是pageStore,现在还保留在org.h2.pagestore下。要想使用旧存储引擎PageStore可以在jdbc链接后面设置:

1
jdbc:h2:~/test;MV_STORE=false

则会使用旧的存储引擎,默认是true,即使用mvStore。存储引擎初始化过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//DataBase.java
/**
* 初始化存储引擎
*
* @return
*/

public PageStore getPageStore() {
//MV_STORE=false/true 默认true 表明存储引擎使用 MvStore
if (dbSettings.mvStore) {
if (store == null) {
//传dataBase对象进去 初始化 MVTableEngine.Store(包含 MvStore TransactionStore)
store = MVTableEngine.init(this);
}
return null;
}
//存储引擎使用 pageStore
synchronized (this) {
if (pageStore == null) {
pageStore = new PageStore(this, databaseName +
Constants.SUFFIX_PAGE_FILE, accessModeData, cacheSize);
if (pageSize != Constants.DEFAULT_PAGE_SIZE) {
pageStore.setPageSize(pageSize);
}
if (!readOnly && fileLockMethod == FileLockMethod.FS) {
pageStore.setLockFile(true);
}
pageStore.setLogMode(logMode);
pageStore.open();
}
return pageStore;
}
}

MVStore初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public static Store init(final Database db) {
Store store = db.getStore();
if (store != null) {
return store;
}
byte[] key = db.getFileEncryptionKey();
String dbPath = db.getDatabasePath();
MVStore.Builder builder = new MVStore.Builder();
store = new Store();
boolean encrypted = false;
if (dbPath != null) {
String fileName = dbPath + Constants.SUFFIX_MV_FILE;
MVStoreTool.compactCleanUp(fileName);
builder.fileName(fileName);
builder.pageSplitSize(db.getPageSize());
if (db.isReadOnly()) {
builder.readOnly();
} else {
// possibly create the directory
boolean exists = FileUtils.exists(fileName);
if (exists && !FileUtils.canWrite(fileName)) {
// read only
} else {
String dir = FileUtils.getParent(fileName);
FileUtils.createDirectories(dir);
}
int autoCompactFillRate = db.getSettings().maxCompactCount;
if (autoCompactFillRate <= 100) {
builder.autoCompactFillRate(autoCompactFillRate);
}
}
if (key != null) {
encrypted = true;
builder.encryptionKey(decodePassword(key));
}
if (db.getSettings().compressData) {
builder.compress();
// use a larger page split size to improve the compression ratio
builder.pageSplitSize(64 * 1024);
}
builder.backgroundExceptionHandler(new UncaughtExceptionHandler() {

@Override
public void uncaughtException(Thread t, Throwable e) {
db.setBackgroundException(DbException.convert(e));
}

});
// always start without background thread first, and if necessary,
// it will be set up later, after db has been fully started,
// otherwise background thread would compete for store lock
// with maps opening procedure
builder.autoCommitDisabled();
}
store.open(db, builder, encrypted);
db.setStore(store);
return store;
}


/**
* Open the store for this database.
*
* @param db the database
* @param builder the builder
* @param encrypted whether the store is encrypted
*/

void open(Database db, MVStore.Builder builder, boolean encrypted) {
this.encrypted = encrypted;
try {
//初始化MvStore
this.mvStore = builder.open();
FileStore fs = mvStore.getFileStore();
if (fs != null) {
this.fileName = fs.getFileName();
}
if (!db.getSettings().reuseSpace) {
mvStore.setReuseSpace(false);
}
mvStore.setVersionsToKeep(0);
//初始化 TransactionStore
this.transactionStore = new TransactionStore(mvStore,
new ValueDataType(db, null), db.getLockTimeout());
} catch (IllegalStateException e) {
throw convertIllegalStateException(e);
}
}

首先创建MVStore.Builder对象,利用MVStore.Builder对象创建MVStore,MVStore.Builder可配置的参数:

  • autoCommitBufferSize
  • autoCompactFillRate
  • backgroundExceptionHandler
  • cacheSize
  • compress (LZF和Deflate)
  • encryptionKey
  • fileName
  • fileStore
  • pageSplitSize
  • readOnly

表与存储引擎的关系

一个数据库对应:

  • 一个MVStore实例
  • 一个TransactionStore实例
  • 一个preparedTransactions MVMap
  • 一个undoLog MVMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
MVStore(Map<String, Object> config) {
recoveryMode = config.containsKey("recoveryMode");
compressionLevel = DataUtils.getConfigParam(config, "compress", 0);
String fileName = (String) config.get("fileName");
FileStore fileStore = (FileStore) config.get("fileStore");
fileStoreIsProvided = fileStore != null;
if(fileStore == null && fileName != null) {
fileStore = new FileStore();
}
this.fileStore = fileStore;

int pgSplitSize = 48; // for "mem:" case it is # of keys
CacheLongKeyLIRS.Config cc = null;
if (this.fileStore != null) {
int mb = DataUtils.getConfigParam(config, "cacheSize", 16);
if (mb > 0) {
cc = new CacheLongKeyLIRS.Config();
cc.maxMemory = mb * 1024L * 1024L;
Object o = config.get("cacheConcurrency");
if (o != null) {
cc.segmentCount = (Integer)o;
}
}
pgSplitSize = 16 * 1024;
}
if (cc != null) {
cache = new CacheLongKeyLIRS<>(cc);
} else {
cache = null;
}

pgSplitSize = DataUtils.getConfigParam(config, "pageSplitSize", pgSplitSize);
// Make sure pages will fit into cache
if (cache != null && pgSplitSize > cache.getMaxItemSize()) {
pgSplitSize = (int)cache.getMaxItemSize();
}
pageSplitSize = pgSplitSize;
keysPerPage = DataUtils.getConfigParam(config, "keysPerPage", 48);
backgroundExceptionHandler =
(UncaughtExceptionHandler)config.get("backgroundExceptionHandler");
meta = new MVMap<>(this);
if (this.fileStore != null) {
retentionTime = this.fileStore.getDefaultRetentionTime();
// 19 KB memory is about 1 KB storage
int kb = Math.max(1, Math.min(19, Utils.scaleForAvailableMemory(64))) * 1024;
kb = DataUtils.getConfigParam(config, "autoCommitBufferSize", kb);
autoCommitMemory = kb * 1024;
autoCompactFillRate = DataUtils.getConfigParam(config, "autoCompactFillRate", 90);
char[] encryptionKey = (char[]) config.get("encryptionKey");
try {
if (!fileStoreIsProvided) {
boolean readOnly = config.containsKey("readOnly");
//创建数据库文件 *.db
this.fileStore.open(fileName, readOnly, encryptionKey);
}
//往 *.db 写数据
if (this.fileStore.size() == 0) {
creationTime = getTimeAbsolute();
lastCommitTime = creationTime;
storeHeader.put(HDR_H, 2);
storeHeader.put(HDR_BLOCK_SIZE, BLOCK_SIZE);
storeHeader.put(HDR_FORMAT, FORMAT_WRITE);
storeHeader.put(HDR_CREATED, creationTime);
writeStoreHeader();
} else {
// there is no need to lock store here, since it is not opened yet,
// just to make some assertions happy, when they ensure single-threaded access
storeLock.lock();
try {
readStoreHeader();
} finally {
storeLock.unlock();
}
}
} catch (IllegalStateException e) {
panic(e);
} finally {
if (encryptionKey != null) {
Arrays.fill(encryptionKey, (char) 0);
}
}
lastCommitTime = getTimeSinceCreation();

scrubMetaMap();

// setAutoCommitDelay starts the thread, but only if
// the parameter is different from the old value
int delay = DataUtils.getConfigParam(config, "autoCommitDelay", 1000);
setAutoCommitDelay(delay);
} else {
autoCommitMemory = 0;
autoCompactFillRate = 0;
}
}

MVStore 初始化了7个MVMap,新建一个表对底层存储引擎来说其实就是新建一个MVMap,然后往这个MVMap插入Page,最终数据落到硬盘里的*.db文件里。

  • meta
    id:0
    name:meta

  • openTransactions
    id:1
    name: openTransactions

  • undoLog.1
    id:2
    name:undoLog.1

  • table.0
    id:3
    name:table.0

  • lobMap
    id:4
    name:lobMap

  • lobRef
    id:5
    name:lobRef

  • lobData
    id:6
    name:lobData