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

Merge pull request #1058 from h2database/txcommit-atomic

Txcommit atomic
...@@ -737,6 +737,9 @@ public class Database implements DataHandler { ...@@ -737,6 +737,9 @@ 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);
...@@ -762,6 +765,7 @@ public class Database implements DataHandler { ...@@ -762,6 +765,7 @@ public class Database implements DataHandler {
metaIdIndex = meta.addIndex(systemSession, "SYS_ID", metaIdIndex = meta.addIndex(systemSession, "SYS_ID",
0, pkCols, IndexType.createPrimaryKey( 0, pkCols, IndexType.createPrimaryKey(
false, false), true, null); false, false), true, null);
systemSession.commit(true);
objectIds.set(0); objectIds.set(0);
starting = true; starting = true;
Cursor cursor = metaIdIndex.find(systemSession, null, null); Cursor cursor = metaIdIndex.find(systemSession, null, null);
...@@ -777,6 +781,7 @@ public class Database implements DataHandler { ...@@ -777,6 +781,7 @@ public class Database implements DataHandler {
rec.execute(this, systemSession, eventListener); rec.execute(this, systemSession, eventListener);
} }
} }
systemSession.commit(true);
if (mvStore != null) { if (mvStore != null) {
mvStore.initTransactions(); mvStore.initTransactions();
mvStore.removeTemporaryMaps(objectIds); mvStore.removeTemporaryMaps(objectIds);
......
...@@ -30,10 +30,12 @@ import org.h2.jdbc.JdbcConnection; ...@@ -30,10 +30,12 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.db.MVTable; import org.h2.mvstore.db.MVTable;
import org.h2.mvstore.db.MVTableEngine; import org.h2.mvstore.db.MVTableEngine;
import org.h2.mvstore.tx.TransactionStore.Change; import org.h2.mvstore.tx.TransactionStore;
import org.h2.mvstore.tx.Transaction; import org.h2.mvstore.tx.Transaction;
import org.h2.mvstore.tx.VersionedValue;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -59,7 +61,7 @@ import org.h2.value.ValueString; ...@@ -59,7 +61,7 @@ import org.h2.value.ValueString;
* mode, this object resides on the server side and communicates with a * mode, this object resides on the server side and communicates with a
* SessionRemote object on the client side. * SessionRemote object on the client side.
*/ */
public class Session extends SessionWithState { public class Session extends SessionWithState implements TransactionStore.RollbackListener {
/** /**
* This special log position means that the log entry has been written. * This special log position means that the log entry has been written.
...@@ -649,6 +651,7 @@ public class Session extends SessionWithState { ...@@ -649,6 +651,7 @@ public class Session extends SessionWithState {
currentTransactionName = null; currentTransactionName = null;
transactionStart = 0; transactionStart = 0;
if (transaction != null) { if (transaction != null) {
try {
// increment the data mod count, so that other sessions // increment the data mod count, so that other sessions
// see the changes // see the changes
// TODO should not rely on locking // TODO should not rely on locking
...@@ -660,9 +663,10 @@ public class Session extends SessionWithState { ...@@ -660,9 +663,10 @@ public class Session extends SessionWithState {
} }
} }
transaction.commit(); transaction.commit();
} finally {
transaction = null; transaction = null;
} }
if (containsUncommitted()) { } else if (containsUncommitted()) {
// need to commit even if rollback is not possible // need to commit even if rollback is not possible
// (create/drop table and so on) // (create/drop table and so on)
database.commit(this); database.commit(this);
...@@ -768,18 +772,9 @@ public class Session extends SessionWithState { ...@@ -768,18 +772,9 @@ public class Session extends SessionWithState {
checkCommitRollback(); checkCommitRollback();
currentTransactionName = null; currentTransactionName = null;
transactionStart = 0; transactionStart = 0;
boolean needCommit = false; boolean needCommit = undoLog.size() > 0 || transaction != null;
if (undoLog.size() > 0) { if(needCommit) {
rollbackTo(null, false);
needCommit = true;
}
if (transaction != null) {
rollbackTo(null, false); rollbackTo(null, false);
needCommit = true;
// rollback stored the undo operations in the transaction
// committing will end the transaction
transaction.commit();
transaction = null;
} }
if (!locks.isEmpty() || needCommit) { if (!locks.isEmpty() || needCommit) {
database.commit(this); database.commit(this);
...@@ -806,29 +801,11 @@ public class Session extends SessionWithState { ...@@ -806,29 +801,11 @@ public class Session extends SessionWithState {
undoLog.removeLast(trimToSize); undoLog.removeLast(trimToSize);
} }
if (transaction != null) { if (transaction != null) {
long savepointId = savepoint == null ? 0 : savepoint.transactionSavepoint; if (savepoint == null) {
HashMap<String, MVTable> tableMap = transaction.rollback();
database.getMvStore().getTables(); transaction = null;
Iterator<Change> it = transaction.getChanges(savepointId);
while (it.hasNext()) {
Change c = it.next();
MVTable t = tableMap.get(c.mapName);
if (t != null) {
long key = ((ValueLong) c.key).getLong();
ValueArray value = (ValueArray) c.value;
short op;
Row row;
if (value == null) {
op = UndoLogRecord.INSERT;
row = t.getRow(this, key);
} else { } else {
op = UndoLogRecord.DELETE; transaction.rollbackToSavepoint(savepoint.transactionSavepoint);
row = createRow(value.getList(), Row.MEMORY_CALCULATE);
}
row.setKey(key);
UndoLogRecord log = new UndoLogRecord(t, op, row);
log.undo(this);
}
} }
} }
if (savepoints != null) { if (savepoints != null) {
...@@ -841,6 +818,13 @@ public class Session extends SessionWithState { ...@@ -841,6 +818,13 @@ public class Session extends SessionWithState {
} }
} }
} }
// Because cache may have captured query result (in Query.lastResult),
// which is based on data from uncommitted transaction.,
// It is not valid after rollback, therefore cache has to be cleared.
if(queryCache != null) {
queryCache.clear();
}
} }
@Override @Override
...@@ -1112,7 +1096,7 @@ public class Session extends SessionWithState { ...@@ -1112,7 +1096,7 @@ public class Session extends SessionWithState {
*/ */
public boolean containsUncommitted() { public boolean containsUncommitted() {
if (database.getMvStore() != null) { if (database.getMvStore() != null) {
return transaction != null; return transaction != null && transaction.hasChanges();
} }
return firstUncommittedLog != Session.LOG_WRITTEN; return firstUncommittedLog != Session.LOG_WRITTEN;
} }
...@@ -1688,7 +1672,7 @@ public class Session extends SessionWithState { ...@@ -1688,7 +1672,7 @@ public class Session extends SessionWithState {
database.shutdownImmediately(); database.shutdownImmediately();
throw DbException.get(ErrorCode.DATABASE_IS_CLOSED); throw DbException.get(ErrorCode.DATABASE_IS_CLOSED);
} }
transaction = store.getTransactionStore().begin(); transaction = store.getTransactionStore().begin(this);
} }
startStatement = -1; startStatement = -1;
} }
...@@ -1768,6 +1752,62 @@ public class Session extends SessionWithState { ...@@ -1768,6 +1752,62 @@ public class Session extends SessionWithState {
tablesToAnalyze.add(table); tablesToAnalyze.add(table);
} }
@Override
public void onRollback(MVMap<Object, VersionedValue> map, Object key,
VersionedValue existingValue,
VersionedValue restoredValue) {
// Here we are relying on the fact that map which backs table's primary index
// has the same name as the table itself
MVTableEngine.Store store = database.getMvStore();
if(store != null) {
MVTable table = store.getTable(map.getName());
if (table != null) {
long recKey = ((ValueLong)key).getLong();
Row oldRow = getRowFromVersionedValue(table, recKey, existingValue);
Row newRow = getRowFromVersionedValue(table, recKey, restoredValue);
table.fireAfterRow(this, oldRow, newRow, true);
if (table.getContainsLargeObject()) {
if (oldRow != null) {
for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) {
Value v = oldRow.getValue(i);
if (v.isLinkedToTable()) {
removeAtCommit(v);
}
}
}
if (newRow != null) {
for (int i = 0, len = newRow.getColumnCount(); i < len; i++) {
Value v = newRow.getValue(i);
if (v.isLinkedToTable()) {
removeAtCommitStop(v);
}
}
}
}
}
}
}
private static Row getRowFromVersionedValue(MVTable table, long recKey,
VersionedValue versionedValue) {
Object value = versionedValue == null ? null : versionedValue.value;
if (value == null) {
return null;
}
Row result;
if(value instanceof Row) {
result = (Row) value;
assert result.getKey() == recKey : result.getKey() + " != " + recKey;
} else {
ValueArray array = (ValueArray) value;
result = table.createRow(array.getList(), 0);
result.setKey(recKey);
}
return result;
}
/** /**
* Represents a savepoint (a position in a transaction to where one can roll * Represents a savepoint (a position in a transaction to where one can roll
* back to). * back to).
......
...@@ -162,7 +162,7 @@ public class MVTableEngine implements TableEngine { ...@@ -162,7 +162,7 @@ public class MVTableEngine implements TableEngine {
this.transactionStore = new TransactionStore( this.transactionStore = new TransactionStore(
store, store,
new ValueDataType(db.getCompareMode(), db, null)); new ValueDataType(db.getCompareMode(), db, null));
transactionStore.init(); // transactionStore.init();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw convertIllegalStateException(e); throw convertIllegalStateException(e);
} }
...@@ -206,8 +206,8 @@ public class MVTableEngine implements TableEngine { ...@@ -206,8 +206,8 @@ public class MVTableEngine implements TableEngine {
return transactionStore; return transactionStore;
} }
public HashMap<String, MVTable> getTables() { public MVTable getTable(String tableName) {
return new HashMap<>(tableMap); return tableMap.get(tableName);
} }
/** /**
......
...@@ -81,13 +81,24 @@ public class Transaction { ...@@ -81,13 +81,24 @@ public class Transaction {
*/ */
final TransactionStore store; final TransactionStore store;
/**
* Listener for this transaction's rollback changes.
*/
final TransactionStore.RollbackListener listener;
/** /**
* The transaction id. * The transaction id.
* More appropriate name for this field would be "slotId"
*/ */
final int transactionId; final int transactionId;
/**
* This is really a transaction identity, because it's not re-used.
*/
public final long sequenceNum;
/* /*
* Transation state is an atomic composite field: * Transaction state is an atomic composite field:
* bit 45 : flag whether transaction had rollback(s) * bit 45 : flag whether transaction had rollback(s)
* bits 44-41 : status * bits 44-41 : status
* bits 40 : overflow control bit, 1 indicates overflow * bits 40 : overflow control bit, 1 indicates overflow
...@@ -99,12 +110,16 @@ public class Transaction { ...@@ -99,12 +110,16 @@ public class Transaction {
private String name; private String name;
Transaction(TransactionStore store, int transactionId, int status, boolean wasStored;
String name, long logId) {
Transaction(TransactionStore store, int transactionId, long sequenceNum, int status,
String name, long logId, TransactionStore.RollbackListener listener) {
this.store = store; this.store = store;
this.transactionId = transactionId; this.transactionId = transactionId;
this.sequenceNum = sequenceNum;
this.statusAndLogId = new AtomicLong(composeState(status, logId, false)); this.statusAndLogId = new AtomicLong(composeState(status, logId, false));
this.name = name; this.name = name;
this.listener = listener;
} }
public int getId() { public int getId() {
...@@ -168,6 +183,10 @@ public class Transaction { ...@@ -168,6 +183,10 @@ public class Transaction {
} }
} }
public boolean hasChanges() {
return hasChanges(statusAndLogId.get());
}
public void setName(String name) { public void setName(String name) {
checkNotClosed(); checkNotClosed();
this.name = name; this.name = name;
...@@ -289,10 +308,12 @@ public class Transaction { ...@@ -289,10 +308,12 @@ public class Transaction {
* Commit the transaction. Afterwards, this transaction is closed. * Commit the transaction. Afterwards, this transaction is closed.
*/ */
public void commit() { public void commit() {
assert store.openTransactions.get().get(transactionId);
long state = setStatus(STATUS_COMMITTING); long state = setStatus(STATUS_COMMITTING);
long logId = Transaction.getLogId(state); long logId = Transaction.getLogId(state);
int oldStatus = Transaction.getStatus(state); boolean hasChanges = hasChanges(state);
store.commit(this, logId, oldStatus);
store.commit(this, logId, hasChanges);
} }
/** /**
...@@ -330,7 +351,7 @@ public class Transaction { ...@@ -330,7 +351,7 @@ public class Transaction {
store.rollbackTo(this, logId, 0); store.rollbackTo(this, logId, 0);
} }
} finally { } finally {
store.endTransaction(this, STATUS_ROLLED_BACK); store.endTransaction(this, true);
} }
} }
...@@ -373,7 +394,7 @@ public class Transaction { ...@@ -373,7 +394,7 @@ public class Transaction {
@Override @Override
public String toString() { public String toString() {
long state = statusAndLogId.get(); long state = statusAndLogId.get();
return transactionId + " " + STATUS_NAMES[getStatus(state)] + " " + getLogId(state); return transactionId + "(" + sequenceNum + ") " + STATUS_NAMES[getStatus(state)] + " " + getLogId(state);
} }
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore.tx;
import java.util.BitSet;
/**
* Class VersionedBitSet extends standard BitSet to add a version field.
* This will allow bit set and version to be changed atomically.
*/
final class VersionedBitSet extends BitSet
{
private long version;
public VersionedBitSet() {}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public VersionedBitSet clone() {
return (VersionedBitSet)super.clone();
}
}
...@@ -24,7 +24,7 @@ public class VersionedValue { ...@@ -24,7 +24,7 @@ public class VersionedValue {
/** /**
* The value. * The value.
*/ */
final Object value; public final Object value;
VersionedValue(long operationId, Object value) { VersionedValue(long operationId, Object value) {
this.operationId = operationId; this.operationId = operationId;
......
...@@ -621,8 +621,12 @@ public abstract class Table extends SchemaObjectBase { ...@@ -621,8 +621,12 @@ public abstract class Table extends SchemaObjectBase {
} }
} }
public Row createRow(Value[] data, int memory) {
return database.createRow(data, memory);
}
public Row getTemplateRow() { public Row getTemplateRow() {
return database.createRow(new Value[columns.length], Row.MEMORY_CALCULATE); return createRow(new Value[columns.length], Row.MEMORY_CALCULATE);
} }
/** /**
......
...@@ -107,7 +107,7 @@ public class TestMVStoreTool extends TestBase { ...@@ -107,7 +107,7 @@ public class TestMVStoreTool extends TestBase {
assertEquals(size2, FileUtils.size(fileNameNew)); assertEquals(size2, FileUtils.size(fileNameNew));
MVStoreTool.compact(fileNameCompressed, true); MVStoreTool.compact(fileNameCompressed, true);
assertEquals(size3, FileUtils.size(fileNameCompressed)); assertEquals(size3, FileUtils.size(fileNameCompressed));
trace("Recompacted in " + (System.currentTimeMillis() - start) + " ms."); trace("Re-compacted in " + (System.currentTimeMillis() - start) + " ms.");
start = System.currentTimeMillis(); start = System.currentTimeMillis();
MVStore s1 = new MVStore.Builder(). MVStore s1 = new MVStore.Builder().
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论