提交 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 ...@@ -19,7 +19,10 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <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 </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. instead of the default class loader if the system property "h2.useThreadContextClassLoader" is set.
Thanks a lot to Noah Fontes for the patch! Thanks a lot to Noah Fontes for the patch!
......
...@@ -27,6 +27,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -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"). </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. 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>Remove the old connection pool logic (system property "h2.fastConnectionPool").
</li><li>Enable "h2.modifyOnWrite".
</li></ul> </li></ul>
<h2>Priority 1</h2> <h2>Priority 1</h2>
......
...@@ -232,6 +232,15 @@ public class SysProperties { ...@@ -232,6 +232,15 @@ public class SysProperties {
*/ */
public static final long MAX_TRACE_DATA_LENGTH = Utils.getProperty("h2.maxTraceDataLength", 65535); 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 /> * System property <code>h2.minColumnNameMap</code> (default: 3).<br />
* The minimum number of columns where a hash table is created when result set * The minimum number of columns where a hash table is created when result set
......
...@@ -422,6 +422,10 @@ public class PageBtreeIndex extends PageIndex { ...@@ -422,6 +422,10 @@ public class PageBtreeIndex extends PageIndex {
} }
public void writeRowCount() { public void writeRowCount() {
if (SysProperties.MODIFY_ON_WRITE && rootPageId == 0) {
// currently creating the index
return;
}
PageBtree root = getPage(rootPageId); PageBtree root = getPage(rootPageId);
root.setRowCountStored(MathUtils.convertLongToInt(rowCount)); root.setRowCountStored(MathUtils.convertLongToInt(rowCount));
} }
......
...@@ -12,6 +12,7 @@ import java.util.HashSet; ...@@ -12,6 +12,7 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.UndoLogRecord; import org.h2.engine.UndoLogRecord;
...@@ -507,6 +508,10 @@ public class PageDataIndex extends PageIndex { ...@@ -507,6 +508,10 @@ public class PageDataIndex extends PageIndex {
} }
public void writeRowCount() { public void writeRowCount() {
if (SysProperties.MODIFY_ON_WRITE && rootPageId == 0) {
// currently creating the index
return;
}
try { try {
PageData root = getPage(rootPageId, 0); PageData root = getPage(rootPageId, 0);
root.setRowCountStored(MathUtils.convertLongToInt(rowCount)); root.setRowCountStored(MathUtils.convertLongToInt(rowCount));
......
...@@ -249,8 +249,9 @@ public class PageLog { ...@@ -249,8 +249,9 @@ public class PageLog {
* committed operations are re-applied. * committed operations are re-applied.
* *
* @param stage the recovery stage * @param stage the recovery stage
* @return whether the transaction log was empty
*/ */
void recover(int stage) { boolean recover(int stage) {
if (trace.isDebugEnabled()) { if (trace.isDebugEnabled()) {
trace.debug("log recover stage: " + stage); trace.debug("log recover stage: " + stage);
} }
...@@ -258,12 +259,13 @@ public class PageLog { ...@@ -258,12 +259,13 @@ public class PageLog {
PageInputStream in = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage); PageInputStream in = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage);
usedLogPages = in.allocateAllPages(); usedLogPages = in.allocateAllPages();
in.close(); in.close();
return; return true;
} }
pageIn = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage); pageIn = new PageInputStream(store, logKey, firstTrunkPage, firstDataPage);
DataReader in = new DataReader(pageIn); DataReader in = new DataReader(pageIn);
int logId = 0; int logId = 0;
Data data = store.createData(); Data data = store.createData();
boolean isEmpty = true;
try { try {
int pos = 0; int pos = 0;
while (true) { while (true) {
...@@ -272,6 +274,7 @@ public class PageLog { ...@@ -272,6 +274,7 @@ public class PageLog {
break; break;
} }
pos++; pos++;
isEmpty = false;
if (x == UNDO) { if (x == UNDO) {
int pageId = in.readVarInt(); int pageId = in.readVarInt();
int size = in.readVarInt(); int size = in.readVarInt();
...@@ -409,6 +412,7 @@ public class PageLog { ...@@ -409,6 +412,7 @@ public class PageLog {
if (stage == RECOVERY_STAGE_REDO) { if (stage == RECOVERY_STAGE_REDO) {
usedLogPages = null; usedLogPages = null;
} }
return isEmpty;
} }
/** /**
......
...@@ -195,6 +195,7 @@ public class PageStore implements CacheWriter { ...@@ -195,6 +195,7 @@ public class PageStore implements CacheWriter {
private HashMap<String, Integer> statistics; private HashMap<String, Integer> statistics;
private int logMode = LOG_MODE_SYNC; private int logMode = LOG_MODE_SYNC;
private boolean lockFile; private boolean lockFile;
private boolean readMode;
/** /**
* Create a new page store object. * Create a new page store object.
...@@ -351,18 +352,29 @@ public class PageStore implements CacheWriter { ...@@ -351,18 +352,29 @@ public class PageStore implements CacheWriter {
// the multi-version index sometimes compares rows // the multi-version index sometimes compares rows
// and the LOB storage is not yet available. // and the LOB storage is not yet available.
database.setMultiVersion(false); database.setMultiVersion(false);
recover(); boolean isEmpty = recover();
database.setMultiVersion(old); database.setMultiVersion(old);
if (!database.isReadOnly()) { if (!database.isReadOnly()) {
recoveryRunning = true; readMode = true;
log.free(); if (!isEmpty || !SysProperties.MODIFY_ON_WRITE || tempObjects != null) {
logFirstTrunkPage = allocatePage(); openForWriting();
log.openForWriting(logFirstTrunkPage, false); removeOldTempIndexes();
recoveryRunning = false; }
freed.set(0, pageCount, true); }
checkpoint(); }
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() { private void removeOldTempIndexes() {
...@@ -400,7 +412,7 @@ public class PageStore implements CacheWriter { ...@@ -400,7 +412,7 @@ public class PageStore implements CacheWriter {
*/ */
public synchronized void checkpoint() { public synchronized void checkpoint() {
trace.debug("checkpoint"); trace.debug("checkpoint");
if (log == null || database.isReadOnly()) { if (log == null || readMode || database.isReadOnly()) {
// the file was never fully opened // the file was never fully opened
return; return;
} }
...@@ -454,6 +466,10 @@ public class PageStore implements CacheWriter { ...@@ -454,6 +466,10 @@ public class PageStore implements CacheWriter {
if (!database.getSettings().pageStoreTrim) { if (!database.getSettings().pageStoreTrim) {
return; return;
} }
if (SysProperties.MODIFY_ON_WRITE && readMode && compactMode == 0) {
return;
}
openForWriting();
// find the last used page // find the last used page
int lastUsed = -1; int lastUsed = -1;
for (int i = getFreeListId(pageCount); i >= 0; i--) { for (int i = getFreeListId(pageCount); i >= 0; i--) {
...@@ -1005,6 +1021,7 @@ public class PageStore implements CacheWriter { ...@@ -1005,6 +1021,7 @@ public class PageStore implements CacheWriter {
if (old == null) { if (old == null) {
old = readPage(pos); old = readPage(pos);
} }
openForWriting();
log.addUndo(pos, old); log.addUndo(pos, old);
} }
} }
...@@ -1113,6 +1130,7 @@ public class PageStore implements CacheWriter { ...@@ -1113,6 +1130,7 @@ public class PageStore implements CacheWriter {
* @return the page id * @return the page id
*/ */
public synchronized int allocatePage() { public synchronized int allocatePage() {
openForWriting();
int pos = allocatePage(null, 0); int pos = allocatePage(null, 0);
if (!recoveryRunning) { if (!recoveryRunning) {
if (logMode != LOG_MODE_OFF) { if (logMode != LOG_MODE_OFF) {
...@@ -1318,11 +1336,14 @@ public class PageStore implements CacheWriter { ...@@ -1318,11 +1336,14 @@ public class PageStore implements CacheWriter {
/** /**
* Run recovery. * Run recovery.
*
* @return whether the transaction log was empty
*/ */
private void recover() { private boolean recover() {
trace.debug("log recover"); trace.debug("log recover");
recoveryRunning = true; recoveryRunning = true;
log.recover(PageLog.RECOVERY_STAGE_UNDO); boolean isEmpty = true;
isEmpty &= log.recover(PageLog.RECOVERY_STAGE_UNDO);
if (reservedPages != null) { if (reservedPages != null) {
for (int r : reservedPages.keySet()) { for (int r : reservedPages.keySet()) {
if (trace.isDebugEnabled()) { if (trace.isDebugEnabled()) {
...@@ -1331,10 +1352,10 @@ public class PageStore implements CacheWriter { ...@@ -1331,10 +1352,10 @@ public class PageStore implements CacheWriter {
allocatePage(r); allocatePage(r);
} }
} }
log.recover(PageLog.RECOVERY_STAGE_ALLOCATE); isEmpty &= log.recover(PageLog.RECOVERY_STAGE_ALLOCATE);
openMetaIndex(); openMetaIndex();
readMetaData(); readMetaData();
log.recover(PageLog.RECOVERY_STAGE_REDO); isEmpty &= log.recover(PageLog.RECOVERY_STAGE_REDO);
boolean setReadOnly = false; boolean setReadOnly = false;
if (!database.isReadOnly()) { if (!database.isReadOnly()) {
if (log.getInDoubtTransactions().size() == 0) { if (log.getInDoubtTransactions().size() == 0) {
...@@ -1376,6 +1397,7 @@ public class PageStore implements CacheWriter { ...@@ -1376,6 +1397,7 @@ public class PageStore implements CacheWriter {
database.setReadOnly(true); database.setReadOnly(true);
} }
trace.debug("log recover done"); trace.debug("log recover done");
return isEmpty;
} }
/** /**
...@@ -1401,6 +1423,7 @@ public class PageStore implements CacheWriter { ...@@ -1401,6 +1423,7 @@ public class PageStore implements CacheWriter {
*/ */
public synchronized void commit(Session session) { public synchronized void commit(Session session) {
checkOpen(); checkOpen();
openForWriting();
log.commit(session.getId()); log.commit(session.getId());
if (log.getSize() - logSizeBase > maxLogSize) { if (log.getSize() - logSizeBase > maxLogSize) {
checkpoint(); checkpoint();
...@@ -1816,6 +1839,7 @@ public class PageStore implements CacheWriter { ...@@ -1816,6 +1839,7 @@ public class PageStore implements CacheWriter {
*/ */
public synchronized void logTruncate(Session session, int tableId) { public synchronized void logTruncate(Session session, int tableId) {
if (!recoveryRunning) { if (!recoveryRunning) {
openForWriting();
log.logTruncate(session, tableId); log.logTruncate(session, tableId);
} }
} }
......
...@@ -137,6 +137,7 @@ import org.h2.test.unit.TestFtp; ...@@ -137,6 +137,7 @@ import org.h2.test.unit.TestFtp;
import org.h2.test.unit.TestIntArray; import org.h2.test.unit.TestIntArray;
import org.h2.test.unit.TestIntIntHashMap; import org.h2.test.unit.TestIntIntHashMap;
import org.h2.test.unit.TestJmx; import org.h2.test.unit.TestJmx;
import org.h2.test.unit.TestModifyOnWrite;
import org.h2.test.unit.TestObjectDeserialization; import org.h2.test.unit.TestObjectDeserialization;
import org.h2.test.unit.TestTraceSystem; import org.h2.test.unit.TestTraceSystem;
import org.h2.test.unit.TestMathUtils; import org.h2.test.unit.TestMathUtils;
...@@ -341,6 +342,9 @@ java org.h2.test.TestAll timer ...@@ -341,6 +342,9 @@ java org.h2.test.TestAll timer
System.setProperty("h2.delayWrongPasswordMax", "0"); System.setProperty("h2.delayWrongPasswordMax", "0");
System.setProperty("h2.useThreadContextClassLoader", "true"); System.setProperty("h2.useThreadContextClassLoader", "true");
int testing;
System.setProperty("h2.modifyOnWrite", "true");
// System.setProperty("h2.storeLocalTime", "true"); // System.setProperty("h2.storeLocalTime", "true");
// speedup // speedup
...@@ -668,6 +672,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -668,6 +672,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestIntIntHashMap().runTest(this); new TestIntIntHashMap().runTest(this);
new TestJmx().runTest(this); new TestJmx().runTest(this);
new TestMathUtils().runTest(this); new TestMathUtils().runTest(this);
new TestModifyOnWrite().runTest(this);
new TestOldVersion().runTest(this); new TestOldVersion().runTest(this);
new TestNetUtils().runTest(this); new TestNetUtils().runTest(this);
new TestObjectDeserialization().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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论