Unverified 提交 f796e624 authored 作者: Andrei Tokar's avatar Andrei Tokar 提交者: GitHub

Merge pull request #1188 from h2database/undo-log-split

Undo log split to reduce contention
...@@ -77,8 +77,12 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -77,8 +77,12 @@ public class AlterTableAddConstraint extends SchemaCommand {
try { try {
return tryUpdate(); return tryUpdate();
} catch (DbException e) { } catch (DbException e) {
for (Index index : createdIndexes) { try {
session.getDatabase().removeSchemaObject(session, index); for (Index index : createdIndexes) {
session.getDatabase().removeSchemaObject(session, index);
}
} catch (Throwable ex) {
e.addSuppressed(ex);
} }
throw e; throw e;
} finally { } finally {
......
...@@ -159,10 +159,14 @@ public class CreateTable extends CommandWithColumns { ...@@ -159,10 +159,14 @@ public class CreateTable extends CommandWithColumns {
} }
} }
} catch (DbException e) { } catch (DbException e) {
db.checkPowerOff(); try {
db.removeSchemaObject(session, table); db.checkPowerOff();
if (!transactional) { db.removeSchemaObject(session, table);
session.commit(true); if (!transactional) {
session.commit(true);
}
} catch (Throwable ex) {
e.addSuppressed(ex);
} }
throw e; throw e;
} }
......
...@@ -752,6 +752,9 @@ public class Database implements DataHandler { ...@@ -752,6 +752,9 @@ public class Database implements DataHandler {
getPageStore(); getPageStore();
} }
} }
if(mvStore != null) {
mvStore.getTransactionStore().init();
}
systemUser = new User(this, 0, SYSTEM_USER_NAME, true); systemUser = new User(this, 0, SYSTEM_USER_NAME, true);
mainSchema = new Schema(this, 0, Constants.SCHEMA_MAIN, systemUser, true); mainSchema = new Schema(this, 0, Constants.SCHEMA_MAIN, systemUser, true);
infoSchema = new Schema(this, -1, "INFORMATION_SCHEMA", systemUser, true); infoSchema = new Schema(this, -1, "INFORMATION_SCHEMA", systemUser, true);
...@@ -762,9 +765,6 @@ public class Database implements DataHandler { ...@@ -762,9 +765,6 @@ public class Database implements DataHandler {
systemUser.setAdmin(true); systemUser.setAdmin(true);
systemSession = new Session(this, systemUser, ++nextSessionId); systemSession = new Session(this, systemUser, ++nextSessionId);
lobSession = new Session(this, systemUser, ++nextSessionId); lobSession = new Session(this, systemUser, ++nextSessionId);
if(mvStore != null) {
mvStore.getTransactionStore().init(systemSession);
}
CreateTableData data = new CreateTableData(); CreateTableData data = new CreateTableData();
ArrayList<Column> cols = data.columns; ArrayList<Column> cols = data.columns;
Column columnId = new Column("ID", Value.INT); Column columnId = new Column("ID", Value.INT);
......
...@@ -300,10 +300,10 @@ public class Trace { ...@@ -300,10 +300,10 @@ public class Trace {
if (!space) { if (!space) {
buff.append(' '); buff.append(' ');
} }
buff.append("*/"). buff.append("*/");
append(StringUtils.javaEncode(sql)). StringUtils.javaEncode(sql, buff);
append(StringUtils.javaEncode(params)). StringUtils.javaEncode(params, buff);
append(';'); buff.append(';');
sql = buff.toString(); sql = buff.toString();
traceWriter.write(TraceSystem.INFO, module, sql, null); traceWriter.write(TraceSystem.INFO, module, sql, null);
} }
......
...@@ -128,17 +128,15 @@ public class FileStore { ...@@ -128,17 +128,15 @@ public class FileStore {
if (file != null) { if (file != null) {
return; return;
} }
if (fileName != null) { // ensure the Cache file system is registered
// ensure the Cache file system is registered FilePathCache.INSTANCE.getScheme();
FilePathCache.INSTANCE.getScheme(); FilePath p = FilePath.get(fileName);
FilePath p = FilePath.get(fileName); // if no explicit scheme was specified, NIO is used
// if no explicit scheme was specified, NIO is used if (p instanceof FilePathDisk &&
if (p instanceof FilePathDisk && !fileName.startsWith(p.getScheme() + ":")) {
!fileName.startsWith(p.getScheme() + ":")) { // ensure the NIO file system is registered
// ensure the NIO file system is registered FilePathNio.class.getName();
FilePathNio.class.getName(); fileName = "nio:" + fileName;
fileName = "nio:" + fileName;
}
} }
this.fileName = fileName; this.fileName = fileName;
FilePath f = FilePath.get(fileName); FilePath f = FilePath.get(fileName);
......
...@@ -253,7 +253,7 @@ public class MVTableEngine implements TableEngine { ...@@ -253,7 +253,7 @@ public class MVTableEngine implements TableEngine {
public void initTransactions() { public void initTransactions() {
List<Transaction> list = transactionStore.getOpenTransactions(); List<Transaction> list = transactionStore.getOpenTransactions();
for (Transaction t : list) { for (Transaction t : list) {
if (t.getStatus() == Transaction.STATUS_COMMITTING) { if (t.getStatus() == Transaction.STATUS_COMMITTED) {
t.commit(); t.commit();
} else if (t.getStatus() != Transaction.STATUS_PREPARED) { } else if (t.getStatus() != Transaction.STATUS_PREPARED) {
t.rollback(); t.rollback();
......
...@@ -30,19 +30,22 @@ final class RollbackDecisionMaker extends MVMap.DecisionMaker<Object[]> { ...@@ -30,19 +30,22 @@ final class RollbackDecisionMaker extends MVMap.DecisionMaker<Object[]> {
@Override @Override
public MVMap.Decision decide(Object[] existingValue, Object[] providedValue) { public MVMap.Decision decide(Object[] existingValue, Object[] providedValue) {
assert decision == null; assert decision == null;
assert existingValue != null; // normaly existingValue will always be there except of db initialization
VersionedValue valueToRestore = (VersionedValue) existingValue[2]; // where some undo log enty was captured on disk but actual map entry was not
long operationId; if (existingValue != null ) {
if (valueToRestore == null || VersionedValue valueToRestore = (VersionedValue) existingValue[2];
(operationId = valueToRestore.getOperationId()) == 0 || long operationId;
TransactionStore.getTransactionId(operationId) == transactionId if (valueToRestore == null ||
&& TransactionStore.getLogId(operationId) < toLogId) { (operationId = valueToRestore.getOperationId()) == 0 ||
int mapId = (Integer) existingValue[0]; TransactionStore.getTransactionId(operationId) == transactionId
MVMap<Object, VersionedValue> map = store.openMap(mapId); && TransactionStore.getLogId(operationId) < toLogId) {
if (map != null && !map.isClosed()) { int mapId = (Integer) existingValue[0];
Object key = existingValue[1]; MVMap<Object, VersionedValue> map = store.openMap(mapId);
VersionedValue previousValue = map.operate(key, valueToRestore, MVMap.DecisionMaker.DEFAULT); if (map != null && !map.isClosed()) {
listener.onRollback(map, key, previousValue, valueToRestore); Object key = existingValue[1];
VersionedValue previousValue = map.operate(key, valueToRestore, MVMap.DecisionMaker.DEFAULT);
listener.onRollback(map, key, previousValue, valueToRestore);
}
} }
} }
decision = MVMap.Decision.REMOVE; decision = MVMap.Decision.REMOVE;
......
...@@ -32,41 +32,36 @@ public class Transaction { ...@@ -32,41 +32,36 @@ public class Transaction {
*/ */
public static final int STATUS_PREPARED = 2; public static final int STATUS_PREPARED = 2;
/**
* The status of a transaction that is being committed, but possibly not
* yet finished. A transactions can go into this state when the store is
* closed while the transaction is committing. When opening a store,
* such transactions should be committed.
*/
public static final int STATUS_COMMITTING = 3;
/** /**
* The status of a transaction that has been logically committed or rather * The status of a transaction that has been logically committed or rather
* marked as committed, because it might be still listed among prepared, * marked as committed, because it might be still listed among prepared,
* if it was prepared for commit, undo log entries might still exists for it * if it was prepared for commit. Undo log entries might still exists for it
* and not all of it's changes within map's are re-written as committed yet. * and not all of it's changes within map's are re-written as committed yet.
* Nevertheless, those changes should be already viewed by other * Nevertheless, those changes should be already viewed by other
* transactions as committed. * transactions as committed.
* This transaction's id can not be re-used until all the above is completed * This transaction's id can not be re-used until all of the above is completed
* and transaction is closed. * and transaction is closed.
* A transactions can be observed in this state when the store was
* closed while the transaction was not closed yet.
* When opening a store, such transactions will automatically
* be processed and closed as committed.
*/ */
private static final int STATUS_COMMITTED = 4; public static final int STATUS_COMMITTED = 3;
/** /**
* The status of a transaction that currently in a process of rolling back * The status of a transaction that currently in a process of rolling back
* to a savepoint. * to a savepoint.
*/ */
private static final int STATUS_ROLLING_BACK = 5; private static final int STATUS_ROLLING_BACK = 4;
/** /**
* The status of a transaction that has been rolled back completely, * The status of a transaction that has been rolled back completely,
* but undo operations are not finished yet. * but undo operations are not finished yet.
*/ */
private static final int STATUS_ROLLED_BACK = 6; private static final int STATUS_ROLLED_BACK = 5;
private static final String STATUS_NAMES[] = { private static final String STATUS_NAMES[] = {
"CLOSED", "OPEN", "PREPARED", "COMMITTING", "CLOSED", "OPEN", "PREPARED", "COMMITTED", "ROLLING_BACK", "ROLLED_BACK"
"COMMITTED", "ROLLING_BACK", "ROLLED_BACK"
}; };
static final int LOG_ID_BITS = 40; static final int LOG_ID_BITS = 40;
private static final int LOG_ID_BITS1 = LOG_ID_BITS + 1; private static final int LOG_ID_BITS1 = LOG_ID_BITS + 1;
...@@ -175,6 +170,11 @@ public class Transaction { ...@@ -175,6 +170,11 @@ public class Transaction {
return getStatus(statusAndLogId.get()); return getStatus(statusAndLogId.get());
} }
/**
* Changes transaction status to a specified value
* @param status to be set
* @return transaction state as it was before status change
*/
long setStatus(int status) { long setStatus(int status) {
while (true) { while (true) {
long currentState = statusAndLogId.get(); long currentState = statusAndLogId.get();
...@@ -192,23 +192,19 @@ public class Transaction { ...@@ -192,23 +192,19 @@ public class Transaction {
case STATUS_PREPARED: case STATUS_PREPARED:
valid = currentStatus == STATUS_OPEN; valid = currentStatus == STATUS_OPEN;
break; break;
case STATUS_COMMITTING: case STATUS_COMMITTED:
valid = currentStatus == STATUS_OPEN || valid = currentStatus == STATUS_OPEN ||
currentStatus == STATUS_PREPARED || currentStatus == STATUS_PREPARED ||
// this case is only possible if called // this case is only possible if called
// from endLeftoverTransactions() // from endLeftoverTransactions()
currentStatus == STATUS_COMMITTING; currentStatus == STATUS_COMMITTED;
break;
case STATUS_COMMITTED:
valid = currentStatus == STATUS_COMMITTING;
break; break;
case STATUS_ROLLED_BACK: case STATUS_ROLLED_BACK:
valid = currentStatus == STATUS_OPEN || valid = currentStatus == STATUS_OPEN ||
currentStatus == STATUS_PREPARED; currentStatus == STATUS_PREPARED;
break; break;
case STATUS_CLOSED: case STATUS_CLOSED:
valid = currentStatus == STATUS_COMMITTING || valid = currentStatus == STATUS_COMMITTED ||
currentStatus == STATUS_COMMITTED ||
currentStatus == STATUS_ROLLED_BACK; currentStatus == STATUS_ROLLED_BACK;
break; break;
default: default:
...@@ -365,11 +361,11 @@ public class Transaction { ...@@ -365,11 +361,11 @@ public class Transaction {
Throwable ex = null; Throwable ex = null;
boolean hasChanges = false; boolean hasChanges = false;
try { try {
long state = setStatus(STATUS_COMMITTING); long state = setStatus(STATUS_COMMITTED);
hasChanges = hasChanges(state); hasChanges = hasChanges(state);
int previousStatus = getStatus(state);
if (hasChanges) { if (hasChanges) {
long logId = getLogId(state); store.commit(this, previousStatus == STATUS_COMMITTED);
store.commit(this, logId);
} }
} catch (Throwable e) { } catch (Throwable e) {
ex = e; ex = e;
......
...@@ -77,11 +77,22 @@ public class TransactionMap<K, V> { ...@@ -77,11 +77,22 @@ public class TransactionMap<K, V> {
// when none of the variables concurrently changes it's value. // when none of the variables concurrently changes it's value.
BitSet committingTransactions; BitSet committingTransactions;
MVMap.RootReference mapRootReference; MVMap.RootReference mapRootReference;
MVMap.RootReference undoLogRootReference; MVMap.RootReference[] undoLogRootReferences;
long undoLogSize;
do { do {
committingTransactions = store.committingTransactions.get(); committingTransactions = store.committingTransactions.get();
mapRootReference = map.getRoot(); mapRootReference = map.getRoot();
undoLogRootReference = store.undoLog.getRoot(); BitSet opentransactions = store.openTransactions.get();
undoLogRootReferences = new MVMap.RootReference[opentransactions.length()];
undoLogSize = 0;
for (int i = opentransactions.nextSetBit(0); i >= 0; i = opentransactions.nextSetBit(i+1)) {
MVMap<Long, Object[]> undoLog = store.undoLogs[i];
if (undoLog != null) {
MVMap.RootReference rootReference = undoLog.getRoot();
undoLogRootReferences[i] = rootReference;
undoLogSize += rootReference.root.getTotalCount();
}
}
} while(committingTransactions != store.committingTransactions.get() || } while(committingTransactions != store.committingTransactions.get() ||
mapRootReference != map.getRoot()); mapRootReference != map.getRoot());
// Now we have a snapshot, where mapRootReference points to state of the map, // Now we have a snapshot, where mapRootReference points to state of the map,
...@@ -89,8 +100,6 @@ public class TransactionMap<K, V> { ...@@ -89,8 +100,6 @@ public class TransactionMap<K, V> {
// and committingTransactions mask tells us which of seemingly uncommitted changes // and committingTransactions mask tells us which of seemingly uncommitted changes
// should be considered as committed. // should be considered as committed.
// Subsequent processing uses this snapshot info only. // Subsequent processing uses this snapshot info only.
Page undoRootPage = undoLogRootReference.root;
long undoLogSize = undoRootPage.getTotalCount();
Page mapRootPage = mapRootReference.root; Page mapRootPage = mapRootReference.root;
long size = mapRootPage.getTotalCount(); long size = mapRootPage.getTotalCount();
// if we are looking at the map without any uncommitted values // if we are looking at the map without any uncommitted values
...@@ -112,7 +121,8 @@ public class TransactionMap<K, V> { ...@@ -112,7 +121,8 @@ public class TransactionMap<K, V> {
long operationId = currentValue.getOperationId(); long operationId = currentValue.getOperationId();
if (operationId != 0) { // skip committed entries if (operationId != 0) { // skip committed entries
int txId = TransactionStore.getTransactionId(operationId); int txId = TransactionStore.getTransactionId(operationId);
boolean isVisible = txId == transaction.transactionId || committingTransactions.get(txId); boolean isVisible = txId == transaction.transactionId ||
committingTransactions.get(txId);
Object v = isVisible ? currentValue.value : currentValue.getCommittedValue(); Object v = isVisible ? currentValue.value : currentValue.getCommittedValue();
if (v == null) { if (v == null) {
--size; --size;
...@@ -120,26 +130,31 @@ public class TransactionMap<K, V> { ...@@ -120,26 +130,31 @@ public class TransactionMap<K, V> {
} }
} }
} else { } else {
// The undo log is much smaller than the map - scan the undo log, and then lookup relevant map entry. // The undo logs are much smaller than the map - scan all undo logs, and then lookup relevant map entry.
Cursor<Long, Object[]> cursor = new Cursor<>(undoRootPage, null); for (MVMap.RootReference undoLogRootReference : undoLogRootReferences) {
while(cursor.hasNext()) { if (undoLogRootReference != null) {
cursor.next(); Cursor<Long, Object[]> cursor = new Cursor<>(undoLogRootReference.root, null);
Object op[] = cursor.getValue(); while (cursor.hasNext()) {
if ((int)op[0] == map.getId()) { cursor.next();
VersionedValue currentValue = map.get(mapRootPage, op[1]); Object op[] = cursor.getValue();
// If map entry is not there, then we never counted it, in the first place, so skip it. if ((int) op[0] == map.getId()) {
// This is possible when undo entry exists because it belongs VersionedValue currentValue = map.get(mapRootPage, op[1]);
// to a committed but not yet closed transaction, // If map entry is not there, then we never counted it, in the first place, so skip it.
// and it was later deleted by some other already committed and closed transaction. // This is possible when undo entry exists because it belongs
if (currentValue != null) { // to a committed but not yet closed transaction,
// only the last undo entry for any given map key should be considered // and it was later deleted by some other already committed and closed transaction.
long operationId = cursor.getKey(); if (currentValue != null) {
if (currentValue.getOperationId() == operationId) { // only the last undo entry for any given map key should be considered
int txId = TransactionStore.getTransactionId(operationId); long operationId = cursor.getKey();
boolean isVisible = txId == transaction.transactionId || committingTransactions.get(txId); if (currentValue.getOperationId() == operationId) {
Object v = isVisible ? currentValue.value : currentValue.getCommittedValue(); int txId = TransactionStore.getTransactionId(operationId);
if (v == null) { boolean isVisible = txId == transaction.transactionId ||
--size; committingTransactions.get(txId);
Object v = isVisible ? currentValue.value : currentValue.getCommittedValue();
if (v == null) {
--size;
}
}
} }
} }
} }
......
...@@ -148,8 +148,13 @@ public class StringUtils { ...@@ -148,8 +148,13 @@ public class StringUtils {
* @return the Java representation * @return the Java representation
*/ */
public static String javaEncode(String s) { public static String javaEncode(String s) {
StringBuilder buff = new StringBuilder(s.length());
javaEncode(s, buff);
return buff.toString();
}
public static void javaEncode(String s, StringBuilder buff) {
int length = s.length(); int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char c = s.charAt(i); char c = s.charAt(i);
switch (c) { switch (c) {
...@@ -202,7 +207,6 @@ public class StringUtils { ...@@ -202,7 +207,6 @@ public class StringUtils {
} }
} }
} }
return buff.toString();
} }
/** /**
......
...@@ -513,8 +513,10 @@ public class TestConcurrent extends TestMVStore { ...@@ -513,8 +513,10 @@ public class TestConcurrent extends TestMVStore {
Thread.sleep(1); Thread.sleep(1);
} }
Exception e = task.getException(); Exception e = task.getException();
assertEquals(DataUtils.ERROR_CLOSED, if (e != null) {
DataUtils.getErrorCode(e.getMessage())); assertEquals(DataUtils.ERROR_CLOSED,
DataUtils.getErrorCode(e.getMessage()));
}
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// sometimes storing works, in which case // sometimes storing works, in which case
// closing must fail // closing must fail
......
...@@ -151,7 +151,7 @@ public class TestStreamStore extends TestBase { ...@@ -151,7 +151,7 @@ public class TestStreamStore extends TestBase {
long readCount = s.getFileStore().getReadCount(); long readCount = s.getFileStore().getReadCount();
// the read count should be low because new blocks // the read count should be low because new blocks
// are appended at the end (not between existing blocks) // are appended at the end (not between existing blocks)
assertTrue("rc: " + readCount, readCount <= 17); assertTrue("rc: " + readCount, readCount <= 20);
map = s.openMap("data"); map = s.openMap("data");
assertTrue("size: " + map.size(), map.sizeAsLong() >= 200); assertTrue("size: " + map.size(), map.sizeAsLong() >= 200);
s.close(); s.close();
......
...@@ -204,6 +204,7 @@ public class TestTransactionStore extends TestBase { ...@@ -204,6 +204,7 @@ public class TestTransactionStore extends TestBase {
break; break;
} }
} }
task.get();
// we expect at least 10% the operations were successful // we expect at least 10% the operations were successful
assertTrue(failCount.toString() + " >= " + (count * 0.9), assertTrue(failCount.toString() + " >= " + (count * 0.9),
failCount.get() < count * 0.9); failCount.get() < count * 0.9);
...@@ -395,24 +396,22 @@ public class TestTransactionStore extends TestBase { ...@@ -395,24 +396,22 @@ public class TestTransactionStore extends TestBase {
store.close(); store.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
// roll back a bit, until we have some undo log entries // roll back a bit, until we have some undo log entries
assertTrue(s.hasMap("undoLog"));
for (int back = 0; back < 100; back++) { for (int back = 0; back < 100; back++) {
int minus = r.nextInt(10); int minus = r.nextInt(10);
s.rollbackTo(Math.max(0, s.getCurrentVersion() - minus)); s.rollbackTo(Math.max(0, s.getCurrentVersion() - minus));
MVMap<?, ?> undo = s.openMap("undoLog"); if (hasDataUndoLog(s)) {
if (undo.size() > 0) {
break; break;
} }
} }
// re-open the store, because we have opened // re-open TransactionStore, because we rolled back
// the undoLog map with the wrong data type // underlying MVStore without rolling back TranactionStore
s.close(); s.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
ts = new TransactionStore(s); ts = new TransactionStore(s);
List<Transaction> list = ts.getOpenTransactions(); List<Transaction> list = ts.getOpenTransactions();
if (list.size() != 0) { if (list.size() != 0) {
tx = list.get(0); tx = list.get(0);
if (tx.getStatus() == Transaction.STATUS_COMMITTING) { if (tx.getStatus() == Transaction.STATUS_COMMITTED) {
i++; i++;
} }
} }
...@@ -422,6 +421,15 @@ public class TestTransactionStore extends TestBase { ...@@ -422,6 +421,15 @@ public class TestTransactionStore extends TestBase {
} }
} }
private boolean hasDataUndoLog(MVStore s) {
for (int i = 0; i < 255; i++) {
if(s.hasData(TransactionStore.getUndoLogName(true, 1))) {
return true;
}
}
return false;
}
private void testGetModifiedMaps() { private void testGetModifiedMaps() {
MVStore s = MVStore.open(null); MVStore s = MVStore.open(null);
TransactionStore ts = new TransactionStore(s); TransactionStore ts = new TransactionStore(s);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论