Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

SERVER-12825 don't use ixisect if a compound index is available

  • Loading branch information...
commit 997798bde77060ce9d3990d3f4caabbb9e5058df 1 parent 4c599a8
@dstorch dstorch authored
View
69 src/mongo/db/query/plan_enumerator.cpp
@@ -480,15 +480,19 @@ namespace mongo {
// 0. Impose an ordering (idx1, idx2) using the key patterns.
// (*See note below.)
// 1. Assign predicates which prefix idx1 to idx1.
- // 2. Add assigned indices to a set of indices---the "already
+ // 2. Add assigned predicates to a set of predicates---the "already
// assigned set".
// 3. Assign predicates which prefix idx2 to idx2, as long as they
- // been assigned to idx1 already. Add newly assigned indices to
+ // been assigned to idx1 already. Add newly assigned predicates to
// the "already assigned set".
// 4. Try to assign predicates to idx1 by compounding.
// 5. Add any predicates assigned to idx1 by compounding to the
// "already assigned set",
// 6. Try to assign predicates to idx2 by compounding.
+ // 7. Determine if we have already assigned all predicates in
+ // the "already assigned set" to a single index. If so, then
+ // don't generate an ixisect solution, as compounding will
+ // be better. Otherwise, output the ixisect assignments.
//
// *NOTE on ordering. Suppose we have two indices A and B, and a
// predicate P1 which is over the prefix of both indices A and B.
@@ -676,6 +680,25 @@ namespace mongo {
}
}
+ // Add predicates in 'secondAssign' to the set of all assigned predicates.
+ for (size_t i = 0; i < secondAssign.preds.size(); ++i) {
+ if (predsAssigned.end() == predsAssigned.find(secondAssign.preds[i])) {
+ predsAssigned.insert(secondAssign.preds[i]);
+ }
+ }
+
+ //
+ // Step #7:
+ // Make sure we haven't already assigned this set of predicates by compounding.
+ // If we have, then bail out for this pair of indices.
+ //
+ if (alreadyCompounded(predsAssigned, andAssignment)) {
+ // There is no need to add either 'firstAssign' or 'secondAssign'
+ // to 'andAssignment' in this case because we have already performed
+ // assignments to single indices in enumerateOneIndex(...).
+ continue;
+ }
+
// We're done with this particular pair of indices; output
// the resulting assignments.
AndEnumerableState state;
@@ -830,6 +853,48 @@ namespace mongo {
}
}
+ bool PlanEnumerator::alreadyCompounded(const set<MatchExpression*>& ixisectAssigned,
+ const AndAssignment* andAssignment) {
+ for (size_t i = 0; i < andAssignment->choices.size(); ++i) {
+ const AndEnumerableState& state = andAssignment->choices[i];
+
+ // We cannot have assigned this set of predicates already by
+ // compounding unless this is an assignment to a single index.
+ if (state.assignments.size() != 1) {
+ continue;
+ }
+
+ // If the set of preds in 'ixisectAssigned' is a subset of 'oneAssign.preds',
+ // then all the preds can be used by compounding on a single index.
+ const OneIndexAssignment& oneAssign = state.assignments[0];
+
+ // If 'ixisectAssigned' is larger than 'oneAssign.preds', then
+ // it can't be a subset.
+ if (ixisectAssigned.size() > oneAssign.preds.size()) {
+ continue;
+ }
+
+ // Check for subset by counting the number of elements in 'oneAssign.preds'
+ // that are contained in 'ixisectAssigned'. The elements of both 'oneAssign.preds'
+ // and 'ixisectAssigned' are unique (no repeated elements).
+ size_t count = 0;
+ for (size_t j = 0; j < oneAssign.preds.size(); ++j) {
+ if (ixisectAssigned.end() != ixisectAssigned.find(oneAssign.preds[j])) {
+ ++count;
+ }
+ }
+
+ if (ixisectAssigned.size() == count) {
+ return true;
+ }
+
+ // We cannot assign the preds by compounding on 'oneAssign'.
+ // Move on to the next index.
+ }
+
+ return false;
+ }
+
void PlanEnumerator::compound(const vector<MatchExpression*>& tryCompound,
const IndexEntry& thisIndex,
OneIndexAssignment* assign) {
View
22 src/mongo/db/query/plan_enumerator.h
@@ -305,6 +305,28 @@ namespace mongo {
void getMultikeyCompoundablePreds(const MatchExpression* assigned,
const vector<MatchExpression*>& couldCompound,
vector<MatchExpression*>* out);
+
+ /**
+ * 'andAssignment' contains assignments that we've already committed to outputting,
+ * including both single index assignments and ixisect assignments.
+ *
+ * 'ixisectAssigned' is a set of predicates that we are about to add to 'andAssignment'
+ * as an index intersection assignment.
+ *
+ * Returns true if an single index assignment which is already in 'andAssignment'
+ * contains a superset of the predicates in 'ixisectAssigned'. This means that we
+ * can assign the same preds to a compound index rather than using index intersection.
+ *
+ * Ex.
+ * Suppose we have indices {a: 1}, {b: 1}, and {a: 1, b: 1} with query
+ * {a: 2, b: 2}. When we try to intersect {a: 1} and {b: 1} the predicates
+ * a==2 and b==2 will get assigned to respective indices. But then we will
+ * call this function with ixisectAssigned equal to the set {'a==2', 'b==2'},
+ * and notice that we have already assigned this same set of predicates to
+ * the single index {a: 1, b: 1} via compounding.
+ */
+ bool alreadyCompounded(const set<MatchExpression*>& ixisectAssigned,
+ const AndAssignment* andAssignment);
/**
* Output index intersection assignments inside of an AND node.
*/
View
94 src/mongo/db/query/query_planner_test.cpp
@@ -2806,18 +2806,18 @@ namespace {
// SERVER-12196
TEST_F(QueryPlannerTest, IntersectBasicTwoPredCompoundMatchesIdxOrder1) {
params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
- addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1));
addIndex(BSON("b" << 1));
runQuery(fromjson("{a:1, b:1}"));
assertNumSolutions(3U);
- assertSolutionExists("{fetch: {filter: null, node: "
- "{ixscan: {filter: null, pattern: {a:1, b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {b:1}, node: "
+ "{ixscan: {filter: null, pattern: {a:1}}}}}");
assertSolutionExists("{fetch: {filter: {a:1}, node: "
"{ixscan: {filter: null, pattern: {b:1}}}}}");
- assertSolutionExists("{fetch: {filter: null, node: {andHash: {nodes: ["
- "{ixscan: {filter: null, pattern: {a:1, b:1}}},"
+ assertSolutionExists("{fetch: {filter: null, node: {andSorted: {nodes: ["
+ "{ixscan: {filter: null, pattern: {a:1}}},"
"{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
}
@@ -2825,17 +2825,17 @@ namespace {
TEST_F(QueryPlannerTest, IntersectBasicTwoPredCompoundMatchesIdxOrder2) {
params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
addIndex(BSON("b" << 1));
- addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("a" << 1));
runQuery(fromjson("{a:1, b:1}"));
assertNumSolutions(3U);
- assertSolutionExists("{fetch: {filter: null, node: "
- "{ixscan: {filter: null, pattern: {a:1, b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {b:1}, node: "
+ "{ixscan: {filter: null, pattern: {a:1}}}}}");
assertSolutionExists("{fetch: {filter: {a:1}, node: "
"{ixscan: {filter: null, pattern: {b:1}}}}}");
- assertSolutionExists("{fetch: {filter: null, node: {andHash: {nodes: ["
- "{ixscan: {filter: null, pattern: {a:1, b:1}}},"
+ assertSolutionExists("{fetch: {filter: null, node: {andSorted: {nodes: ["
+ "{ixscan: {filter: null, pattern: {a:1}}},"
"{ixscan: {filter: null, pattern: {b:1}}}]}}}}");
}
@@ -2924,6 +2924,80 @@ namespace {
}
//
+ // Index intersection cases for SERVER-12825: make sure that
+ // we don't generate an ixisect plan if a compound index is
+ // available instead.
+ //
+
+ // SERVER-12825
+ TEST_F(QueryPlannerTest, IntersectCompoundInsteadBasic) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
+ addIndex(BSON("a" << 1));
+ addIndex(BSON("b" << 1));
+ addIndex(BSON("a" << 1 << "b" << 1));
+ runQuery(fromjson("{a: 1, b: 1}"));
+
+ assertNumSolutions(3U);
+ assertSolutionExists("{fetch: {filter: {b:1}, node: "
+ "{ixscan: {filter: null, pattern: {a:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {a:1}, node: "
+ "{ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: null, node: "
+ "{ixscan: {filter: null, pattern: {a:1,b:1}}}}}");
+ }
+
+ // SERVER-12825
+ TEST_F(QueryPlannerTest, IntersectCompoundInsteadThreeCompoundIndices) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("c" << 1 << "d" << 1));
+ addIndex(BSON("a" << 1 << "c" << -1 << "b" << -1 << "d" << 1));
+ runQuery(fromjson("{a: 1, b: 1, c: 1, d: 1}"));
+
+ assertNumSolutions(3U);
+ assertSolutionExists("{fetch: {filter: {$and: [{c:1},{d:1}]}, node: "
+ "{ixscan: {filter: null, pattern: {a:1,b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {$and:[{a:1},{b:1}]}, node: "
+ "{ixscan: {filter: null, pattern: {c:1,d:1}}}}}");
+ assertSolutionExists("{fetch: {filter: null, node: "
+ "{ixscan: {filter: null, pattern: {a:1,c:-1,b:-1,d:1}}}}}");
+ }
+
+ // SERVER-12825
+ TEST_F(QueryPlannerTest, IntersectCompoundInsteadUnusedField) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
+ addIndex(BSON("a" << 1));
+ addIndex(BSON("b" << 1));
+ addIndex(BSON("a" << 1 << "b" << 1 << "c" << 1));
+ runQuery(fromjson("{a: 1, b: 1}"));
+
+ assertNumSolutions(3U);
+ assertSolutionExists("{fetch: {filter: {b:1}, node: "
+ "{ixscan: {filter: null, pattern: {a:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {a:1}, node: "
+ "{ixscan: {filter: null, pattern: {b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: null, node: "
+ "{ixscan: {filter: null, pattern: {a:1,b:1,c:1}}}}}");
+ }
+
+ // SERVER-12825
+ TEST_F(QueryPlannerTest, IntersectCompoundInsteadUnusedField2) {
+ params.options = QueryPlannerParams::NO_TABLE_SCAN | QueryPlannerParams::INDEX_INTERSECTION;
+ addIndex(BSON("a" << 1 << "b" << 1));
+ addIndex(BSON("c" << 1 << "d" << 1));
+ addIndex(BSON("a" << 1 << "b" << 1 << "c" << 1));
+ runQuery(fromjson("{a: 1, c: 1}"));
+
+ assertNumSolutions(3U);
+ assertSolutionExists("{fetch: {filter: {c:1}, node: "
+ "{ixscan: {filter: null, pattern: {a:1,b:1}}}}}");
+ assertSolutionExists("{fetch: {filter: {a:1}, node: "
+ "{ixscan: {filter: null, pattern: {c:1,d:1}}}}}");
+ assertSolutionExists("{fetch: {filter: null, node: "
+ "{ixscan: {filter: null, pattern: {a:1,b:1,c:1}}}}}");
+ }
+
+ //
// 2dsphere V2 sparse indices, SERVER-9639
//
Please sign in to comment.
Something went wrong with that request. Please try again.