Unverified 提交 19767b0d authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #1120 from grandinj/1097_slow_querygroup

#1097 reduce memory overhead of the group by data structures
...@@ -5,6 +5,13 @@ ...@@ -5,6 +5,13 @@
*/ */
package org.h2.command.dml; package org.h2.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
...@@ -12,23 +19,40 @@ import org.h2.engine.Constants; ...@@ -12,23 +19,40 @@ import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.expression.*; import org.h2.expression.Alias;
import org.h2.expression.Comparison;
import org.h2.expression.ConditionAndOr;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.*; import org.h2.result.LazyResult;
import org.h2.table.*; import org.h2.result.LocalResult;
import org.h2.util.*; import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn;
import org.h2.table.JoinBatch;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.ColumnNamer;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.CompareMode;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
/** /**
* This class represents a simple SELECT statement. * This class represents a simple SELECT statement.
* *
...@@ -81,9 +105,21 @@ public class Select extends Query { ...@@ -81,9 +105,21 @@ public class Select extends Query {
boolean[] groupByExpression; boolean[] groupByExpression;
/** /**
* The current group-by values. * The array of current group-by expression data e.g. AggregateData.
*/ */
HashMap<Expression, Object> currentGroup; Object[] currentGroupByExprData;
/**
* Maps an expression object to an index, to use in accessing the Object[] pointed to by groupByData.
*/
private final HashMap<Expression,Integer> exprToIndexInGroupByData = new HashMap<>();
/**
* Map of group-by key to group-by expression data e.g. AggregateData
*/
private HashMap<Value, Object[]> groupByData;
/**
* Key into groupByData that produces currentGroupByExprData. Not used in lazy mode.
*/
private ValueArray currentGroupsKey;
private int havingIndex; private int havingIndex;
private boolean isGroupQuery, isGroupSortedQuery; private boolean isGroupQuery, isGroupSortedQuery;
...@@ -151,8 +187,45 @@ public class Select extends Query { ...@@ -151,8 +187,45 @@ public class Select extends Query {
return group; return group;
} }
public HashMap<Expression, Object> getCurrentGroup() { /**
return currentGroup; * Is there currently a group-by active
*/
public boolean isCurrentGroup() {
return currentGroupByExprData != null;
}
/**
* Get the group-by data for the current group and the passed in expression.
*/
public Object getCurrentGroupExprData(Expression expr) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index == null) {
return null;
}
return currentGroupByExprData[index];
}
/**
* Set the group-by data for the current group and the passed in expression.
*/
public void setCurrentGroupExprData(Expression expr, Object obj) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index != null) {
assert currentGroupByExprData[index] == null;
currentGroupByExprData[index] = obj;
return;
}
index = exprToIndexInGroupByData.size();
exprToIndexInGroupByData.put(expr, index);
if (index >= currentGroupByExprData.length) {
currentGroupByExprData = Arrays.copyOf(currentGroupByExprData, currentGroupByExprData.length * 2);
// this can be null in lazy mode
if (currentGroupsKey != null) {
// since we changed the size of the array, update the object in the groups map
groupByData.put(currentGroupsKey, currentGroupByExprData);
}
}
currentGroupByExprData[index] = obj;
} }
public int getCurrentGroupRowId() { public int getCurrentGroupRowId() {
...@@ -313,20 +386,21 @@ public class Select extends Query { ...@@ -313,20 +386,21 @@ public class Select extends Query {
} }
private void queryGroup(int columnCount, LocalResult result) { private void queryGroup(int columnCount, LocalResult result) {
ValueHashMap<HashMap<Expression, Object>> groups = groupByData = new HashMap<>();
ValueHashMap.newInstance(); currentGroupByExprData = null;
currentGroupsKey = null;
exprToIndexInGroupByData.clear();
try {
int rowNumber = 0; int rowNumber = 0;
setCurrentRowNumber(0); setCurrentRowNumber(0);
currentGroup = null;
ValueArray defaultGroup = ValueArray.get(new Value[0]); ValueArray defaultGroup = ValueArray.get(new Value[0]);
int sampleSize = getSampleSizeValue(session); int sampleSize = getSampleSizeValue(session);
while (topTableFilter.next()) { while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1); setCurrentRowNumber(rowNumber + 1);
if (isConditionMet()) { if (isConditionMet()) {
Value key;
rowNumber++; rowNumber++;
if (groupIndex == null) { if (groupIndex == null) {
key = defaultGroup; currentGroupsKey = defaultGroup;
} else { } else {
Value[] keyValues = new Value[groupIndex.length]; Value[] keyValues = new Value[groupIndex.length];
// update group // update group
...@@ -335,14 +409,14 @@ public class Select extends Query { ...@@ -335,14 +409,14 @@ public class Select extends Query {
Expression expr = expressions.get(idx); Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session); keyValues[i] = expr.getValue(session);
} }
key = ValueArray.get(keyValues); currentGroupsKey = ValueArray.get(keyValues);
} }
HashMap<Expression, Object> values = groups.get(key); Object[] values = groupByData.get(currentGroupsKey);
if (values == null) { if (values == null) {
values = new HashMap<>(); values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
groups.put(key, values); groupByData.put(currentGroupsKey, values);
} }
currentGroup = values; currentGroupByExprData = values;
currentGroupRowId++; currentGroupRowId++;
for (int i = 0; i < columnCount; i++) { for (int i = 0; i < columnCount; i++) {
if (groupByExpression == null || !groupByExpression[i]) { if (groupByExpression == null || !groupByExpression[i]) {
...@@ -355,14 +429,14 @@ public class Select extends Query { ...@@ -355,14 +429,14 @@ public class Select extends Query {
} }
} }
} }
if (groupIndex == null && groups.size() == 0) { if (groupIndex == null && groupByData.size() == 0) {
groups.put(defaultGroup, new HashMap<Expression, Object>()); groupByData.put(defaultGroup,
new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]);
} }
ArrayList<Value> keys = groups.keys(); for (Map.Entry<Value, Object[]> entry : groupByData.entrySet()) {
for (Value v : keys) { currentGroupsKey = (ValueArray) entry.getKey();
ValueArray key = (ValueArray) v; currentGroupByExprData = entry.getValue();
currentGroup = groups.get(key); Value[] keyValues = currentGroupsKey.getList();
Value[] keyValues = key.getList();
Value[] row = new Value[columnCount]; Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) { for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j]; row[groupIndex[j]] = keyValues[j];
...@@ -380,6 +454,12 @@ public class Select extends Query { ...@@ -380,6 +454,12 @@ public class Select extends Query {
row = keepOnlyDistinct(row, columnCount); row = keepOnlyDistinct(row, columnCount);
result.addRow(row); result.addRow(row);
} }
} finally {
groupByData = null;
currentGroupsKey = null;
currentGroupByExprData = null;
exprToIndexInGroupByData.clear();
}
} }
/** /**
...@@ -1475,13 +1555,15 @@ public class Select extends Query { ...@@ -1475,13 +1555,15 @@ public class Select extends Query {
LazyResultGroupSorted(Expression[] expressions, int columnCount) { LazyResultGroupSorted(Expression[] expressions, int columnCount) {
super(expressions, columnCount); super(expressions, columnCount);
currentGroup = null; currentGroupByExprData = null;
currentGroupsKey = null;
} }
@Override @Override
public void reset() { public void reset() {
super.reset(); super.reset();
currentGroup = null; currentGroupByExprData = null;
currentGroupsKey = null;
} }
@Override @Override
...@@ -1501,11 +1583,11 @@ public class Select extends Query { ...@@ -1501,11 +1583,11 @@ public class Select extends Query {
Value[] row = null; Value[] row = null;
if (previousKeyValues == null) { if (previousKeyValues == null) {
previousKeyValues = keyValues; previousKeyValues = keyValues;
currentGroup = new HashMap<>(); currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
} else if (!Arrays.equals(previousKeyValues, keyValues)) { } else if (!Arrays.equals(previousKeyValues, keyValues)) {
row = createGroupSortedRow(previousKeyValues, columnCount); row = createGroupSortedRow(previousKeyValues, columnCount);
previousKeyValues = keyValues; previousKeyValues = keyValues;
currentGroup = new HashMap<>(); currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
} }
currentGroupRowId++; currentGroupRowId++;
......
...@@ -283,8 +283,7 @@ public class Aggregate extends Expression { ...@@ -283,8 +283,7 @@ public class Aggregate extends Expression {
// if (on != null) { // if (on != null) {
// on.updateAggregate(); // on.updateAggregate();
// } // }
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
...@@ -296,10 +295,10 @@ public class Aggregate extends Expression { ...@@ -296,10 +295,10 @@ public class Aggregate extends Expression {
} }
lastGroupRowId = groupRowId; lastGroupRowId = groupRowId;
AggregateData data = (AggregateData) group.get(this); AggregateData data = (AggregateData) select.getCurrentGroupExprData(this);
if (data == null) { if (data == null) {
data = AggregateData.create(type); data = AggregateData.create(type);
group.put(this, data); select.setCurrentGroupExprData(this, data);
} }
Value v = on == null ? null : on.getValue(session); Value v = on == null ? null : on.getValue(session);
if (type == AggregateType.GROUP_CONCAT) { if (type == AggregateType.GROUP_CONCAT) {
...@@ -372,13 +371,13 @@ public class Aggregate extends Expression { ...@@ -372,13 +371,13 @@ public class Aggregate extends Expression {
DbException.throwInternalError("type=" + type); DbException.throwInternalError("type=" + type);
} }
} }
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL()); throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
} }
AggregateData data = (AggregateData) group.get(this); AggregateData data = (AggregateData)select.getCurrentGroupExprData(this);
if (data == null) { if (data == null) {
data = AggregateData.create(type); data = AggregateData.create(type);
select.setCurrentGroupExprData(this, data);
} }
if (type == AggregateType.GROUP_CONCAT) { if (type == AggregateType.GROUP_CONCAT) {
Value[] array = ((AggregateDataCollecting) data).getArray(); Value[] array = ((AggregateDataCollecting) data).getArray();
......
...@@ -7,6 +7,7 @@ package org.h2.expression; ...@@ -7,6 +7,7 @@ package org.h2.expression;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.util.ValueHashMap; import org.h2.util.ValueHashMap;
...@@ -47,9 +48,9 @@ class AggregateDataHistogram extends AggregateData { ...@@ -47,9 +48,9 @@ class AggregateDataHistogram extends AggregateData {
} }
ValueArray[] values = new ValueArray[distinctValues.size()]; ValueArray[] values = new ValueArray[distinctValues.size()];
int i = 0; int i = 0;
for (Value dv : distinctValues.keys()) { for (Map.Entry<Value,AggregateDataHistogram> entry : distinctValues.entries()) {
AggregateDataHistogram d = distinctValues.get(dv); AggregateDataHistogram d = entry.getValue();
values[i] = ValueArray.get(new Value[] { dv, ValueLong.get(d.count) }); values[i] = ValueArray.get(new Value[] { entry.getKey(), ValueLong.get(d.count) });
i++; i++;
} }
final CompareMode compareMode = database.getCompareMode(); final CompareMode compareMode = database.getCompareMode();
......
...@@ -159,14 +159,13 @@ public class ExpressionColumn extends Expression { ...@@ -159,14 +159,13 @@ public class ExpressionColumn extends Expression {
if (select == null) { if (select == null) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL()); throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
} }
HashMap<Expression, Object> values = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (values == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
Value v = (Value) values.get(this); Value v = (Value) select.getCurrentGroupExprData(this);
if (v == null) { if (v == null) {
values.put(this, now); select.setCurrentGroupExprData(this, now);
} else { } else {
if (!database.areEqual(now, v)) { if (!database.areEqual(now, v)) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL()); throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
...@@ -178,9 +177,8 @@ public class ExpressionColumn extends Expression { ...@@ -178,9 +177,8 @@ public class ExpressionColumn extends Expression {
public Value getValue(Session session) { public Value getValue(Session session) {
Select select = columnResolver.getSelect(); Select select = columnResolver.getSelect();
if (select != null) { if (select != null) {
HashMap<Expression, Object> values = select.getCurrentGroup(); if (select.isCurrentGroup()) {
if (values != null) { Value v = (Value) select.getCurrentGroupExprData(this);
Value v = (Value) values.get(this);
if (v != null) { if (v != null) {
return v; return v;
} }
......
...@@ -167,15 +167,14 @@ public class JavaAggregate extends Expression { ...@@ -167,15 +167,14 @@ public class JavaAggregate extends Expression {
@Override @Override
public Value getValue(Session session) { public Value getValue(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL()); throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
} }
try { try {
Aggregate agg; Aggregate agg;
if (distinct) { if (distinct) {
agg = getInstance(); agg = getInstance();
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this); AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data != null) { if (data != null) {
for (Value value : data.values) { for (Value value : data.values) {
if (args.length == 1) { if (args.length == 1) {
...@@ -191,7 +190,7 @@ public class JavaAggregate extends Expression { ...@@ -191,7 +190,7 @@ public class JavaAggregate extends Expression {
} }
} }
} else { } else {
agg = (Aggregate) group.get(this); agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) { if (agg == null) {
agg = getInstance(); agg = getInstance();
} }
...@@ -208,8 +207,7 @@ public class JavaAggregate extends Expression { ...@@ -208,8 +207,7 @@ public class JavaAggregate extends Expression {
@Override @Override
public void updateAggregate(Session session) { public void updateAggregate(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
...@@ -229,10 +227,10 @@ public class JavaAggregate extends Expression { ...@@ -229,10 +227,10 @@ public class JavaAggregate extends Expression {
try { try {
if (distinct) { if (distinct) {
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this); AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data == null) { if (data == null) {
data = new AggregateDataCollecting(); data = new AggregateDataCollecting();
group.put(this, data); select.setCurrentGroupExprData(this, data);
} }
Value[] argValues = new Value[args.length]; Value[] argValues = new Value[args.length];
Value arg = null; Value arg = null;
...@@ -243,10 +241,10 @@ public class JavaAggregate extends Expression { ...@@ -243,10 +241,10 @@ public class JavaAggregate extends Expression {
} }
data.add(session.getDatabase(), dataType, true, args.length == 1 ? arg : ValueArray.get(argValues)); data.add(session.getDatabase(), dataType, true, args.length == 1 ? arg : ValueArray.get(argValues));
} else { } else {
Aggregate agg = (Aggregate) group.get(this); Aggregate agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) { if (agg == null) {
agg = getInstance(); agg = getInstance();
group.put(this, agg); select.setCurrentGroupExprData(this, agg);
} }
Object[] argValues = new Object[args.length]; Object[] argValues = new Object[args.length];
Object arg = null; Object arg = null;
......
...@@ -5,20 +5,38 @@ ...@@ -5,20 +5,38 @@
*/ */
package org.h2.util; package org.h2.util;
import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* This hash map supports keys of type Value. * This hash map supports keys of type Value.
* <p>
* ValueHashMap is a very simple implementation without allocation of additional
* objects for entries. It's very fast with good distribution of hashes, but if
* hashes have a lot of collisions this implementation tends to be very slow.
* <p>
* HashMap in archaic versions of Java have some overhead for allocation of
* entries, but slightly better behaviour with limited number of collisions,
* because collisions have no impact on non-colliding entries. HashMap in modern
* versions of Java also have the same overhead, but it builds a trees of keys
* with colliding hashes, that's why even if the all keys have exactly the same
* hash code it still offers a good performance similar to TreeMap. So
* ValueHashMap is faster in typical cases, but may behave really bad in some
* cases. HashMap is slower in typical cases, but its performance does not
* degrade too much even in the worst possible case (if keys are comparable).
* *
* @param <V> the value type * @param <V> the value type
*/ */
public class ValueHashMap<V> extends HashBase { public class ValueHashMap<V> extends HashBase {
private Value[] keys; Value[] keys;
private V[] values; V[] values;
/** /**
* Create a new value hash map. * Create a new value hash map.
...@@ -175,6 +193,51 @@ public class ValueHashMap<V> extends HashBase { ...@@ -175,6 +193,51 @@ public class ValueHashMap<V> extends HashBase {
return list; return list;
} }
public Iterable<Map.Entry<Value, V>> entries() {
return new EntryIterable();
}
private final class EntryIterable implements Iterable<Map.Entry<Value, V>> {
EntryIterable() {
}
@Override
public Iterator<Map.Entry<Value, V>> iterator() {
return new EntryIterator();
}
}
private final class EntryIterator implements Iterator<Map.Entry<Value, V>> {
private int keysIndex = -1;
private int left = size;
EntryIterator() {
}
@Override
public boolean hasNext() {
return left > 0;
}
@Override
public Map.Entry<Value, V> next() {
if (left <= 0)
throw new NoSuchElementException();
left--;
for (;;) {
keysIndex++;
Value key = keys[keysIndex];
if (key != null && key != ValueNull.DELETED)
return new AbstractMap.SimpleImmutableEntry<Value, V>(key, values[keysIndex]);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/** /**
* Get the list of values. * Get the list of values.
* *
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论