提交 570437e8 authored 作者: Thomas Mueller's avatar Thomas Mueller

Improved performance for read operations.

上级 44027adc
...@@ -12,13 +12,15 @@ import java.util.Iterator; ...@@ -12,13 +12,15 @@ import java.util.Iterator;
* A cursor to iterate over elements in ascending order. * A cursor to iterate over elements in ascending order.
* *
* @param <K> the key type * @param <K> the key type
* @param <V> the value type
*/ */
public class Cursor<K> implements Iterator<K> { public class Cursor<K, V> implements Iterator<K> {
private final MVMap<K, ?> map; private final MVMap<K, ?> map;
private final K from; private final K from;
private CursorPos pos; private CursorPos pos;
private K current; private K current;
private V currentValue, lastValue;
private final Page root; private final Page root;
private boolean initialized; private boolean initialized;
...@@ -42,10 +44,20 @@ public class Cursor<K> implements Iterator<K> { ...@@ -42,10 +44,20 @@ public class Cursor<K> implements Iterator<K> {
public K next() { public K next() {
hasNext(); hasNext();
K c = current; K c = current;
lastValue = currentValue;
fetchNext(); fetchNext();
return c; return c;
} }
/**
* Get the last read value if there was one.
*
* @return the value or null
*/
public V getValue() {
return lastValue;
}
/** /**
* Skip over that many entries. This method is relatively fast (for this map * Skip over that many entries. This method is relatively fast (for this map
* implementation) even if many entries need to be skipped. * implementation) even if many entries need to be skipped.
...@@ -110,7 +122,9 @@ public class Cursor<K> implements Iterator<K> { ...@@ -110,7 +122,9 @@ public class Cursor<K> implements Iterator<K> {
private void fetchNext() { private void fetchNext() {
while (pos != null) { while (pos != null) {
if (pos.index < pos.page.getKeyCount()) { if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++); int index = pos.index++;
current = (K) pos.page.getKey(index);
currentValue = (V) pos.page.getValue(index);
return; return;
} }
pos = pos.parent; pos = pos.parent;
......
...@@ -17,6 +17,8 @@ import java.util.ArrayList; ...@@ -17,6 +17,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.util.New; import org.h2.util.New;
...@@ -853,4 +855,38 @@ public class DataUtils { ...@@ -853,4 +855,38 @@ public class DataUtils {
} }
} }
/**
* An entry of a map.
*
* @param <K> the key type
* @param <V> the value type
*/
public static class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw DataUtils.newUnsupportedOperationException(
"Updating the value is not supported");
}
}
} }
...@@ -745,20 +745,30 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -745,20 +745,30 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
/** /**
* Iterate over all keys. * Iterate over a number of keys.
* *
* @param from the first key to return * @param from the first key to return
* @return the iterator * @return the iterator
*/ */
public Cursor<K> keyIterator(K from) { public Iterator<K> keyIterator(K from) {
return new Cursor<K>(this, root, from); return new Cursor<K, V>(this, root, from);
}
/**
* Get a cursor to iterate over a number of keys and values.
*
* @param from the first key to return
* @return the cursor
*/
public Cursor<K, V> cursor(K from) {
return new Cursor<K, V>(this, root, from);
} }
@Override @Override
public Set<Map.Entry<K, V>> entrySet() { public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>(); HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) { for (Cursor<K, V> cursor = cursor(null); cursor.hasNext();) {
map.put(k, get(k)); map.put(cursor.next(), cursor.getValue());
} }
return map.entrySet(); return map.entrySet();
} }
...@@ -771,7 +781,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -771,7 +781,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override @Override
public Iterator<K> iterator() { public Iterator<K> iterator() {
return new Cursor<K>(map, root, null); return new Cursor<K, V>(map, root, null);
} }
@Override @Override
......
...@@ -17,6 +17,7 @@ import org.h2.result.SortOrder; ...@@ -17,6 +17,7 @@ import org.h2.result.SortOrder;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.ValueLong;
/** /**
* An index that delegates indexing to another index. * An index that delegates indexing to another index.
...@@ -53,10 +54,10 @@ public class MVDelegateIndex extends BaseIndex { ...@@ -53,10 +54,10 @@ public class MVDelegateIndex extends BaseIndex {
@Override @Override
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
long min = mainIndex.getKey(first, Long.MIN_VALUE, Long.MIN_VALUE); ValueLong min = mainIndex.getKey(first, MVPrimaryIndex.MIN, MVPrimaryIndex.MIN);
// ifNull is MIN_VALUE as well, because the column is never NULL // ifNull is MIN_VALUE as well, because the column is never NULL
// so avoid returning all rows (returning one row is OK) // so avoid returning all rows (returning one row is OK)
long max = mainIndex.getKey(last, Long.MAX_VALUE, Long.MIN_VALUE); ValueLong max = mainIndex.getKey(last, MVPrimaryIndex.MAX, MVPrimaryIndex.MIN);
return mainIndex.find(session, min, max); return mainIndex.find(session, min, max);
} }
......
...@@ -9,6 +9,8 @@ package org.h2.mvstore.db; ...@@ -9,6 +9,8 @@ package org.h2.mvstore.db;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -18,6 +20,7 @@ import org.h2.index.BaseIndex; ...@@ -18,6 +20,7 @@ import org.h2.index.BaseIndex;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.db.TransactionStore.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore.TransactionMap; import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.result.Row; import org.h2.result.Row;
...@@ -36,6 +39,10 @@ import org.h2.value.ValueNull; ...@@ -36,6 +39,10 @@ import org.h2.value.ValueNull;
*/ */
public class MVPrimaryIndex extends BaseIndex { public class MVPrimaryIndex extends BaseIndex {
static final ValueLong MIN = ValueLong.get(Long.MIN_VALUE);
static final ValueLong MAX = ValueLong.get(Long.MAX_VALUE);
static final ValueLong ZERO = ValueLong.get(0);
private final MVTable mvTable; private final MVTable mvTable;
private final String mapName; private final String mapName;
private TransactionMap<Value, Value> dataMap; private TransactionMap<Value, Value> dataMap;
...@@ -152,30 +159,30 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -152,30 +159,30 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
long min, max; ValueLong min, max;
if (first == null || mainIndexColumn < 0) { if (first == null || mainIndexColumn < 0) {
min = Long.MIN_VALUE; min = MIN;
} else { } else {
Value v = first.getValue(mainIndexColumn); ValueLong v = (ValueLong) first.getValue(mainIndexColumn);
if (v == null) { if (v == null) {
min = 0; min = ZERO;
} else { } else {
min = v.getLong(); min = v;
} }
} }
if (last == null || mainIndexColumn < 0) { if (last == null || mainIndexColumn < 0) {
max = Long.MAX_VALUE; max = MAX;
} else { } else {
Value v = last.getValue(mainIndexColumn); ValueLong v = (ValueLong) last.getValue(mainIndexColumn);
if (v == null) { if (v == null) {
max = Long.MAX_VALUE; max = MAX;
} else { } else {
max = v.getLong(); max = v;
} }
} }
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator( return new MVStoreCursor(map.entryIterator(
ValueLong.get(min)), max); min), max);
} }
@Override @Override
...@@ -196,7 +203,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -196,7 +203,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
long cost = 10 * (dataMap.map.sizeAsLong() + Constants.COST_ROW_OFFSET); long cost = 10 * (dataMap.sizeAsLongEstimated() + Constants.COST_ROW_OFFSET);
return cost; return cost;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
...@@ -236,15 +243,15 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -236,15 +243,15 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public Cursor findFirstOrLast(Session session, boolean first) { public Cursor findFirstOrLast(Session session, boolean first) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
Value v = first ? map.firstKey() : map.lastKey(); ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) { if (v == null) {
return new MVStoreCursor(session, Collections.<Value>emptyList().iterator(), 0); return new MVStoreCursor(Collections.<Entry<Value, Value>>emptyList().iterator(), null);
} }
long key = v.getLong(); Value value = map.get(v);
MVStoreCursor cursor = new MVStoreCursor(session, Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
Arrays.asList((Value) ValueLong.get(key)).iterator(), key); @SuppressWarnings("unchecked")
cursor.next(); List<Entry<Value, Value>> list = Arrays.asList(e);
return cursor; return new MVStoreCursor(list.iterator(), v);
} }
@Override @Override
...@@ -261,7 +268,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -261,7 +268,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -290,7 +297,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -290,7 +297,7 @@ public class MVPrimaryIndex extends BaseIndex {
* @param ifNull the value to use if the column is NULL * @param ifNull the value to use if the column is NULL
* @return the key * @return the key
*/ */
long getKey(SearchRow row, long ifEmpty, long ifNull) { ValueLong getKey(SearchRow row, ValueLong ifEmpty, ValueLong ifNull) {
if (row == null) { if (row == null) {
return ifEmpty; return ifEmpty;
} }
...@@ -300,7 +307,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -300,7 +307,7 @@ public class MVPrimaryIndex extends BaseIndex {
} else if (v == ValueNull.INSTANCE) { } else if (v == ValueNull.INSTANCE) {
return ifNull; return ifNull;
} }
return v.getLong(); return (ValueLong) v.convertTo(Value.LONG);
} }
/** /**
...@@ -311,9 +318,9 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -311,9 +318,9 @@ public class MVPrimaryIndex extends BaseIndex {
* @param last the key of the last row * @param last the key of the last row
* @return the cursor * @return the cursor
*/ */
Cursor find(Session session, long first, long last) { Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.keyIterator(ValueLong.get(first)), last); return new MVStoreCursor(map.entryIterator(first), last);
} }
@Override @Override
...@@ -340,14 +347,12 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -340,14 +347,12 @@ public class MVPrimaryIndex extends BaseIndex {
*/ */
class MVStoreCursor implements Cursor { class MVStoreCursor implements Cursor {
private final Session session; private final Iterator<Entry<Value, Value>> it;
private final Iterator<Value> it; private final ValueLong last;
private final long last; private Entry<Value, Value> current;
private ValueLong current;
private Row row; private Row row;
public MVStoreCursor(Session session, Iterator<Value> it, long last) { public MVStoreCursor(Iterator<Entry<Value, Value>> it, ValueLong last) {
this.session = session;
this.it = it; this.it = it;
this.last = last; this.last = last;
} }
...@@ -356,7 +361,9 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -356,7 +361,9 @@ public class MVPrimaryIndex extends BaseIndex {
public Row get() { public Row get() {
if (row == null) { if (row == null) {
if (current != null) { if (current != null) {
row = getRow(session, current.getLong()); ValueArray array = (ValueArray) current.getValue();
row = new Row(array.getList(), 0);
row.setKey(current.getKey().getLong());
} }
} }
return row; return row;
...@@ -369,8 +376,8 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -369,8 +376,8 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public boolean next() { public boolean next() {
current = (ValueLong) it.next(); current = it.next();
if (current != null && current.getLong() > last) { if (current != null && current.getKey().getLong() > last.getLong()) {
current = null; current = null;
} }
row = null; row = null;
......
...@@ -66,7 +66,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -66,7 +66,7 @@ public class MVSecondaryIndex extends BaseIndex {
ValueDataType valueType = new ValueDataType(null, null, null); ValueDataType valueType = new ValueDataType(null, null, null);
dataMap = mvTable.getTransaction(null).openMap( dataMap = mvTable.getTransaction(null).openMap(
mapName, keyType, valueType); mapName, keyType, valueType);
if (keyType != dataMap.map.getKeyType()) { if (keyType != dataMap.getKeyType()) {
throw DbException.throwInternalError("Incompatible key type"); throw DbException.throwInternalError("Incompatible key type");
} }
} }
...@@ -102,6 +102,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -102,6 +102,7 @@ public class MVSecondaryIndex extends BaseIndex {
} }
if (indexType.isUnique()) { if (indexType.isUnique()) {
// check if there is another (uncommitted) entry // check if there is another (uncommitted) entry
// TODO use entry iterator
Iterator<Value> it = map.keyIterator(unique, true); Iterator<Value> it = map.keyIterator(unique, true);
while (it.hasNext()) { while (it.hasNext()) {
ValueArray k = (ValueArray) it.next(); ValueArray k = (ValueArray) it.next();
...@@ -195,7 +196,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -195,7 +196,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
return 10 * getCostRangeIndex(masks, dataMap.map.sizeAsLong(), filter, sortOrder); return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongEstimated(), filter, sortOrder);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -244,7 +245,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -244,7 +245,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
try { try {
return dataMap.map.sizeAsLong() == 0; return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -259,7 +260,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -259,7 +260,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
...@@ -282,7 +282,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex { ...@@ -282,7 +282,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
try { try {
return dataMap.map.sizeAsLong() == 0; return dataMap.sizeAsLongEstimated() == 0;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -297,7 +297,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex { ...@@ -297,7 +297,7 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.sizeAsLong(); return dataMap.sizeAsLongEstimated();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
...@@ -12,10 +12,12 @@ import java.util.HashMap; ...@@ -12,10 +12,12 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import org.h2.mvstore.Cursor; import org.h2.mvstore.Cursor;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVMapConcurrent;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
...@@ -27,6 +29,11 @@ import org.h2.util.New; ...@@ -27,6 +29,11 @@ import org.h2.util.New;
*/ */
public class TransactionStore { public class TransactionStore {
/**
* Whether the concurrent maps should be used.
*/
private static final boolean CONCURRENT = false;
/** /**
* The store. * The store.
*/ */
...@@ -241,7 +248,7 @@ public class TransactionStore { ...@@ -241,7 +248,7 @@ public class TransactionStore {
} }
} }
<K, V> void removeMap(TransactionMap<K, V> map) { synchronized <K, V> void removeMap(TransactionMap<K, V> map) {
maps.remove(map.mapId); maps.remove(map.mapId);
store.removeMap(map.map); store.removeMap(map.map);
} }
...@@ -256,6 +263,7 @@ public class TransactionStore { ...@@ -256,6 +263,7 @@ public class TransactionStore {
if (store.isClosed()) { if (store.isClosed()) {
return; return;
} }
// TODO could synchronize on blocks
synchronized (undoLog) { synchronized (undoLog) {
t.setStatus(Transaction.STATUS_COMMITTING); t.setStatus(Transaction.STATUS_COMMITTING);
for (long logId = 0; logId < maxLogId; logId++) { for (long logId = 0; logId < maxLogId; logId++) {
...@@ -294,6 +302,32 @@ public class TransactionStore { ...@@ -294,6 +302,32 @@ public class TransactionStore {
endTransaction(t); endTransaction(t);
} }
synchronized <K> MVMap<K, VersionedValue> openMap(String name, DataType keyType, DataType valueType) {
if (keyType == null) {
keyType = new ObjectDataType();
}
if (valueType == null) {
valueType = new ObjectDataType();
}
VersionedValueType vt = new VersionedValueType(valueType);
MVMap<K, VersionedValue> map;
if (CONCURRENT) {
MVMapConcurrent.Builder<K, VersionedValue> builder =
new MVMapConcurrent.Builder<K, VersionedValue>().
keyType(keyType).valueType(vt);
map = store.openMap(name, builder);
} else {
MVMap.Builder<K, VersionedValue> builder =
new MVMap.Builder<K, VersionedValue>().
keyType(keyType).valueType(vt);
map = store.openMap(name, builder);
}
@SuppressWarnings("unchecked")
MVMap<Object, VersionedValue> m = (MVMap<Object, VersionedValue>) map;
maps.put(map.getId(), m);
return map;
}
synchronized MVMap<Object, VersionedValue> openMap(int mapId) { synchronized MVMap<Object, VersionedValue> openMap(int mapId) {
MVMap<Object, VersionedValue> map = maps.get(mapId); MVMap<Object, VersionedValue> map = maps.get(mapId);
if (map != null) { if (map != null) {
...@@ -316,17 +350,6 @@ public class TransactionStore { ...@@ -316,17 +350,6 @@ public class TransactionStore {
return map; return map;
} }
/**
* Check whether the given transaction id is still open and contains log
* entries.
*
* @param transactionId the transaction id
* @return true if it is open
*/
boolean isTransactionOpen(int transactionId) {
return transactionId != 0;
}
/** /**
* End this transaction * End this transaction
* *
...@@ -362,6 +385,7 @@ public class TransactionStore { ...@@ -362,6 +385,7 @@ public class TransactionStore {
* @param toLogId the log id to roll back to * @param toLogId the log id to roll back to
*/ */
void rollbackTo(Transaction t, long maxLogId, long toLogId) { void rollbackTo(Transaction t, long maxLogId, long toLogId) {
// TODO could synchronize on blocks
synchronized (undoLog) { synchronized (undoLog) {
for (long logId = maxLogId - 1; logId >= toLogId; logId--) { for (long logId = maxLogId - 1; logId >= toLogId; logId--) {
Long undoKey = getOperationId(t.getId(), logId); Long undoKey = getOperationId(t.getId(), logId);
...@@ -619,16 +643,7 @@ public class TransactionStore { ...@@ -619,16 +643,7 @@ public class TransactionStore {
*/ */
public <K, V> TransactionMap<K, V> openMap(String name, DataType keyType, DataType valueType) { public <K, V> TransactionMap<K, V> openMap(String name, DataType keyType, DataType valueType) {
checkNotClosed(); checkNotClosed();
if (keyType == null) { MVMap<K, VersionedValue> map = store.openMap(name, keyType, valueType);
keyType = new ObjectDataType();
}
if (valueType == null) {
valueType = new ObjectDataType();
}
VersionedValueType vt = new VersionedValueType(valueType);
MVMap.Builder<K, VersionedValue> builder = new MVMap.Builder<K, VersionedValue>()
.keyType(keyType).valueType(vt);
MVMap<K, VersionedValue> map = store.store.openMap(name, builder);
int mapId = map.getId(); int mapId = map.getId();
return new TransactionMap<K, V>(this, map, mapId); return new TransactionMap<K, V>(this, map, mapId);
} }
...@@ -733,28 +748,28 @@ public class TransactionStore { ...@@ -733,28 +748,28 @@ public class TransactionStore {
*/ */
public static class TransactionMap<K, V> { public static class TransactionMap<K, V> {
/**
* The map used for writing (the latest version).
* <p>
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value }
*/
final MVMap<K, VersionedValue> map;
/** /**
* The map id. * The map id.
*/ */
final int mapId; final int mapId;
private Transaction transaction;
/** /**
* If a record was read that was updated by this transaction, and the * If a record was read that was updated by this transaction, and the
* update occurred before this log id, the older version is read. This * update occurred before this log id, the older version is read. This
* is so that changes are not immediately visible, to support statement * is so that changes are not immediately visible, to support statement
* processing (for example "update test set id = id + 1"). * processing (for example "update test set id = id + 1").
*/ */
private long readLogId = Long.MAX_VALUE; long readLogId = Long.MAX_VALUE;
/**
* The map used for writing (the latest version).
* <p>
* Key: key the key of the data.
* Value: { transactionId, oldVersion, value }
*/
final MVMap<K, VersionedValue> map;
private Transaction transaction;
TransactionMap(Transaction transaction, MVMap<K, VersionedValue> map, int mapId) { TransactionMap(Transaction transaction, MVMap<K, VersionedValue> map, int mapId) {
this.transaction = transaction; this.transaction = transaction;
...@@ -785,6 +800,15 @@ public class TransactionStore { ...@@ -785,6 +800,15 @@ public class TransactionStore {
return m; return m;
} }
/**
* Get the size of the raw map.
*
* @return the size
*/
public long sizeAsLongEstimated() {
return map.sizeAsLong();
}
/** /**
* Get the size of the map as seen by this transaction. * Get the size of the map as seen by this transaction.
* *
...@@ -793,10 +817,11 @@ public class TransactionStore { ...@@ -793,10 +817,11 @@ public class TransactionStore {
public long sizeAsLong() { public long sizeAsLong() {
// TODO this method is very slow // TODO this method is very slow
long size = 0; long size = 0;
Cursor<K> cursor = map.keyIterator(null); Cursor<K, VersionedValue> cursor = map.cursor(null);
while (cursor.hasNext()) { while (cursor.hasNext()) {
K key = cursor.next(); K key = cursor.next();
if (get(key) != null) { VersionedValue data = cursor.getValue();
if (getValue(key, readLogId, data) != null) {
size++; size++;
} }
} }
...@@ -942,26 +967,26 @@ public class TransactionStore { ...@@ -942,26 +967,26 @@ public class TransactionStore {
} }
return true; return true;
} }
int tx = getTransactionId(current.operationId); long id = current.operationId;
if (tx == transaction.transactionId) { if (id == 0) {
// added or updated by this transaction // committed
transaction.log(mapId, key, current); transaction.log(mapId, key, current);
// the transaction is committed:
// overwrite the value
if (!map.replace(key, current, newValue)) { if (!map.replace(key, current, newValue)) {
// strange, somebody overwrite the value // somebody else was faster
// even thought the change was not committed
transaction.logUndo(); transaction.logUndo();
return false; return false;
} }
return true; return true;
} }
// added or updated by another transaction int tx = getTransactionId(current.operationId);
boolean open = transaction.store.isTransactionOpen(tx); if (tx == transaction.transactionId) {
if (!open) { // added or updated by this transaction
transaction.log(mapId, key, current); transaction.log(mapId, key, current);
// the transaction is committed:
// overwrite the value
if (!map.replace(key, current, newValue)) { if (!map.replace(key, current, newValue)) {
// somebody else was faster // strange, somebody overwrite the value
// even thought the change was not committed
transaction.logUndo(); transaction.logUndo();
return false; return false;
} }
...@@ -1033,6 +1058,10 @@ public class TransactionStore { ...@@ -1033,6 +1058,10 @@ public class TransactionStore {
private VersionedValue getValue(K key, long maxLog) { private VersionedValue getValue(K key, long maxLog) {
VersionedValue data = map.get(key); VersionedValue data = map.get(key);
return getValue(key, maxLog, data);
}
VersionedValue getValue(K key, long maxLog, VersionedValue data) {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
int tx; int tx;
if (data == null) { if (data == null) {
...@@ -1040,25 +1069,21 @@ public class TransactionStore { ...@@ -1040,25 +1069,21 @@ public class TransactionStore {
return null; return null;
} }
long id = data.operationId; long id = data.operationId;
if (id == 0) {
// it is committed
return data;
}
tx = getTransactionId(id); tx = getTransactionId(id);
long logId = getLogId(id);
if (tx == transaction.transactionId) { if (tx == transaction.transactionId) {
// added by this transaction // added by this transaction
if (logId < maxLog) { if (getLogId(id) < maxLog) {
return data; return data;
} }
} }
// added, updated, or removed by another transaction
boolean open = transaction.store.isTransactionOpen(tx);
if (!open) {
// it is committed
return data;
}
// get the value before the uncommitted transaction // get the value before the uncommitted transaction
Long x = getOperationId(tx, logId);
Object[] d; Object[] d;
synchronized (transaction.store.undoLog) { synchronized (transaction.store.undoLog) {
d = transaction.store.undoLog.get(x); d = transaction.store.undoLog.get(id);
} }
if (d == null) { if (d == null) {
// this entry was committed or rolled back // this entry was committed or rolled back
...@@ -1125,7 +1150,7 @@ public class TransactionStore { ...@@ -1125,7 +1150,7 @@ public class TransactionStore {
* @return the result * @return the result
*/ */
public K getLatestCeilingKey(K key) { public K getLatestCeilingKey(K key) {
Cursor<K> cursor = map.keyIterator(key); Iterator<K> cursor = map.keyIterator(key);
while (cursor.hasNext()) { while (cursor.hasNext()) {
key = cursor.next(); key = cursor.next();
if (get(key, Long.MAX_VALUE) != null) { if (get(key, Long.MAX_VALUE) != null) {
...@@ -1143,7 +1168,7 @@ public class TransactionStore { ...@@ -1143,7 +1168,7 @@ public class TransactionStore {
*/ */
public K ceilingKey(K key) { public K ceilingKey(K key) {
// TODO this method is slow // TODO this method is slow
Cursor<K> cursor = map.keyIterator(key); Iterator<K> cursor = map.keyIterator(key);
while (cursor.hasNext()) { while (cursor.hasNext()) {
key = cursor.next(); key = cursor.next();
if (get(key) != null) { if (get(key) != null) {
...@@ -1195,10 +1220,105 @@ public class TransactionStore { ...@@ -1195,10 +1220,105 @@ public class TransactionStore {
* @return the iterator * @return the iterator
*/ */
public Iterator<K> keyIterator(K from, boolean includeUncommitted) { public Iterator<K> keyIterator(K from, boolean includeUncommitted) {
Cursor<K> it = map.keyIterator(from); Iterator<K> it = map.keyIterator(from);
return wrapIterator(it, includeUncommitted); return wrapIterator(it, includeUncommitted);
} }
public Iterator<Entry<K, V>> entryIterator(K from) {
final Cursor<K, VersionedValue> cursor = map.cursor(from);
return new Iterator<Entry<K, V>>() {
private Entry<K, V> current;
{
fetchNext();
}
private void fetchNext() {
while (cursor.hasNext()) {
final K key = cursor.next();
VersionedValue data = cursor.getValue();
data = getValue(key, readLogId, data);
if (data != null && data.value != null) {
@SuppressWarnings("unchecked")
final V value = (V) data.value;
current = new DataUtils.MapEntry<K, V>(key, value);
return;
}
}
current = null;
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Entry<K, V> next() {
Entry<K, V> result = current;
fetchNext();
return result;
}
@Override
public void remove() {
throw DataUtils.newUnsupportedOperationException(
"Removing is not supported");
}
};
}
/**
* Iterate over keys.
*
* @param cursor the cursor to wrap
* @param includeUncommitted whether uncommitted entries should be included
* @return the iterator
*/
public Iterator<K> wrapCursor(final Cursor<K, VersionedValue> cursor, final boolean includeUncommitted) {
return new Iterator<K>() {
private K current;
{
fetchNext();
}
private void fetchNext() {
while (cursor.hasNext()) {
current = cursor.next();
if (includeUncommitted) {
return;
}
VersionedValue data = cursor.getValue();
data = getValue(current, readLogId, data);
if (data != null && data.value != null) {
return;
}
}
current = null;
}
@Override
public boolean hasNext() {
return current != null;
}
@Override
public K next() {
K result = current;
fetchNext();
return result;
}
@Override
public void remove() {
throw DataUtils.newUnsupportedOperationException(
"Removing is not supported");
}
};
}
/** /**
* Iterate over keys. * Iterate over keys.
* *
...@@ -1207,6 +1327,7 @@ public class TransactionStore { ...@@ -1207,6 +1327,7 @@ public class TransactionStore {
* @return the iterator * @return the iterator
*/ */
public Iterator<K> wrapIterator(final Iterator<K> iterator, final boolean includeUncommitted) { public Iterator<K> wrapIterator(final Iterator<K> iterator, final boolean includeUncommitted) {
// TODO duplicate code for wrapCursor and wrapIterator
return new Iterator<K>() { return new Iterator<K>() {
private K current; private K current;
...@@ -1251,6 +1372,10 @@ public class TransactionStore { ...@@ -1251,6 +1372,10 @@ public class TransactionStore {
return transaction; return transaction;
} }
public DataType getKeyType() {
return map.getKeyType();
}
} }
/** /**
......
/*
* Copyright 2004-2013 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.store;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.h2.mvstore.Chunk;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.Page;
import org.h2.mvstore.db.TransactionStore;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.util.Profiler;
import org.h2.value.ValueLong;
import org.h2.value.ValueString;
/**
* Tests performance and helps analyze bottlenecks.
*/
public class TestBenchmark extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
test(true);
test(false);
test(true);
test(false);
test(true);
test(false);
}
private void test(boolean mvStore) throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
String url = "mvstore";
if (mvStore) {
url += ";MV_STORE=TRUE;LOG=0";
}
// 2033 mvstore
// 2313 (2075?) default
url = getURL(url, true);
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id bigint primary key, name varchar)");
conn.setAutoCommit(false);
PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)");
String data = "Hello World";
int rowCount = 100000;
int readCount = 20* rowCount;
for (int i = 0; i < rowCount; i++) {
prep.setInt(1, i);
prep.setString(2, data);
prep.execute();
if (i % 100 == 0) {
conn.commit();
}
}
long start = System.currentTimeMillis();
// Profiler prof = new Profiler();
// prof.sumClasses=true;
// prof.startCollecting();
;
prep = conn.prepareStatement("select * from test where id = ?");
for (int i = 0; i < readCount; i++) {
prep.setInt(1, i % rowCount);
prep.executeQuery();
}
//System.out.println("Transactionstore.counter " + ValueLong.counter);
//System.out.println("count " + Page.writeCount + " avgLen " + (1.0*Page.writeLength/Page.writeCount) + " avgSize " + (1.0*Page.writeSize/Page.writeCount));
//System.out.println(prof.getTop(5));
//System.out.println("ountUnc:" + counterUnc);
//System.out.println("ount:" + counter);
System.out.println((System.currentTimeMillis() - start) + " " + (mvStore ? "mvstore" : "default"));
conn.close();
// MVStore s = new MVStore.Builder().fileName(getBaseDir() + "/mvstore.mv.db").open();
// int count = 0;
// long length = 0;
// for(String k : s.getMetaMap().keyList()) {
// if (k.startsWith("chunk.")) {
// String x = s.getMetaMap().get(k);
// Chunk c = Chunk.fromString(x);
// if (c.length < Integer.MAX_VALUE) {
// count++;
// length += c.length;
// }
// }
// }
// if (count > 0) {
// System.out.println("chunks: " + count + " average length: " + (length / count));
// }
// s.close();
}
}
...@@ -706,7 +706,7 @@ public class TestMVStore extends TestBase { ...@@ -706,7 +706,7 @@ public class TestMVStore extends TestBase {
map.put(i, 10 * i); map.put(i, 10 * i);
} }
Cursor<Integer> c = map.keyIterator(50); Cursor<Integer, Integer> c = map.cursor(50);
// skip must reset the root of the cursor // skip must reset the root of the cursor
c.skip(10); c.skip(10);
for (int i = 70; i < 100; i += 2) { for (int i = 70; i < 100; i += 2) {
...@@ -732,7 +732,7 @@ public class TestMVStore extends TestBase { ...@@ -732,7 +732,7 @@ public class TestMVStore extends TestBase {
} }
} }
// skip // skip
c = map.keyIterator(0); c = map.cursor(0);
assertTrue(c.hasNext()); assertTrue(c.hasNext());
assertEquals(0, c.next().intValue()); assertEquals(0, c.next().intValue());
c.skip(0); c.skip(0);
...@@ -742,11 +742,11 @@ public class TestMVStore extends TestBase { ...@@ -742,11 +742,11 @@ public class TestMVStore extends TestBase {
c.skip(20); c.skip(20);
assertEquals(48, c.next().intValue()); assertEquals(48, c.next().intValue());
c = map.keyIterator(0); c = map.cursor(0);
c.skip(20); c.skip(20);
assertEquals(40, c.next().intValue()); assertEquals(40, c.next().intValue());
c = map.keyIterator(0); c = map.cursor(0);
assertEquals(0, c.next().intValue()); assertEquals(0, c.next().intValue());
assertEquals(12, map.keyList().indexOf(24)); assertEquals(12, map.keyList().indexOf(24));
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论