提交 990e81ce authored 作者: Noel Grandin's avatar Noel Grandin

choose better index when the index does not need to touch the table data

when we can satisfy a query from an index without touching table data,
then that index should have a lower cost than an index that DOES need
to retrieve table data
上级 0c6a8026
...@@ -5,11 +5,15 @@ ...@@ -5,11 +5,15 @@
*/ */
package org.h2.index; package org.h2.index;
import java.util.Arrays;
import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
import org.h2.engine.Mode; import org.h2.engine.Mode;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.result.Row; import org.h2.result.Row;
...@@ -21,6 +25,7 @@ import org.h2.table.IndexColumn; ...@@ -21,6 +25,7 @@ import org.h2.table.IndexColumn;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -148,7 +153,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -148,7 +153,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* b-tree range index. This is the estimated cost required to search one * b-tree range index. This is the estimated cost required to search one
* row, and then iterate over the given number of rows. * row, and then iterate over the given number of rows.
* *
* @param masks the search mask * @param masks the IndexCondition search masks, one for each Column in the table
* @param rowCount the number of rows in the index * @param rowCount the number of rows in the index
* @param filters all joined table filters * @param filters all joined table filters
* @param filter the current table filter index * @param filter the current table filter index
...@@ -233,6 +238,26 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -233,6 +238,26 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
cost -= coveringCount; cost -= coveringCount;
} }
} }
// If we have two indexes with the same cost, and one of the indexes can satisfy the query
// without needing to read from the primary table, make that one slightly lower cost
HashSet<Column> set1 = New.hashSet();
for (int i=0; i < filters.length; i++) {
if (filters[i].getSelect() != null) {
filters[i].getSelect().isEverything(ExpressionVisitor.getColumnsVisitor(set1));
}
}
if (!set1.isEmpty()) {
HashSet<Column> set2 = New.hashSet();
for (Column c : set1) {
if (c.getTable() == getTable()) {
set2.add(c);
}
}
set2.removeAll(Arrays.asList(getColumns()));
if (set2.isEmpty()) {
cost -= 1;
}
}
return cost; return cost;
} }
......
...@@ -219,8 +219,10 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -219,8 +219,10 @@ public class MVPrimaryIndex extends BaseIndex {
public double getCost(Session session, int[] masks, public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder) { TableFilter[] filters, int filter, SortOrder sortOrder) {
try { try {
long cost = 10 * (dataMap.sizeAsLongMax() + Constants.COST_ROW_OFFSET); // we use 9 here and 10 in MVSecondaryIndex to biase the decision towards
return cost; // using a table-scan when it has similar cost to an index
return 9 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(),
filters, filter, sortOrder);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED, e); throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
} }
......
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
package org.h2.table; package org.h2.table;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor; import org.h2.expression.ExpressionVisitor;
...@@ -104,12 +106,17 @@ public class Plan { ...@@ -104,12 +106,17 @@ public class Plan {
* @return the cost * @return the cost
*/ */
public double calculateCost(Session session) { public double calculateCost(Session session) {
if (session.getTrace().isDebugEnabled()) {
session.getTrace().debug("Plan : calculate cost for plan {0}", Arrays.toString(allFilters));
}
double cost = 1; double cost = 1;
boolean invalidPlan = false; boolean invalidPlan = false;
for (int i = 0; i < allFilters.length; i++) { for (int i = 0; i < allFilters.length; i++) {
TableFilter tableFilter = allFilters[i]; TableFilter tableFilter = allFilters[i];
session.getTrace().debug("Plan : for table filter {0}", tableFilter);
PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i); PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i);
planItems.put(tableFilter, item); planItems.put(tableFilter, item);
session.getTrace().debug("Plan : best plan item cost {0} index {1}", (int)item.cost, item.getIndex().getPlanSQL());
cost += cost * item.cost; cost += cost * item.cost;
setEvaluatable(tableFilter, true); setEvaluatable(tableFilter, true);
Expression on = tableFilter.getJoinCondition(); Expression on = tableFilter.getJoinCondition();
...@@ -123,6 +130,7 @@ public class Plan { ...@@ -123,6 +130,7 @@ public class Plan {
if (invalidPlan) { if (invalidPlan) {
cost = Double.POSITIVE_INFINITY; cost = Double.POSITIVE_INFINITY;
} }
session.getTrace().debug("Plan : plan cost {0}", (int)cost);
for (TableFilter f : allFilters) { for (TableFilter f : allFilters) {
setEvaluatable(f, false); setEvaluatable(f, false);
} }
......
...@@ -697,11 +697,13 @@ public abstract class Table extends SchemaObjectBase { ...@@ -697,11 +697,13 @@ public abstract class Table extends SchemaObjectBase {
PlanItem item = new PlanItem(); PlanItem item = new PlanItem();
item.setIndex(getScanIndex(session)); item.setIndex(getScanIndex(session));
item.cost = item.getIndex().getCost(session, null, filters, filter, null); item.cost = item.getIndex().getCost(session, null, filters, filter, null);
session.getTrace().debug("Table : potential plan item cost {0} index {1}", item.cost, item.getIndex().getPlanSQL());
ArrayList<Index> indexes = getIndexes(); ArrayList<Index> indexes = getIndexes();
if (indexes != null && masks != null) { if (indexes != null && masks != null) {
for (int i = 1, size = indexes.size(); i < size; i++) { for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i); Index index = indexes.get(i);
double cost = index.getCost(session, masks, filters, filter, sortOrder); double cost = index.getCost(session, masks, filters, filter, sortOrder);
session.getTrace().debug("Table : potential plan item cost {0} index {1}", cost, index.getPlanSQL());
if (cost < item.cost) { if (cost < item.cost) {
item.cost = cost; item.cost = cost;
item.setIndex(index); item.setIndex(index);
......
...@@ -62,6 +62,7 @@ public class TestOptimizations extends TestBase { ...@@ -62,6 +62,7 @@ public class TestOptimizations extends TestBase {
testNestedInSelect(); testNestedInSelect();
testInSelectJoin(); testInSelectJoin();
testMinMaxNullOptimization(); testMinMaxNullOptimization();
testUseCoveringIndex();
// testUseIndexWhenAllColumnsNotInOrderBy(); // testUseIndexWhenAllColumnsNotInOrderBy();
if (config.networked) { if (config.networked) {
return; return;
...@@ -1027,4 +1028,21 @@ public class TestOptimizations extends TestBase { ...@@ -1027,4 +1028,21 @@ public class TestOptimizations extends TestBase {
conn.close(); conn.close();
} }
private void testUseCoveringIndex() throws SQLException {
deleteDb("optimizations");
Connection conn = getConnection("optimizations");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TBL_A(id IDENTITY PRIMARY KEY NOT NULL, name VARCHAR NOT NULL, active BOOLEAN DEFAULT TRUE, UNIQUE KEY TBL_A_UK (name) )");
stat.execute("CREATE TABLE TBL_B(id IDENTITY PRIMARY KEY NOT NULL, tbl_a_id BIGINT NOT NULL, createDate TIMESTAMP DEFAULT NOW(), UNIQUE KEY TBL_B_UK (tbl_a_id, createDate), FOREIGN KEY (tbl_a_id) REFERENCES TBL_A(id) )");
stat.execute("INSERT INTO TBL_A (name) SELECT 'package_' || CAST(X as VARCHAR) FROM SYSTEM_RANGE(1, 100) WHERE X <= 100");
stat.execute("INSERT INTO TBL_B (tbl_a_id, createDate) SELECT CASE WHEN tbl_a_id = 0 THEN 1 ELSE tbl_a_id END, createDate FROM ( SELECT ROUND((RAND() * 100)) AS tbl_a_id, DATEADD('SECOND', X, NOW()) as createDate FROM SYSTEM_RANGE(1, 50000) WHERE X < 50000 )");
stat.execute("CREATE INDEX tbl_b_idx ON tbl_b(tbl_a_id, id)");
stat.execute("ANALYZE");
ResultSet rs = stat.executeQuery("EXPLAIN ANALYZE SELECT MAX(b.id) as id FROM tbl_b b JOIN tbl_a a ON b.tbl_a_id = a.id GROUP BY b.tbl_a_id HAVING A.ACTIVE = TRUE");
rs.next();
assertContains(rs.getString(1), "/* PUBLIC.TBL_B_IDX: TBL_A_ID = A.ID */");
conn.close();
}
} }
...@@ -228,7 +228,7 @@ public class TestTableEngines extends TestBase { ...@@ -228,7 +228,7 @@ public class TestTableEngines extends TestBase {
checkPlan(stat, "select * from t where a > 0 and b > ''", "IDX_B_A"); checkPlan(stat, "select * from t where a > 0 and b > ''", "IDX_B_A");
checkPlan(stat, "select * from t where b < ''", "IDX_B_A"); checkPlan(stat, "select * from t where b < ''", "IDX_B_A");
checkPlan(stat, "select * from t where b < '' and c < 1", "IDX_C_B_A"); checkPlan(stat, "select * from t where b < '' and c < 1", "IDX_C_B_A");
checkPlan(stat, "select * from t where a = 0", "scan"); checkPlan(stat, "select * from t where a = 0", "IDX_C_B_A");
checkPlan(stat, "select * from t where a > 0 order by c, b", "IDX_C_B_A"); checkPlan(stat, "select * from t where a > 0 order by c, b", "IDX_C_B_A");
checkPlan(stat, "select * from t where a = 0 and c > 0", "IDX_C_B_A"); checkPlan(stat, "select * from t where a = 0 and c > 0", "IDX_C_B_A");
checkPlan(stat, "select * from t where a = 0 and b < 0", "IDX_B_A"); checkPlan(stat, "select * from t where a = 0 and b < 0", "IDX_B_A");
...@@ -535,11 +535,11 @@ public class TestTableEngines extends TestBase { ...@@ -535,11 +535,11 @@ public class TestTableEngines extends TestBase {
+ "/* SELECT A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/ */ " + "/* SELECT A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/ */ "
+ "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_B: B = Z.A */ " + "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_B: B = Z.A */ "
+ "ON 1=1 WHERE Z.A = T.B"); + "ON 1=1 WHERE Z.A = T.B");
checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.T_IDX_B */ "
+ "INNER JOIN ( SELECT A FROM PUBLIC.T ) Z " + "INNER JOIN ( SELECT A FROM PUBLIC.T ) Z "
+ "/* batched:view SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ " + "/* batched:view SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ "
+ "WHERE A IS ?1: A = T.B */ ON 1=1 WHERE Z.A = T.B"); + "WHERE A IS ?1: A = T.B */ ON 1=1 WHERE Z.A = T.B");
checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.T_IDX_A */ "
+ "INNER JOIN ( ((SELECT A FROM PUBLIC.T) UNION ALL (SELECT B FROM PUBLIC.U)) " + "INNER JOIN ( ((SELECT A FROM PUBLIC.T) UNION ALL (SELECT B FROM PUBLIC.U)) "
+ "UNION ALL (SELECT B FROM PUBLIC.T) ) Z /* batched:view " + "UNION ALL (SELECT B FROM PUBLIC.T) ) Z /* batched:view "
+ "((SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ WHERE A IS ?1) " + "((SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ WHERE A IS ?1) "
...@@ -548,12 +548,12 @@ public class TestTableEngines extends TestBase { ...@@ -548,12 +548,12 @@ public class TestTableEngines extends TestBase {
+ "UNION ALL " + "UNION ALL "
+ "(SELECT B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ WHERE B IS ?1)" + "(SELECT B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ WHERE B IS ?1)"
+ ": A = T.A */ ON 1=1 WHERE Z.A = T.A"); + ": A = T.A */ ON 1=1 WHERE Z.A = T.A");
checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.T_IDX_A */ "
+ "INNER JOIN ( SELECT U.A FROM PUBLIC.U INNER JOIN PUBLIC.T ON 1=1 WHERE U.B = T.B ) Z " + "INNER JOIN ( SELECT U.A FROM PUBLIC.U INNER JOIN PUBLIC.T ON 1=1 WHERE U.B = T.B ) Z "
+ "/* batched:view SELECT U.A FROM PUBLIC.U /++ batched:fake PUBLIC.U_IDX_A: A IS ?1 ++/ " + "/* batched:view SELECT U.A FROM PUBLIC.U /++ batched:fake PUBLIC.U_IDX_A: A IS ?1 ++/ "
+ "/++ WHERE U.A IS ?1 ++/ INNER JOIN PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B = U.B ++/ " + "/++ WHERE U.A IS ?1 ++/ INNER JOIN PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B = U.B ++/ "
+ "ON 1=1 WHERE (U.A IS ?1) AND (U.B = T.B): A = T.A */ ON 1=1 WHERE Z.A = T.A"); + "ON 1=1 WHERE (U.A IS ?1) AND (U.B = T.B): A = T.A */ ON 1=1 WHERE Z.A = T.A");
checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.T /* PUBLIC.T_IDX_A */ "
+ "INNER JOIN ( SELECT A FROM PUBLIC.U ) Z /* SELECT A FROM PUBLIC.U " + "INNER JOIN ( SELECT A FROM PUBLIC.U ) Z /* SELECT A FROM PUBLIC.U "
+ "/++ PUBLIC.U_IDX_A: A IS ?1 ++/ WHERE A IS ?1: A = T.A */ ON 1=1 WHERE T.A = Z.A"); + "/++ PUBLIC.U_IDX_A: A IS ?1 ++/ WHERE A IS ?1: A = T.A */ ON 1=1 WHERE T.A = Z.A");
checkPlan(stat, "SELECT 1 FROM " checkPlan(stat, "SELECT 1 FROM "
...@@ -564,7 +564,7 @@ public class TestTableEngines extends TestBase { ...@@ -564,7 +564,7 @@ public class TestTableEngines extends TestBase {
+ "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE T.A = Z.A"); + "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE T.A = Z.A");
checkPlan(stat, "SELECT 1 FROM " checkPlan(stat, "SELECT 1 FROM "
+ "( SELECT U.A FROM PUBLIC.T INNER JOIN PUBLIC.U ON 1=1 WHERE T.B = U.B ) Z " + "( SELECT U.A FROM PUBLIC.T INNER JOIN PUBLIC.U ON 1=1 WHERE T.B = U.B ) Z "
+ "/* SELECT U.A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/ " + "/* SELECT U.A FROM PUBLIC.T /++ PUBLIC.T_IDX_B ++/ "
+ "INNER JOIN PUBLIC.U /++ PUBLIC.U_IDX_B: B = T.B ++/ " + "INNER JOIN PUBLIC.U /++ PUBLIC.U_IDX_B: B = T.B ++/ "
+ "ON 1=1 WHERE T.B = U.B */ INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ " + "ON 1=1 WHERE T.B = U.B */ INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ "
+ "ON 1=1 WHERE Z.A = T.A"); + "ON 1=1 WHERE Z.A = T.A");
...@@ -573,14 +573,14 @@ public class TestTableEngines extends TestBase { ...@@ -573,14 +573,14 @@ public class TestTableEngines extends TestBase {
+ "UNION " + "UNION "
+ "(SELECT A FROM PUBLIC.U /++ PUBLIC.\"scan\" ++/) */ " + "(SELECT A FROM PUBLIC.U /++ PUBLIC.\"scan\" ++/) */ "
+ "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE Z.A = T.A"); + "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE Z.A = T.A");
checkPlan(stat, "SELECT 1 FROM PUBLIC.U /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.U /* PUBLIC.U_IDX_B */ "
+ "INNER JOIN ( (SELECT A, B FROM PUBLIC.T) UNION (SELECT B, A FROM PUBLIC.U) ) Z " + "INNER JOIN ( (SELECT A, B FROM PUBLIC.T) UNION (SELECT B, A FROM PUBLIC.U) ) Z "
+ "/* batched:view (SELECT A, B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ " + "/* batched:view (SELECT A, B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ "
+ "WHERE B IS ?1) UNION (SELECT B, A FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ " + "WHERE B IS ?1) UNION (SELECT B, A FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ "
+ "WHERE A IS ?1): B = U.B */ ON 1=1 /* WHERE U.B = Z.B */ " + "WHERE A IS ?1): B = U.B */ ON 1=1 /* WHERE U.B = Z.B */ "
+ "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 " + "INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 "
+ "WHERE (U.B = Z.B) AND (Z.A = T.A)"); + "WHERE (U.B = Z.B) AND (Z.A = T.A)");
checkPlan(stat, "SELECT 1 FROM PUBLIC.U /* PUBLIC.\"scan\" */ " checkPlan(stat, "SELECT 1 FROM PUBLIC.U /* PUBLIC.U_IDX_A */ "
+ "INNER JOIN ( SELECT A, B FROM PUBLIC.U ) Z " + "INNER JOIN ( SELECT A, B FROM PUBLIC.U ) Z "
+ "/* batched:fake SELECT A, B FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ " + "/* batched:fake SELECT A, B FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ "
+ "WHERE A IS ?1: A = U.A */ ON 1=1 /* WHERE U.A = Z.A */ " + "WHERE A IS ?1: A = U.A */ ON 1=1 /* WHERE U.A = Z.A */ "
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论