提交 439b8981 authored 作者: Thomas Mueller's avatar Thomas Mueller

New experimental system property "h2.modifyOnWrite": when enabled, the database…

New experimental system property "h2.modifyOnWrite": when enabled, the database file is only modified when writing to the database. This should speed up the serialized file lock.
上级 5fe06002
......@@ -19,7 +19,10 @@ Change Log
<h1>Change Log</h1>
<h2>Next Version (unreleased)</h2>
<ul><li>A NullPointerException could occur in TableView.isDeterministic for invalid views.
<ul><li>New experimental system property "h2.modifyOnWrite":
when enabled, the database file is only modified when writing to the database.
This should speed up the serialized file lock.
</li><li>A NullPointerException could occur in TableView.isDeterministic for invalid views.
</li><li>Issue 180: when deserializing objects, the context class loader is used
instead of the default class loader if the system property "h2.useThreadContextClassLoader" is set.
Thanks a lot to Noah Fontes for the patch!
......
......@@ -27,6 +27,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Enable the new storage format for dates (system property "h2.storeLocalTime").
Document time literals: between minus 2 million and 2 million hours with nanosecond resolution.
</li><li>Remove the old connection pool logic (system property "h2.fastConnectionPool").
</li><li>Enable "h2.modifyOnWrite".
</li></ul>
<h2>Priority 1</h2>
......
......@@ -232,6 +232,15 @@ public class SysProperties {
*/
public static final long MAX_TRACE_DATA_LENGTH = Utils.getProperty("h2.maxTraceDataLength", 65535);
/**
* System property <code>h2.modifyOnWrite</code> (default: false).<br />
* Only modify the database file when writing to the database. If disabled,
* opening the database modifies the file (to prepare it for writing).
* This only occurs when no recovery is necessary.
* When enabled, the serialized file lock is faster.
*/
public static final boolean MODIFY_ON_WRITE = Utils.getProperty("h2.modifyOnWrite", false);
/**
* System property <code>h2.minColumnNameMap</code> (default: 3).<br />
* The minimum number of columns where a hash table is created when result set
......
......@@ -422,6 +422,10 @@ public class PageBtreeIndex extends PageIndex {
}
public void writeRowCount() {
if (SysProperties.MODIFY_ON_WRITE && rootPageId == 0) {
// currently creating the index
return;
}
PageBtree root = getPage(rootPageId);
root.setRowCountStored(MathUtils.convertLongToInt(rowCount));
}
......
......@@ -12,6 +12,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.engine.Session;
import org.h2.engine.UndoLogRecord;
......@@ -507,6 +508,10 @@ public class PageDataIndex extends PageIndex {
}
public void writeRowCount() {
if (SysProperties.MODIFY_ON_WRITE && rootPageId == 0) {
// currently creating the index
return;
}
try {
PageData root = getPage(rootPageId, 0);
root.setRowCountStored(MathUtils.convertLongToInt(rowCount));
......
......@@ -249,8 +249,9 @@ public class PageLog {
* committed operations are re-applied.
*
* @param stage the recovery stage
* @return whether the transaction log was empty
*/
void recover(int stage) {
boolean recover(int stage) {
if (trace.isDebugEnabled()) {
trace.debug("log recover stage: " + stage);
}
......@@ -258,12 +259,13 @@ public class PageLog {
PageInputStream in = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage);
usedLogPages = in.allocateAllPages();
in.close();
return;
return true;
}
pageIn = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage);
DataReader in = new DataReader(pageIn);
int logId = 0;
Data data = store.createData();
boolean isEmpty = true;
try {
int pos = 0;
while (true) {
......@@ -272,6 +274,7 @@ public class PageLog {
break;
}
pos++;
isEmpty = false;
if (x == UNDO) {
int pageId = in.readVarInt();
int size = in.readVarInt();
......@@ -409,6 +412,7 @@ public class PageLog {
if (stage == RECOVERY_STAGE_REDO) {
usedLogPages = null;
}
return isEmpty;
}
/**
......
......@@ -195,6 +195,7 @@ public class PageStore implements CacheWriter {
private HashMap<String, Integer> statistics;
private int logMode = LOG_MODE_SYNC;
private boolean lockFile;
private boolean readMode;
/**
* Create a new page store object.
......@@ -351,18 +352,29 @@ public class PageStore implements CacheWriter {
// the multi-version index sometimes compares rows
// and the LOB storage is not yet available.
database.setMultiVersion(false);
recover();
boolean isEmpty = recover();
database.setMultiVersion(old);
if (!database.isReadOnly()) {
recoveryRunning = true;
log.free();
logFirstTrunkPage = allocatePage();
log.openForWriting(logFirstTrunkPage, false);
recoveryRunning = false;
freed.set(0, pageCount, true);
checkpoint();
removeOldTempIndexes();
readMode = true;
if (!isEmpty || !SysProperties.MODIFY_ON_WRITE || tempObjects != null) {
openForWriting();
removeOldTempIndexes();
}
}
}
private void openForWriting() {
if (!readMode || database.isReadOnly()) {
return;
}
readMode = false;
recoveryRunning = true;
log.free();
logFirstTrunkPage = allocatePage();
log.openForWriting(logFirstTrunkPage, false);
recoveryRunning = false;
freed.set(0, pageCount, true);
checkpoint();
}
private void removeOldTempIndexes() {
......@@ -400,7 +412,7 @@ public class PageStore implements CacheWriter {
*/
public synchronized void checkpoint() {
trace.debug("checkpoint");
if (log == null || database.isReadOnly()) {
if (log == null || readMode || database.isReadOnly()) {
// the file was never fully opened
return;
}
......@@ -454,6 +466,10 @@ public class PageStore implements CacheWriter {
if (!database.getSettings().pageStoreTrim) {
return;
}
if (SysProperties.MODIFY_ON_WRITE && readMode && compactMode == 0) {
return;
}
openForWriting();
// find the last used page
int lastUsed = -1;
for (int i = getFreeListId(pageCount); i >= 0; i--) {
......@@ -1005,6 +1021,7 @@ public class PageStore implements CacheWriter {
if (old == null) {
old = readPage(pos);
}
openForWriting();
log.addUndo(pos, old);
}
}
......@@ -1113,6 +1130,7 @@ public class PageStore implements CacheWriter {
* @return the page id
*/
public synchronized int allocatePage() {
openForWriting();
int pos = allocatePage(null, 0);
if (!recoveryRunning) {
if (logMode != LOG_MODE_OFF) {
......@@ -1318,11 +1336,14 @@ public class PageStore implements CacheWriter {
/**
* Run recovery.
*
* @return whether the transaction log was empty
*/
private void recover() {
private boolean recover() {
trace.debug("log recover");
recoveryRunning = true;
log.recover(PageLog.RECOVERY_STAGE_UNDO);
boolean isEmpty = true;
isEmpty &= log.recover(PageLog.RECOVERY_STAGE_UNDO);
if (reservedPages != null) {
for (int r : reservedPages.keySet()) {
if (trace.isDebugEnabled()) {
......@@ -1331,10 +1352,10 @@ public class PageStore implements CacheWriter {
allocatePage(r);
}
}
log.recover(PageLog.RECOVERY_STAGE_ALLOCATE);
isEmpty &= log.recover(PageLog.RECOVERY_STAGE_ALLOCATE);
openMetaIndex();
readMetaData();
log.recover(PageLog.RECOVERY_STAGE_REDO);
isEmpty &= log.recover(PageLog.RECOVERY_STAGE_REDO);
boolean setReadOnly = false;
if (!database.isReadOnly()) {
if (log.getInDoubtTransactions().size() == 0) {
......@@ -1376,6 +1397,7 @@ public class PageStore implements CacheWriter {
database.setReadOnly(true);
}
trace.debug("log recover done");
return isEmpty;
}
/**
......@@ -1401,6 +1423,7 @@ public class PageStore implements CacheWriter {
*/
public synchronized void commit(Session session) {
checkOpen();
openForWriting();
log.commit(session.getId());
if (log.getSize() - logSizeBase > maxLogSize) {
checkpoint();
......@@ -1816,6 +1839,7 @@ public class PageStore implements CacheWriter {
*/
public synchronized void logTruncate(Session session, int tableId) {
if (!recoveryRunning) {
openForWriting();
log.logTruncate(session, tableId);
}
}
......
......@@ -137,6 +137,7 @@ import org.h2.test.unit.TestFtp;
import org.h2.test.unit.TestIntArray;
import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestModifyOnWrite;
import org.h2.test.unit.TestObjectDeserialization;
import org.h2.test.unit.TestTraceSystem;
import org.h2.test.unit.TestMathUtils;
......@@ -341,6 +342,9 @@ java org.h2.test.TestAll timer
System.setProperty("h2.delayWrongPasswordMax", "0");
System.setProperty("h2.useThreadContextClassLoader", "true");
int testing;
System.setProperty("h2.modifyOnWrite", "true");
// System.setProperty("h2.storeLocalTime", "true");
// speedup
......@@ -668,6 +672,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestIntIntHashMap().runTest(this);
new TestJmx().runTest(this);
new TestMathUtils().runTest(this);
new TestModifyOnWrite().runTest(this);
new TestOldVersion().runTest(this);
new TestNetUtils().runTest(this);
new TestObjectDeserialization().runTest(this);
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.constant.SysProperties;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.util.IOUtils;
import org.h2.util.Utils;
/**
* Test that the database file is only modified when writing to the database.
*/
public class TestModifyOnWrite extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.modifyOnWrite", "true");
TestBase.createCaller().init().test();
}
public void test() throws Exception {
if (!SysProperties.MODIFY_ON_WRITE) {
return;
}
deleteDb("modifyOnWrite");
String dbFile = getBaseDir() + "/modifyOnWrite.h2.db";
assertFalse(FileUtils.exists(dbFile));
Connection conn = getConnection("modifyOnWrite");
Statement stat = conn.createStatement();
stat.execute("create table test(id int)");
conn.close();
byte[] test = IOUtils.readBytesAndClose(FileUtils.newInputStream(dbFile), -1);
conn = getConnection("modifyOnWrite");
stat = conn.createStatement();
ResultSet rs;
rs = stat.executeQuery("select * from test");
assertFalse(rs.next());
conn.close();
assertTrue(FileUtils.exists(dbFile));
byte[] test2 = IOUtils.readBytesAndClose(FileUtils.newInputStream(dbFile), -1);
assertEquals(test, test2);
conn = getConnection("modifyOnWrite");
stat = conn.createStatement();
stat.execute("insert into test values(1)");
conn.close();
conn = getConnection("modifyOnWrite");
stat = conn.createStatement();
rs = stat.executeQuery("select * from test");
assertTrue(rs.next());
conn.close();
test2 = IOUtils.readBytesAndClose(FileUtils.newInputStream(dbFile), -1);
assertFalse(Utils.compareSecure(test, test2));
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论