FindCmd是find命令生成执行计划影响因素有:1、过滤器中是否有_id字段;2、过滤其中是否有索引字段;3、是否有空间索引字段;4、是否有TEXT索引字段;5、命令中是否有sort;6、命令中是否有projection字段;7全表扫描。
例如: user表有字段_id,name,age,wages,字段age、wages建立索引。
db.user.find({age:2})命令中age是索引字段,生成执行计划IXSCAN(索引age=1) > FETCH。其他条件不符合,没有生成其他执行计划,具体源代码分析请参照文档《mongodb源代码Find的QueryPlanner::plan解析出来1个执行计划案例流程分析》
什么情况下生成多个执行计划?下面举个例子,根据例子分析
db.user.find({age:28}).sort({wages:1})
> db.user.find({age:28}).sort({wages:1})
{ "_id" : ObjectId("681dc7c924a18a334118bc27"), "name" : "zhong", "age" : 28, "wages" : 8900 }
{ "_id" : ObjectId("681dc7ef24a18a334118bc28"), "name" : "chengxuyuan", "age" : 28, "wages" : 18900 }
mongo/db/query/query_planner.cpp中QueryPlanner::plan根据db.user.find({age:28}).sort({wages:1})生成查询计划,下面对plan方法进行详细分析
StatusWith<std::vector<std::unique_ptr<QuerySolution>>> QueryPlanner::plan(
const CanonicalQuery& query, const QueryPlannerParams& params) {
LOG(5) << "Beginning planning..." << endl
<< "=============================" << endl
<< "Options = " << optionString(params.options) << endl
<< "Canonical query:" << endl
<< redact(query.toString()) << "=============================";
for (size_t i = 0; i < params.indices.size(); ++i) {
LOG(5) << "Index " << i << " is " << params.indices[i].toString();
}
const bool canTableScan = !(params.options & QueryPlannerParams::NO_TABLE_SCAN);
const bool isTailable = query.getQueryRequest().isTailable();
// If the query requests a tailable cursor, the only solution is a collscan + filter with
// tailable set on the collscan.
if (isTailable) {
if (!canTableScan) {
return Status(
ErrorCodes::NoQueryExecutionPlans,
"Running with 'notablescan', so tailable cursors (which always do a table "
"scan) are not allowed");
}
if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR)) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Tailable cursors and geo $near cannot be used together");
}
auto soln = buildCollscanSoln(query, isTailable, params);
if (!soln) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to build collection scan soln");
}
std::vector<std::unique_ptr<QuerySolution>> out;
out.push_back(std::move(soln));
return {std::move(out)};
}
// The hint or sort can be $natural: 1. If this happens, output a collscan. If both
// a $natural hint and a $natural sort are specified, then the direction of the collscan
// is determined by the sign of the sort (not the sign of the hint).
if (!query.getQueryRequest().getHint().isEmpty() ||
!query.getQueryRequest().getSort().isEmpty()) {
BSONObj hintObj = query.getQueryRequest().getHint();
BSONObj sortObj = query.getQueryRequest().getSort();
BSONElement naturalHint = dps::extractElementAtPath(hintObj, "$natural");
BSONElement naturalSort = dps::extractElementAtPath(sortObj, "$natural");
// A hint overrides a $natural sort. This means that we don't force a table
// scan if there is a $natural sort with a non-$natural hint.
if (!naturalHint.eoo() || (!naturalSort.eoo() && hintObj.isEmpty())) {
LOG(5) << "Forcing a table scan due to hinted $natural";
if (!canTableScan) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"hint $natural is not allowed, because 'notablescan' is enabled");
}
if (!query.getQueryRequest().getMin().isEmpty() ||
!query.getQueryRequest().getMax().isEmpty()) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"min and max are incompatible with $natural");
}
auto soln = buildCollscanSoln(query, isTailable, params);
if (!soln) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to build collection scan soln");
}
std::vector<std::unique_ptr<QuerySolution>> out;
out.push_back(std::move(soln));
return {std::move(out)};
}
}
// Hints require us to only consider the hinted index. If index filters in the query settings
// were used to override the allowed indices for planning, we should not use the hinted index
// requested in the query.
BSONObj hintedIndex;
if (!params.indexFiltersApplied) {
hintedIndex = query.getQueryRequest().getHint();
}
// Either the list of indices passed in by the caller, or the list of indices filtered according
// to the hint. This list is later expanded in order to allow the planner to handle wildcard
// indexes.
std::vector<IndexEntry> fullIndexList;
// Will hold a copy of the index entry chosen by the hint.
boost::optional<IndexEntry> hintedIndexEntry;
if (hintedIndex.isEmpty()) {
fullIndexList = params.indices;
} else {
fullIndexList = QueryPlannerIXSelect::findIndexesByHint(hintedIndex, params.indices);
if (fullIndexList.empty()) {
return Status(ErrorCodes::BadValue,
"hint provided does not correspond to an existing index");
}
if (fullIndexList.size() > 1) {
return Status(ErrorCodes::IndexNotFound,
str::stream()
<< "Hint matched multiple indexes, "
<< "must hint by index name. Matched: " << fullIndexList[0].toString()
<< " and " << fullIndexList[1].toString());
}
hintedIndexEntry.emplace(fullIndexList.front());
}
// Figure out what fields we care about.
stdx::unordered_set<string> fields;
QueryPlannerIXSelect::getFields(query.root(), &fields);
for (auto&& field : fields) {
LOG(5) << "Predicate over field '" << field << "'";
}
fullIndexList = QueryPlannerIXSelect::expandIndexes(fields, std::move(fullIndexList));
std::vector<IndexEntry> relevantIndices;
if (!hintedIndexEntry) {
relevantIndices = QueryPlannerIXSelect::findRelevantIndices(fields, fullIndexList);
} else {
relevantIndices = fullIndexList;
// Relevant indices should only ever exceed a size of 1 when there is a hint in the case of
// $** index.
if (relevantIndices.size() > 1) {
for (auto&& entry : relevantIndices) {
invariant(entry.type == IndexType::INDEX_WILDCARD);
}
}
}
// Deal with the .min() and .max() query options. If either exist we can only use an index
// that matches the object inside.
if (!query.getQueryRequest().getMin().isEmpty() ||
!query.getQueryRequest().getMax().isEmpty()) {
if (!hintedIndexEntry) {
return Status(ErrorCodes::Error(51173),
"When using min()/max() a hint of which index to use must be provided");
}
BSONObj minObj = query.getQueryRequest().getMin();
BSONObj maxObj = query.getQueryRequest().getMax();
if ((!minObj.isEmpty() &&
!indexCompatibleMaxMin(minObj, query.getCollator(), *hintedIndexEntry)) ||
(!maxObj.isEmpty() &&
!indexCompatibleMaxMin(maxObj, query.getCollator(), *hintedIndexEntry))) {
return Status(ErrorCodes::Error(51174),
"The index chosen is not compatible with min/max");
}
// Be sure that index expansion didn't do anything. As wildcard indexes are banned for
// min/max, we expect to find a single hinted index entry.
invariant(fullIndexList.size() == 1);
invariant(*hintedIndexEntry == fullIndexList.front());
// In order to be fully compatible, the min has to be less than the max according to the
// index key pattern ordering. The first step in verifying this is "finish" the min and max
// by replacing empty objects and stripping field names.
BSONObj finishedMinObj = finishMinObj(*hintedIndexEntry, minObj, maxObj);
BSONObj finishedMaxObj = finishMaxObj(*hintedIndexEntry, minObj, maxObj);
// Now we have the final min and max. This index is only relevant for the min/max query if
// min < max.
if (finishedMinObj.woCompare(finishedMaxObj, hintedIndexEntry->keyPattern, false) >= 0) {
return Status(ErrorCodes::Error(51175),
"The value provided for min() does not come before the value provided "
"for max() in the hinted index");
}
std::unique_ptr<QuerySolutionNode> solnRoot(QueryPlannerAccess::makeIndexScan(
*hintedIndexEntry, query, params, finishedMinObj, finishedMaxObj));
invariant(solnRoot);
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
if (!soln) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Sort and covering analysis failed while planning hint/min/max query");
}
std::vector<std::unique_ptr<QuerySolution>> out;
out.push_back(std::move(soln));
return {std::move(out)};
}
for (size_t i = 0; i < relevantIndices.size(); ++i) {
LOG(2) << "Relevant index " << i << " is " << relevantIndices[i].toString();
}
// Figure out how useful each index is to each predicate.
QueryPlannerIXSelect::rateIndices(query.root(), "", relevantIndices, query.getCollator());
QueryPlannerIXSelect::stripInvalidAssignments(query.root(), relevantIndices);
// Unless we have GEO_NEAR, TEXT, or a projection, we may be able to apply an optimization
// in which we strip unnecessary index assignments.
//
// Disallowed with projection because assignment to a non-unique index can allow the plan
// to be covered.
//
// TEXT and GEO_NEAR are special because they require the use of a text/geo index in order
// to be evaluated correctly. Stripping these "mandatory assignments" is therefore invalid.
if (query.getQueryRequest().getProj().isEmpty() &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) {
QueryPlannerIXSelect::stripUnneededAssignments(query.root(), relevantIndices);
}
// query.root() is now annotated with RelevantTag(s).
LOG(5) << "Rated tree:" << endl << redact(query.root()->debugString());
// If there is a GEO_NEAR it must have an index it can use directly.
const MatchExpression* gnNode = nullptr;
if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR, &gnNode)) {
// No index for GEO_NEAR? No query.
RelevantTag* tag = static_cast<RelevantTag*>(gnNode->getTag());
if (!tag || (0 == tag->first.size() && 0 == tag->notFirst.size())) {
LOG(5) << "Unable to find index for $geoNear query.";
// Don't leave tags on query tree.
query.root()->resetTag();
return Status(ErrorCodes::NoQueryExecutionPlans,
"unable to find index for $geoNear query");
}
LOG(5) << "Rated tree after geonear processing:" << redact(query.root()->debugString());
}
// Likewise, if there is a TEXT it must have an index it can use directly.
const MatchExpression* textNode = nullptr;
if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT, &textNode)) {
RelevantTag* tag = static_cast<RelevantTag*>(textNode->getTag());
// Exactly one text index required for TEXT. We need to check this explicitly because
// the text stage can't be built if no text index exists or there is an ambiguity as to
// which one to use.
size_t textIndexCount = 0;
for (size_t i = 0; i < fullIndexList.size(); i++) {
if (INDEX_TEXT == fullIndexList[i].type) {
textIndexCount++;
}
}
if (textIndexCount != 1) {
// Don't leave tags on query tree.
query.root()->resetTag();
return Status(ErrorCodes::NoQueryExecutionPlans,
"need exactly one text index for $text query");
}
// Error if the text node is tagged with zero indices.
if (0 == tag->first.size() && 0 == tag->notFirst.size()) {
// Don't leave tags on query tree.
query.root()->resetTag();
return Status(ErrorCodes::NoQueryExecutionPlans,
"failed to use text index to satisfy $text query (if text index is "
"compound, are equality predicates given for all prefix fields?)");
}
// At this point, we know that there is only one text index and that the TEXT node is
// assigned to it.
invariant(1 == tag->first.size() + tag->notFirst.size());
LOG(5) << "Rated tree after text processing:" << redact(query.root()->debugString());
}
std::vector<std::unique_ptr<QuerySolution>> out;
// If we have any relevant indices, we try to create indexed plans.
if (0 < relevantIndices.size()) {
// The enumerator spits out trees tagged with IndexTag(s).
PlanEnumeratorParams enumParams;
enumParams.intersect = params.options & QueryPlannerParams::INDEX_INTERSECTION;
enumParams.root = query.root();
enumParams.indices = &relevantIndices;
PlanEnumerator isp(enumParams);
isp.init().transitional_ignore();
unique_ptr<MatchExpression> nextTaggedTree;
while ((nextTaggedTree = isp.getNext()) && (out.size() < params.maxIndexedSolutions)) {
LOG(5) << "About to build solntree from tagged tree:" << endl
<< redact(nextTaggedTree->debugString());
// Store the plan cache index tree before calling prepareForAccessingPlanning(), so that
// the PlanCacheIndexTree has the same sort as the MatchExpression used to generate the
// plan cache key.
std::unique_ptr<MatchExpression> clone(nextTaggedTree->shallowClone());
std::unique_ptr<PlanCacheIndexTree> cacheData;
auto statusWithCacheData = cacheDataFromTaggedTree(clone.get(), relevantIndices);
if (!statusWithCacheData.isOK()) {
LOG(5) << "Query is not cachable: "
<< redact(statusWithCacheData.getStatus().reason());
} else {
cacheData = std::move(statusWithCacheData.getValue());
}
// We have already cached the tree in canonical order, so now we can order the nodes for
// access planning.
prepareForAccessPlanning(nextTaggedTree.get());
// This can fail if enumeration makes a mistake.
std::unique_ptr<QuerySolutionNode> solnRoot(QueryPlannerAccess::buildIndexedDataAccess(
query, std::move(nextTaggedTree), relevantIndices, params));
if (!solnRoot) {
continue;
}
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
if (soln) {
LOG(5) << "Planner: adding solution:" << endl << redact(soln->toString());
if (statusWithCacheData.isOK()) {
SolutionCacheData* scd = new SolutionCacheData();
scd->tree = std::move(cacheData);
soln->cacheData.reset(scd);
}
out.push_back(std::move(soln));
}
}
}
// Don't leave tags on query tree.
query.root()->resetTag();
LOG(5) << "Planner: outputted " << out.size() << " indexed solutions.";
// Produce legible error message for failed OR planning with a TEXT child.
// TODO: support collection scan for non-TEXT children of OR.
if (out.size() == 0 && textNode != nullptr &&
MatchExpression::OR == query.root()->matchType()) {
MatchExpression* root = query.root();
for (size_t i = 0; i < root->numChildren(); ++i) {
if (textNode == root->getChild(i)) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to produce a solution for TEXT under OR - "
"other non-TEXT clauses under OR have to be indexed as well.");
}
}
}
// An index was hinted. If there are any solutions, they use the hinted index. If not, we
// scan the entire index to provide results and output that as our plan. This is the
// desired behavior when an index is hinted that is not relevant to the query. In the case that
// $** index is hinted, we do not want this behavior.
if (!hintedIndex.isEmpty() && relevantIndices.size() == 1) {
if (out.size() > 0) {
return {std::move(out)};
}
if (relevantIndices.front().type == IndexType::INDEX_WILDCARD) {
return Status(
ErrorCodes::NoQueryExecutionPlans,
"$hint: refusing to build whole-index solution, because it's a wildcard index");
}
// Return hinted index solution if found.
auto soln = buildWholeIXSoln(relevantIndices.front(), query, params);
if (!soln) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to build whole-index solution for $hint");
}
LOG(5) << "Planner: outputting soln that uses hinted index as scan.";
std::vector<std::unique_ptr<QuerySolution>> out;
out.push_back(std::move(soln));
return {std::move(out)};
}
// If a sort order is requested, there may be an index that provides it, even if that
// index is not over any predicates in the query.
//
if (!query.getQueryRequest().getSort().isEmpty() &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) {
// See if we have a sort provided from an index already.
// This is implied by the presence of a non-blocking solution.
bool usingIndexToSort = false;
for (size_t i = 0; i < out.size(); ++i) {
auto soln = out[i].get();
if (!soln->hasBlockingStage) {
usingIndexToSort = true;
break;
}
}
if (!usingIndexToSort) {
for (size_t i = 0; i < fullIndexList.size(); ++i) {
const IndexEntry& index = fullIndexList[i];
// Only regular (non-plugin) indexes can be used to provide a sort, and only
// non-sparse indexes can be used to provide a sort.
//
// TODO: Sparse indexes can't normally provide a sort, because non-indexed
// documents could potentially be missing from the result set. However, if the
// query predicate can be used to guarantee that all documents to be returned
// are indexed, then the index should be able to provide the sort.
//
// For example:
// - Sparse index {a: 1, b: 1} should be able to provide a sort for
// find({b: 1}).sort({a: 1}). SERVER-13908.
// - Index {a: 1, b: "2dsphere"} (which is "geo-sparse", if
// 2dsphereIndexVersion=2) should be able to provide a sort for
// find({b: GEO}).sort({a:1}). SERVER-10801.
if (index.type != INDEX_BTREE) {
continue;
}
if (index.sparse) {
continue;
}
// If the index collation differs from the query collation, the index should not be
// used to provide a sort, because strings will be ordered incorrectly.
if (!CollatorInterface::collatorsMatch(index.collator, query.getCollator())) {
continue;
}
// Partial indexes can only be used to provide a sort only if the query predicate is
// compatible.
if (index.filterExpr && !expression::isSubsetOf(query.root(), index.filterExpr)) {
continue;
}
const BSONObj kp = QueryPlannerAnalysis::getSortPattern(index.keyPattern);
if (providesSort(query, kp)) {
LOG(5) << "Planner: outputting soln that uses index to provide sort.";
auto soln = buildWholeIXSoln(fullIndexList[i], query, params);
if (soln) {
PlanCacheIndexTree* indexTree = new PlanCacheIndexTree();
indexTree->setIndexEntry(fullIndexList[i]);
SolutionCacheData* scd = new SolutionCacheData();
scd->tree.reset(indexTree);
scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN;
scd->wholeIXSolnDir = 1;
soln->cacheData.reset(scd);
out.push_back(std::move(soln));
break;
}
}
if (providesSort(query, QueryPlannerCommon::reverseSortObj(kp))) {
LOG(5) << "Planner: outputting soln that uses (reverse) index "
<< "to provide sort.";
auto soln = buildWholeIXSoln(fullIndexList[i], query, params, -1);
if (soln) {
PlanCacheIndexTree* indexTree = new PlanCacheIndexTree();
indexTree->setIndexEntry(fullIndexList[i]);
SolutionCacheData* scd = new SolutionCacheData();
scd->tree.reset(indexTree);
scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN;
scd->wholeIXSolnDir = -1;
soln->cacheData.reset(scd);
out.push_back(std::move(soln));
break;
}
}
}
}
}
// If a projection exists, there may be an index that allows for a covered plan, even if none
// were considered earlier.
const auto projection = query.getProj();
if (params.options & QueryPlannerParams::GENERATE_COVERED_IXSCANS && out.size() == 0 &&
query.getQueryObj().isEmpty() && projection && !projection->requiresDocument()) {
const auto* indicesToConsider = hintedIndex.isEmpty() ? &fullIndexList : &relevantIndices;
for (auto&& index : *indicesToConsider) {
if (index.type != INDEX_BTREE || index.multikey || index.sparse || index.filterExpr ||
!CollatorInterface::collatorsMatch(index.collator, query.getCollator())) {
continue;
}
QueryPlannerParams paramsForCoveredIxScan;
auto soln = buildWholeIXSoln(index, query, paramsForCoveredIxScan);
if (soln && !soln->root->fetched()) {
LOG(5) << "Planner: outputting soln that uses index to provide projection.";
PlanCacheIndexTree* indexTree = new PlanCacheIndexTree();
indexTree->setIndexEntry(index);
SolutionCacheData* scd = new SolutionCacheData();
scd->tree.reset(indexTree);
scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN;
scd->wholeIXSolnDir = 1;
soln->cacheData.reset(scd);
out.push_back(std::move(soln));
break;
}
}
}
// The caller can explicitly ask for a collscan.
bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
// No indexed plans? We must provide a collscan if possible or else we can't run the query.
bool collScanRequired = 0 == out.size();
if (collScanRequired && !canTableScan) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"No indexed plans available, and running with 'notablescan'");
}
// geoNear and text queries *require* an index.
// Also, if a hint is specified it indicates that we MUST use it.
bool possibleToCollscan =
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT) && hintedIndex.isEmpty();
if (collScanRequired && !possibleToCollscan) {
return Status(ErrorCodes::NoQueryExecutionPlans, "No query solutions");
}
if (possibleToCollscan && (collscanRequested || collScanRequired)) {
auto collscan = buildCollscanSoln(query, isTailable, params);
if (!collscan && collScanRequired) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to build collection scan soln");
}
if (collscan) {
LOG(5) << "Planner: outputting a collscan:" << endl << redact(collscan->toString());
SolutionCacheData* scd = new SolutionCacheData();
scd->solnType = SolutionCacheData::COLLSCAN_SOLN;
collscan->cacheData.reset(scd);
out.push_back(std::move(collscan));
}
}
invariant(out.size() > 0);
return {std::move(out)};
}
} // namespace mongo
QueryPlanner::plan步骤1:打印db.user.find({age:28}).sort({wages:1})命令中关键参数,过滤器,排序,返回字段。
LOG(5) << "Beginning planning..." << endl
<< "=============================" << endl
<< "Options = " << optionString(params.options) << endl
<< "Canonical query:" << endl
<< redact(query.toString()) << "=============================";
打印结果是:age:28,wages正序,没有指定返回字段
=============================
Options = INDEX_INTERSECTION SPLIT_LIMITED_SORT
Canonical query:
ns=db.userTree: age $eq 28.0
Sort: { wages: 1.0 }
Proj: {}
=============================
QueryPlanner::plan步骤2:遍历user表的所有索引
for (size_t i = 0; i < params.indices.size(); ++i) {
LOG(5) << "Index " << i << " is " << params.indices[i].toString();
}
打印结果:_id是系统默认字段,字段age和wages手动增加的字段索引
[conn7] Index 0 is kp: { _id: 1 } unique name: '(_id_, )' io: { v: 2, key: { _id: 1 }, name: "_id_" }
[conn7] Index 1 is kp: { age: 1.0 } name: '(age_1, )' io: { v: 2, key: { age: 1.0 }, name: "age_1" }
[conn7] Index 2 is kp: { wages: 1.0 } name: '(wages_1, )' io: { v: 2, key: { wages: 1.0 }, name: "wages_1" }
QueryPlanner::plan步骤3:db.user.find({age:28}).sort({wages:1})命令查询哪些字段设置了索引
// Figure out what fields we care about.
stdx::unordered_set<string> fields;
QueryPlannerIXSelect::getFields(query.root(), &fields);
for (auto&& field : fields) {
LOG(5) << "Predicate over field '" << field << "'";
}
打印结果是:过滤器字段age设置了索引
[conn1] Predicate over field 'age'
QueryPlanner::plan步骤4:db.user.find({age:28}).sort({wages:1})命令相关过滤器索引有哪些
for (size_t i = 0; i < relevantIndices.size(); ++i) {
LOG(2) << "Relevant index " << i << " is " << relevantIndices[i].toString();
}
打印结果:
[conn1] Relevant index 0 is kp: { age: 1.0 } name: '(age_1, )' io: { v: 2, key: { age: 1.0 }, name: "age_1" }
QueryPlanner::plan步骤5:db.user.find({age:28}).sort({wages:1})命令判断是否有空间索引(gps),结果是没有
// If there is a GEO_NEAR it must have an index it can use directly.
const MatchExpression* gnNode = nullptr;
if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR, &gnNode)) {
// No index for GEO_NEAR? No query.
RelevantTag* tag = static_cast<RelevantTag*>(gnNode->getTag());
if (!tag || (0 == tag->first.size() && 0 == tag->notFirst.size())) {
LOG(5) << "Unable to find index for $geoNear query.";
// Don't leave tags on query tree.
query.root()->resetTag();
return Status(ErrorCodes::NoQueryExecutionPlans,
"unable to find index for $geoNear query");
}
LOG(5) << "Rated tree after geonear processing:" << redact(query.root()->debugString());
}
QueryPlanner::plan步骤6:db.user.find({age:28}).sort({wages:1})命令判断是否有TEXT索引,结果是没有
// Likewise, if there is a TEXT it must have an index it can use directly.
const MatchExpression* textNode = nullptr;
if (QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT, &textNode)) {
RelevantTag* tag = static_cast<RelevantTag*>(textNode->getTag());
...
LOG(5) << "Rated tree after text processing:" << redact(query.root()->debugString());
}
QueryPlanner::plan步骤7:db.user.find({age:28}).sort({wages:1})命令如果过滤器有索引字段,给每个索引生成对应的执行计划
if (0 < relevantIndices.size()) {
// The enumerator spits out trees tagged with IndexTag(s).
PlanEnumeratorParams enumParams;
enumParams.intersect = params.options & QueryPlannerParams::INDEX_INTERSECTION;
enumParams.root = query.root();
enumParams.indices = &relevantIndices;
PlanEnumerator isp(enumParams);
isp.init().transitional_ignore();
unique_ptr<MatchExpression> nextTaggedTree;
while ((nextTaggedTree = isp.getNext()) && (out.size() < params.maxIndexedSolutions)) {
LOG(5) << "About to build solntree from tagged tree:" << endl
<< redact(nextTaggedTree->debugString());
// Store the plan cache index tree before calling prepareForAccessingPlanning(), so that
// the PlanCacheIndexTree has the same sort as the MatchExpression used to generate the
// plan cache key.
std::unique_ptr<MatchExpression> clone(nextTaggedTree->shallowClone());
std::unique_ptr<PlanCacheIndexTree> cacheData;
auto statusWithCacheData = cacheDataFromTaggedTree(clone.get(), relevantIndices);
if (!statusWithCacheData.isOK()) {
LOG(5) << "Query is not cachable: "
<< redact(statusWithCacheData.getStatus().reason());
} else {
cacheData = std::move(statusWithCacheData.getValue());
}
// We have already cached the tree in canonical order, so now we can order the nodes for
// access planning.
prepareForAccessPlanning(nextTaggedTree.get());
// This can fail if enumeration makes a mistake.
std::unique_ptr<QuerySolutionNode> solnRoot(QueryPlannerAccess::buildIndexedDataAccess(
query, std::move(nextTaggedTree), relevantIndices, params));
if (!solnRoot) {
continue;
}
auto soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, std::move(solnRoot));
if (soln) {
LOG(5) << "Planner: adding solution:" << endl << redact(soln->toString());
if (statusWithCacheData.isOK()) {
SolutionCacheData* scd = new SolutionCacheData();
scd->tree = std::move(cacheData);
soln->cacheData.reset(scd);
}
out.push_back(std::move(soln));
}
}
}
打印结果是:执行计划是IXSCAN > FETCH > SORT_KEY_GENERATOR > SORT
QueryPlanner::plan步骤8:db.user.find({age:28}).sort({wages:1})如果有排序,给sort字段生成执行计划。按照字段wages正序排序,wages还是索引,所以需要生成一个执行计划。
if (!query.getQueryRequest().getSort().isEmpty() &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::GEO_NEAR) &&
!QueryPlannerCommon::hasNode(query.root(), MatchExpression::TEXT)) {
if (!usingIndexToSort) {
for (size_t i = 0; i < fullIndexList.size(); ++i) {
const IndexEntry& index = fullIndexList[i];
if (index.type != INDEX_BTREE) {
continue;
}
if (index.sparse) {
continue;
}
// If the index collation differs from the query collation, the index should not be
// used to provide a sort, because strings will be ordered incorrectly.
if (!CollatorInterface::collatorsMatch(index.collator, query.getCollator())) {
continue;
}
// Partial indexes can only be used to provide a sort only if the query predicate is
// compatible.
if (index.filterExpr && !expression::isSubsetOf(query.root(), index.filterExpr)) {
continue;
}
const BSONObj kp = QueryPlannerAnalysis::getSortPattern(index.keyPattern);
if (providesSort(query, kp)) {
LOG(3) << "Planner: outputting soln that uses index to provide sort.";
auto soln = buildWholeIXSoln(fullIndexList[i], query, params);
if (soln) {
PlanCacheIndexTree* indexTree = new PlanCacheIndexTree();
indexTree->setIndexEntry(fullIndexList[i]);
SolutionCacheData* scd = new SolutionCacheData();
scd->tree.reset(indexTree);
scd->solnType = SolutionCacheData::WHOLE_IXSCAN_SOLN;
scd->wholeIXSolnDir = 1;
soln->cacheData.reset(scd);
out.push_back(std::move(soln));
LOG(3) << "Planner: sort solution:" << endl << redact(soln->toString());
break;
}
}
...}
}
打印结果是:IXSCAN > FETCH
[conn1] Planner: outputting soln that uses index to provide sort.
[conn1] Planner: sort solution:
FETCH
---filter:
age $eq 28.0
---fetched = 1
---sortedByDiskLoc = 0
---getSort = [{ wages: 1 }, ]
---Child:
------IXSCAN
---------indexName = wages_1
keyPattern = { wages: 1.0 }
---------direction = 1
---------bounds = field #0['wages']: [MinKey, MaxKey]
---------fetched = 0
---------sortedByDiskLoc = 0
---------getSort = [{ wages: 1 }, ]
QueryPlanner::plan步骤9:db.user.find({age:28}).sort({wages:1})如果执行返回字段,给Proj字段生成执行计划,这个sql没有proj字段,所以没有生成新的计划。
const auto projection = query.getProj();
if (params.options & QueryPlannerParams::GENERATE_COVERED_IXSCANS && out.size() == 0 &&
query.getQueryObj().isEmpty() && projection && !projection->requiresDocument()){
...
}
QueryPlanner::plan步骤10:db.user.find({age:28}).sort({wages:1})命令;如果前面没有生成执行计划,则生成默认的全表扫描计划COLLSCAN,前面步骤7生成了age索引执行计划和步骤8生成了sort字段wages执行计算,所以这里不会生成COLLSCAN全表扫描计划了。
// The caller can explicitly ask for a collscan.
bool collscanRequested = (params.options & QueryPlannerParams::INCLUDE_COLLSCAN);
// No indexed plans? We must provide a collscan if possible or else we can't run the query.
bool collScanRequired = 0 == out.size();
if (collScanRequired && !canTableScan) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"No indexed plans available, and running with 'notablescan'");
}
if (possibleToCollscan && (collscanRequested || collScanRequired)) {
auto collscan = buildCollscanSoln(query, isTailable, params);
if (!collscan && collScanRequired) {
return Status(ErrorCodes::NoQueryExecutionPlans,
"Failed to build collection scan soln");
}
if (collscan) {
LOG(5) << "Planner: outputting a collscan:" << endl << redact(collscan->toString());
SolutionCacheData* scd = new SolutionCacheData();
scd->solnType = SolutionCacheData::COLLSCAN_SOLN;
collscan->cacheData.reset(scd);
out.push_back(std::move(collscan));
}
}
prepareExecution方法解析 QueryPlanner::plan(*canonicalQuery, plannerParams) 根据查询命令生成执行计划完成。
方案1:db.user.find({age:28}).sort({wages:1})命令中过滤器age是索引字段,生成执行计划IXSCAN(读取age索引范围内数据) > FETCH > SORT_KEY_GENERATOR > SORT。
方案2:db.user.find({age:28}).sort({wages:1})命令中排序wages是索引字段,生成执行计划IXSCAN(读取wages索引范围内数据) > FETCH。
方案1和方案2都先执行IXSCAN索引扫描,但是扫描的索引字段不一样。
其他条件不符合,没有生成其他执行计划。
solutions.size()==2,solutions有多个则生成multiPlanStage,将所有计划放进去。下一篇分析多个计划评分,选择评分最高的计划执行。