提交 9d77b433 authored 作者: noelgrandin's avatar noelgrandin

Fix Issue 389: When there is a multi-column primary key, H2 does not seem to…

Fix Issue 389: When there is a multi-column primary key, H2 does not seem to always pick the right index
上级 8635dca5
......@@ -28,6 +28,7 @@ Change Log
</li><li>Fix issue #453, ABBA race conditions in TABLE LINK connection sharing.
</li><li>Fix Issue 449: Postgres Serial data type should not automatically be marked as primary key
</li><li>Fix Issue 406: support "SELECT h2version()"
</li><li>Fix Issue 389: When there is a multi-column primary key, H2 does not seem to always pick the right index
</li></ul>
<h2>Version 1.3.171 (2013-03-17)</h2>
......
......@@ -366,7 +366,7 @@ public class ScriptCommand extends ScriptBase {
}
private int generateInsertValues(int count, Table table) throws IOException {
PlanItem plan = table.getBestPlanItem(session, null);
PlanItem plan = table.getBestPlanItem(session, null, null);
Index index = plan.getIndex();
Cursor cursor = index.find(session, null, null);
Column[] columns = table.getColumns();
......
......@@ -1273,4 +1273,8 @@ public class Select extends Query {
return false;
}
public SortOrder prepareOrder() {
return sort;
}
}
......@@ -126,7 +126,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* @param rowCount the number of rows in the index
* @return the estimated cost
*/
protected long getCostRangeIndex(int[] masks, long rowCount) {
protected long getCostRangeIndex(int[] masks, long rowCount, SortOrder sortOrder) {
rowCount += Constants.COST_ROW_OFFSET;
long cost = rowCount;
long rows = rowCount;
......@@ -163,6 +163,34 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
break;
}
}
// If the query ORDER BY clause matches the ordering of this index, it will be cheaper
// than another index, so adjust the cost accordingly.
if (sortOrder != null) {
int[] columnIndexes = new int[ indexColumns.length ];
int[] columnSortTypes = new int[ indexColumns.length ];
for (int i = 0, len = indexColumns.length; i < len; i++) {
columnIndexes[i] = indexColumns[i].column.getColumnId();
columnSortTypes[i] = indexColumns[i].sortType;
}
boolean sortOrderMatches = true;
int[] sortOrderIndexes = sortOrder.getIndexes();
int coveringCount = 0;
for (int i = 0, len = sortOrderIndexes.length; i < len; i++) {
if (i >= columnIndexes.length) {
// we can still use this index if we are sorting by more than it's columns
break;
}
if (columnIndexes[i] != sortOrderIndexes[i] || columnSortTypes[i] != sortOrder.getSortTypes()[i]) {
sortOrderMatches = false;
break;
}
coveringCount++;
}
if (sortOrderMatches) {
// "coveringCount" makes sure that when we have two or more covering indexes, we choose the one that covers more
cost -= coveringCount;
}
}
return cost;
}
......
......@@ -10,6 +10,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.FunctionTable;
import org.h2.table.IndexColumn;
......@@ -45,7 +46,7 @@ public class FunctionIndex extends BaseIndex {
return new FunctionCursor(functionTable.getResult(session));
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
if (masks != null) {
throw DbException.getUnsupportedException("ALIAS");
}
......
......@@ -10,6 +10,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.RegularTable;
......@@ -93,7 +94,7 @@ public class HashIndex extends BaseIndex {
// nothing to do
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
for (Column column : columns) {
int index = column.getColumnId();
int mask = masks[index];
......
......@@ -9,6 +9,7 @@ package org.h2.index;
import org.h2.engine.Session;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.schema.SchemaObject;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
......@@ -81,7 +82,7 @@ public interface Index extends SchemaObject {
* see constants in IndexCondition
* @return the estimated cost
*/
double getCost(Session session, int[] masks);
double getCost(Session session, int[] masks, SortOrder sortOrder);
/**
* Remove the index.
......
......@@ -14,6 +14,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableLink;
......@@ -134,8 +135,8 @@ public class LinkedIndex extends BaseIndex {
}
}
public double getCost(Session session, int[] masks) {
return 100 + getCostRangeIndex(masks, rowCount + Constants.COST_ROW_OFFSET);
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 100 + getCostRangeIndex(masks, rowCount + Constants.COST_ROW_OFFSET, sortOrder);
}
public void remove(Session session) {
......
......@@ -11,6 +11,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.MetaTable;
......@@ -46,11 +47,11 @@ public class MetaIndex extends BaseIndex {
return new MetaCursor(rows);
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
if (scan) {
return 10 * MetaTable.ROW_COUNT_APPROXIMATION;
}
return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION);
return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION, sortOrder);
}
public void truncate(Session session) {
......
......@@ -13,6 +13,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
......@@ -123,8 +124,8 @@ public class MultiVersionIndex implements Index {
return cursor;
}
public double getCost(Session session, int[] masks) {
return base.getCost(session, masks);
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return base.getCost(session, masks, sortOrder);
}
public boolean needRebuild() {
......
......@@ -13,6 +13,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.store.Data;
import org.h2.store.Page;
import org.h2.store.PageStore;
......@@ -216,8 +217,8 @@ public class PageBtreeIndex extends PageIndex {
return cursor;
}
public double getCost(Session session, int[] masks) {
return 10 * getCostRangeIndex(masks, tableData.getRowCount(session));
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, tableData.getRowCount(session), sortOrder);
}
public boolean needRebuild() {
......
......@@ -19,6 +19,7 @@ import org.h2.engine.UndoLogRecord;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.store.Page;
import org.h2.store.PageStore;
import org.h2.table.Column;
......@@ -300,7 +301,7 @@ public class PageDataIndex extends PageIndex {
return root.getLastKey();
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
long cost = 10 * (tableData.getRowCountApproximation() + Constants.COST_ROW_OFFSET);
return cost;
}
......
......@@ -10,6 +10,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.store.PageStore;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
......@@ -84,8 +85,8 @@ public class PageDelegateIndex extends PageIndex {
return -1;
}
public double getCost(Session session, int[] masks) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session));
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session), sortOrder);
}
public boolean needRebuild() {
......
......@@ -10,6 +10,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.IndexColumn;
import org.h2.table.RangeTable;
......@@ -54,7 +55,7 @@ public class RangeIndex extends BaseIndex {
return new RangeCursor(start, end);
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 1;
}
......
......@@ -19,6 +19,7 @@ import org.h2.engine.UndoLogRecord;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.RegularTable;
......@@ -159,7 +160,7 @@ public class ScanIndex extends BaseIndex {
return new ScanCursor(session, this, database.isMultiVersion());
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return tableData.getRowCountApproximation() + Constants.COST_ROW_OFFSET;
}
......
......@@ -11,6 +11,7 @@ import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.IndexColumn;
import org.h2.table.RegularTable;
import org.h2.table.TableFilter;
......@@ -308,8 +309,8 @@ public class TreeIndex extends BaseIndex {
return new TreeCursor(this, x, first, last);
}
public double getCost(Session session, int[] masks) {
return getCostRangeIndex(masks, tableData.getRowCountApproximation());
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return getCostRangeIndex(masks, tableData.getRowCountApproximation(), sortOrder);
}
public void remove(Session session) {
......
......@@ -19,6 +19,7 @@ import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableView;
......@@ -108,7 +109,7 @@ public class ViewIndex extends BaseIndex {
double cost;
}
public synchronized double getCost(Session session, int[] masks) {
public synchronized double getCost(Session session, int[] masks, SortOrder sortOrder) {
if (recursive) {
return 1000;
}
......
......@@ -13,6 +13,7 @@ import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
......@@ -65,8 +66,8 @@ public class MVDelegateIndex extends BaseIndex {
return -1;
}
public double getCost(Session session, int[] masks) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session));
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 10 * getCostRangeIndex(masks, mainIndex.getRowCount(session), sortOrder);
}
public boolean needRebuild() {
......
......@@ -177,7 +177,7 @@ public class MVPrimaryIndex extends BaseIndex {
}
@Override
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
TransactionMap<Value, Value> map = getMap(session);
long cost = 10 * (map.getSize() + Constants.COST_ROW_OFFSET);
return cost;
......
......@@ -171,9 +171,9 @@ public class MVSecondaryIndex extends BaseIndex {
}
@Override
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
TransactionMap<Value, Value> map = getMap(session);
return 10 * getCostRangeIndex(masks, map.getSize());
return 10 * getCostRangeIndex(masks, map.getSize(), sortOrder);
}
@Override
......
......@@ -29,6 +29,7 @@ import org.h2.result.RowList;
import org.h2.result.SearchRow;
import org.h2.result.SimpleRow;
import org.h2.result.SimpleRowValue;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
import org.h2.schema.SchemaObjectBase;
import org.h2.schema.Sequence;
......@@ -627,15 +628,15 @@ public abstract class Table extends SchemaObjectBase {
* see constants in IndexCondition
* @return the plan item
*/
public PlanItem getBestPlanItem(Session session, int[] masks) {
public PlanItem getBestPlanItem(Session session, int[] masks, SortOrder sortOrder) {
PlanItem item = new PlanItem();
item.setIndex(getScanIndex(session));
item.cost = item.getIndex().getCost(session, null);
item.cost = item.getIndex().getCost(session, null, null);
ArrayList<Index> indexes = getIndexes();
if (indexes != null && masks != null) {
for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i);
double cost = index.getCost(session, masks);
double cost = index.getCost(session, masks, sortOrder);
if (cost < item.cost) {
item.cost = cost;
item.setIndex(index);
......
......@@ -23,6 +23,7 @@ import org.h2.index.IndexCursor;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.util.New;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
......@@ -159,7 +160,7 @@ public class TableFilter implements ColumnResolver {
if (indexConditions.size() == 0) {
item = new PlanItem();
item.setIndex(table.getScanIndex(s));
item.cost = item.getIndex().getCost(s, null);
item.cost = item.getIndex().getCost(s, null, null);
} else {
int len = table.getColumns().length;
int[] masks = new int[len];
......@@ -175,7 +176,11 @@ public class TableFilter implements ColumnResolver {
}
}
}
item = table.getBestPlanItem(s, masks);
SortOrder sortOrder = null;
if (select != null) {
sortOrder = select.prepareOrder();
}
item = table.getBestPlanItem(s, masks, sortOrder);
// The more index conditions, the earlier the table.
// This is to ensure joins without indexes run quickly:
// x (x.a=10); y (x.b=y.b) - see issue 113
......
......@@ -23,6 +23,7 @@ import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.Row;
import org.h2.result.SortOrder;
import org.h2.schema.Schema;
import org.h2.util.IntArray;
import org.h2.util.New;
......@@ -200,9 +201,9 @@ public class TableView extends Table {
return createException != null;
}
public synchronized PlanItem getBestPlanItem(Session session, int[] masks) {
public synchronized PlanItem getBestPlanItem(Session session, int[] masks, SortOrder sortOrder) {
PlanItem item = new PlanItem();
item.cost = index.getCost(session, masks);
item.cost = index.getCost(session, masks, sortOrder);
IntArray masksArray = new IntArray(masks == null ? Utils.EMPTY_INT_ARRAY : masks);
SynchronizedVerifier.check(indexCache);
ViewIndex i2 = indexCache.get(masksArray);
......@@ -352,7 +353,7 @@ public class TableView extends Table {
String msg = createException.getMessage();
throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, createException, getSQL(), msg);
}
PlanItem item = getBestPlanItem(session, null);
PlanItem item = getBestPlanItem(session, null, null);
return item.getIndex();
}
......
......@@ -68,6 +68,7 @@ public class TestOptimizations extends TestBase {
testIn();
testMinMaxCountOptimization(true);
testMinMaxCountOptimization(false);
testOrderedIndexes();
deleteDb("optimizations");
}
......@@ -782,4 +783,28 @@ public class TestOptimizations extends TestBase {
conn.close();
}
/** Where there are multiple indices, and we have an ORDER BY, select the index that already has the required ordering. */
private void testOrderedIndexes() throws SQLException {
deleteDb("optimizations");
Connection conn = getConnection("optimizations");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE my_table(K1 INT, K2 INT, VAL VARCHAR, PRIMARY KEY(K1,K2))");
stat.execute("CREATE INDEX my_index ON my_table(K1,VAL);");
ResultSet rs = stat.executeQuery("EXPLAIN PLAN FOR SELECT * FROM my_table WHERE K1=7 ORDER BY K1,VAL");
rs.next();
assertContains(rs.getString(1), "/* PUBLIC.MY_INDEX: K1 = 7 */");
stat.execute("DROP TABLE my_table");
// where we have two covering indexes, make sure we choose the one that covers more
stat.execute("CREATE TABLE my_table(K1 INT, K2 INT, VAL VARCHAR)");
stat.execute("CREATE INDEX my_index1 ON my_table(K1,K2);");
stat.execute("CREATE INDEX my_index2 ON my_table(K1,K2,VAL);");
rs = stat.executeQuery("EXPLAIN PLAN FOR SELECT * FROM my_table WHERE K1=7 ORDER BY K1,K2,VAL");
rs.next();
assertContains(rs.getString(1), "/* PUBLIC.MY_INDEX2: K1 = 7 */");
conn.close();
}
}
......@@ -17,14 +17,15 @@ import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.SingleRowCursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.index.SingleRowCursor;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.IndexColumn;
import org.h2.table.TableBase;
import org.h2.table.Table;
import org.h2.table.TableBase;
import org.h2.table.TableFilter;
import org.h2.test.TestBase;
import org.h2.value.Value;
......@@ -176,7 +177,7 @@ public class TestTableEngines extends TestBase {
return false;
}
public double getCost(Session session, int[] masks) {
public double getCost(Session session, int[] masks, SortOrder sortOrder) {
return 0;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论