ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MongoDB DISTINCT_SCAN 索引资格判定全解析:来自 query_golden 黄金测试的 17 个场景实证

MongoDB DISTINCT_SCAN 索引资格判定全解析:来自 query_golden 黄金测试的 17 个场景实证 MongoDB DISTINCT_SCAN 索引资格判定全解析来自 query_golden 黄金测试的 17 个场景实证【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读DISTINCT_SCAN是 MongoDB 查询规划器中用于高效回答distinct命令与去重类聚合的高性能访问路径它允许直接沿着索引键顺序跳过重复值。本文以 MongoDB 官方仓库jstests/query_golden/expected_output/featureFlagSbeFull/distinct_index_eligibility.md这份黄金测试预期输出为骨架逐场景剖析索引在何种条件下才能被判定为符合 DISTINCT_SCAN 资格并结合src/mongo/db/query/distinct_access.cpp中的isIndexSuitableForDistinct源码给出底层依据。读完本文你将掌握 multikey、sparse、wildcard、复合索引与flip/strict语义如何共同决定一条去重查询最终走 DISTINCT_SCAN 还是退化为 COLLSCAN。一、背景DISTINCT_SCAN 与黄金测试DISTINCT_SCAN是经典查询执行引擎classic engine中的一种索引扫描阶段出现在explain()的 winning plan 中stage: DISTINCT_SCAN。其核心思路是当需要为某个字段求去重结果时如果该字段是索引的最左前缀或可被索引覆盖就可以按索引序扫描遇到与前一个索引键值相同的记录直接跳过从而省去排序与哈希去重也不需要回表取文档配合PROJECTION_COVERED实现全覆盖。在聚合场景中$group配合$top/$last、以及$sort$group的形态会被转换为canonicalDistinct进而有机会使用DISTINCT_SCAN最终体现在 explain 的$groupByDistinctScan阶段上见下文场景的 classic 引擎 explain。1.1 测试文件与预期输出本文依据的黄金测试驱动文件为 jstests/query_golden/distinct_index_eligibility_md.js其文件头注释明确说明Tests that we generate DISTINCT_SCANs just for eligible indexes.即只对符合资格的索引生成 DISTINCT_SCAN。测试打有两个标签featureFlagShardFilteringDistinctScan依赖该功能开关requires_fcv_82要求 feature compatibility version 为 8.2 及以上。该功能开关定义在 src/mongo/db/query/query_feature_flags.idlfeatureFlagShardFilteringDistinctScan: description: Feature flag to support shard filtering in distinct scan optimization cpp_varname: gFeatureFlagShardFilteringDistinctScan default: true version: 8.2 fcv_gated: true默认开启、面向 MongoDB 8.2 引入且受 FCV 门控。1.2 黄金测试的运行与输出机制这类测试属于 query_golden 系列。测试用例通过 jstests/libs/query/golden_test_utils.js 中的辅助函数生成 Markdown 形式的预期输出outputAggregationPlanAndResults(coll, pipeline)运行聚合管道输出 Pipeline、Results、集合全部索引Total indexes on the collection与 Summarized explainoutputDistinctPlanAndResults(coll, key, filter)运行distinct命令输出 Distinct on/结果/Summarized explain输出中的Execution Engine: sbe/Execution Engine: classic一行来自getEngine(explain)用于区分 SBEsub-query execution engine与经典引擎的执行路径。运行方式参照 jstests/query_golden/README.plan_stability.md 中给出的 resmoke 套件写法例如query_golden_classic套件buildscripts/resmoke.py run --suitesquery_golden_classic jstests/query_golden/distinct_index_eligibility_md.js注意本测试的预期输出存放在featureFlagSbeFull子目录下表示该输出是在 SBE 功能全集开启的前提下记录下来的当 SBE 引擎不可用或功能集不同时部分场景的 explain 会以classic引擎输出文档中明确标注了每个场景的 Execution Engine。二、核心判定逻辑isIndexSuitableForDistinct源码解读黄金测试中的每一个场景最终都归结到同一个函数isIndexSuitableForDistinct定义于 src/mongo/db/query/distinct_access.cpp。其签名与关键分支如下bool isIndexSuitableForDistinct(const BSONObj keyPattern, bool multikey, const MultikeyPaths multikeyPaths, bool sparse, projection_executor::ProjectionExecutor* wildcardProj, std::string_view field, const BSONObj filter, bool flipDistinctScanDirection, bool strictDistinctOnly, bool unwindsArrays, const OrderedPathSet projectionFields, bool hasSort) { // 未要求 strict 时允许忽略缺失字段 const bool mayIgnoreMissing !strictDistinctOnly; // 未要求 strict 或在 distinct 字段上已知会展开数组时允许把数组元素当作独立键 const bool mayUnwindArrays !strictDistinctOnly || unwindsArrays; if (keyPattern.hasField(field)) { if (flipDistinctScanDirection multikey) { return false; // 场景flip multikey } if (!mayIgnoreMissing sparse) { return false; // 场景strict sparse } if (!mayUnwindArrays isAnyComponentOfPathOrProjectionMultikey( keyPattern, multikey, multikeyPaths, field, projectionFields, hasSort)) { return false; // 场景strict multikey on distinct field } return true; } else if (wildcardProj !filter.isEmpty()) { // 通配索引字段被 $** 投影捕获且存在谓词时才有资格 if (projection_executor_utils::applyProjectionToOneField(wildcardProj, field)) { return true; } } return false; }下面把黄金测试中的 17 个场景逐一对号入座。测试集合统一命名为test.distinct_index_eligibility_md每个场景都会drop()集合、重新插入数据并创建索引。三、场景组一Distinct 字段在索引键模式中keyPattern.hasField(field)本节所有场景都满足distinct 字段是索引键的一部分这一前提资格判定全部进入上述函数第一个分支。3.1 flip 与 multikey 的相互作用flip 指flipDistinctScanDirection——当$group带$last累加器时查询优化器可能通过GroupFromFirstTransformation变换将扫描方向反转forward→backward以便用$first等价实现$last。反转方向对普通非 multikey索引是安全的但对数组字段却会改变比较语义。场景 Aflip multikey 无 DISTINCT_SCAN数据与索引插入{a: [1, 2, 3], b: 5}创建复合索引a_1_b_1此时a是 multikey 路径。Pipeline[ { $sort : { a : 1, b : 1 } }, { $group : { _id : $a, accum : { $last : $b } } } ]Results{ _id : [ 1, 2, 3 ], accum : 5 }a是数组_id保留整个数组作为分组键。索引集合[ _id_, a_1_b_1 ]。SBE 引擎 explain 的 winning plan{ stage : GROUP }, { nss : test.distinct_index_eligibility_md, stage : FETCH }, { direction : forward, indexBounds : { a : [ [MinKey, MaxKey] ], b : [ [MinKey, MaxKey] ] }, indexName : a_1_b_1, isMultiKey : true, isPartial : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1 }, multiKeyPaths : { a : [ a ], b : [ ] }, nss : test.distinct_index_eligibility_md, stage : IXSCAN }执行计划为GROUP→FETCH→IXSCAN并没有DISTINCT_SCAN。注意 explain 中isMultiKey: true与multiKeyPaths.a [a]这正是被源码注释点名的场景。源码注释的解释非常关键distinct_access.cppThis CanonicalDistinct was generated as a result of transforming a $group with $last accumulators using the GroupFromFirstTransformation. We cannot use a DISTINCT_SCAN if $last is being applied to an indexed field which is multikey... because changing the sort direction also changes the comparison semantics for arrays, which means that flipping the scan may not exactly flip the order that we see documents in. In the case of using DISTINCT_SCAN for $group, that would mean that $first of the flipped scan may not be the same document as $last from the users requested sort order.即数组元素在索引中是逐个插入的反转扫描后第一个遇到的文档未必是用户请求排序下的最后一个文档$first无法安全替代$last因此必须放弃 DISTINCT_SCAN。场景 Bflip !multikey DISTINCT_SCAN数据与索引插入{a: 1, b: 5}a为标量创建a_1_b_1。同样的$sort $group/$lastpipelineResults 变为{ _id : 1, accum : 5 }。classic 引擎 explainExecution Engine: classic{ stages : [ { $cursor : { rejectedPlans : [ ], winningPlan : [ { stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1, b : 1 } }, { direction : backward, indexBounds : { a : [ [MaxKey, MinKey] ], b : [ [MaxKey, MinKey] ] }, indexName : a_1_b_1, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1 }, multiKeyPaths : { a : [ ], b : [ ] }, stage : DISTINCT_SCAN } ] } }, { $groupByDistinctScan : { newRoot : { _id : $a, accum : $b } } } ] }两个细节值得注意计划中direction: backwardindexBounds呈现为[MaxKey, MinKey]——这正是 flip 反转方向的直观证据管道级$group被改写为$groupByDistinctScan阶段newRoot中_id: $a、accum: $b直接由索引提供配合PROJECTION_COVERED全程不回表isFetching: false。同样的 pipeline、同样的索引仅仅因为a从数组变成标量执行计划就从IXSCAN FETCH GROUP变成了全覆盖的 DISTINCT_SCAN性能差异显著。3.2 strict 与 multikey 的相互作用strictDistinctOnlystrict表示调用方要求严格去重结果中每个键必须精确对应真实文档的字段值且不允许把数组展开成多个键也不允许忽略缺失字段。本组场景使用$top累加器而非$last因此不发生 flip。场景 C!flip strict distinct 字段上 multikey 无 DISTINCT_SCAN数据与索引插入{a: [1, 2, 3], b: 5}创建a_1_b_1。Pipeline[ { $group : { _id : $a, accum : { $top : { output : $b, sortBy : { a : 1, b : 1 } } } } } ]Results{ _id : [ 1, 2, 3 ], accum : 5 }。SBE 引擎 winning plan 退化为{ stage : GROUP }, { direction : forward, filter : { }, nss : test.distinct_index_eligibility_md, stage : COLLSCAN }原因正是源码中的第三条拒绝分支distinct_access.cppstrict 模式不允许预展开数组而数组索引本质上每个元素单独一条索引项任何基于索引的扫描都相当于已经展开了所有数组这与 strict 语义冲突故整体降级为COLLSCAN。场景 D!flip strict 仅在非 distinct 字段上 multikey DISTINCT_SCAN数据与索引插入{a: 1, b: [1, 2, 3]}b是数组创建a_1_b_1。同样的$toppipelineResults 变为{ _id : 1, accum : [ 1, 2, 3 ] }。classic 引擎 explain{ $cursor : { rejectedPlans : [ ], winningPlan : [ { direction : forward, indexBounds : { a : [ [MinKey, MaxKey] ], b : [ [MinKey, MaxKey] ] }, indexName : a_1_b_1, isFetching : true, isMultiKey : true, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1 }, multiKeyPaths : { a : [ ], b : [ b ] }, stage : DISTINCT_SCAN } ] }, { $groupByDistinctScan : { newRoot : { _id : $a, accum : $b } } } ]这里的判定依据是isAnyComponentOfPathOrProjectionMultikey(...)虽然索引整体是 multikeyisMultiKey: true但 multikey 只发生在b路径multiKeyPaths.b [b]distinct 字段a的路径是干净的multiKeyPaths.a []因此可以安全使用 DISTINCT_SCAN由于b是多键字段需要回表取完整数组值故isFetching: true区别于场景 B 的isFetching: false。场景 E!flip !strict distinct 字段非 multikey DISTINCT_SCAN数据与索引插入{a: 1, b: [1, 2, 3]}创建a_1_b_1。这次直接对a执行 distinct 命令带过滤条件{a: {$gt: 3}}Distinct on a, with filter: { a : { $gt : 3 } } Distinct results: [ ]classic 引擎 explain{ stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1 } }, { direction : forward, indexBounds : { a : [ (3.0, inf] ], b : [ [MinKey, MaxKey] ] }, indexName : a_1_b_1, isFetching : false, isMultiKey : true, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1 }, multiKeyPaths : { a : [ ], b : [ b ] }, stage : DISTINCT_SCAN }虽然b是 multikey但 distinct 命令本身不要求每个键对应真实文档的 strict 语义strictDistinctOnly falsemayUnwindArrays为 true因此b的数组属性不再构成障碍。索引边界a: [(3.0, inf]]表明$gt: 3被下推到了索引扫描上且全程 coveredisFetching: false。查询结果为空是因为集合中a的值只有 1不满足 3。3.3 strict 与 sparse 索引的相互作用sparse 索引只索引包含该字段的文档。strict 去重不允许忽略缺失字段因此 strict sparse 天然冲突而非 strict 的 distinct 命令允许忽略缺失字段可以放心使用 sparse 索引。场景 F/G/Hstrict含各种形态 sparse 索引 无 DISTINCT_SCAN数据与索引插入{b: 5}文档中根本没有a字段创建 sparse 索引{a: 1}。三种 pipeline 全部拒绝 DISTINCT_SCANF[ { $group : { _id : $a } } ]→ Results{ _id : null }SBE 计划为GROUP → COLLSCANG[ { $group : { _id : $a, accum : { $last : $b } } } ]→ Results{ _id : null, accum : 5 }SBE 计划为GROUP → COLLSCANH[ { $sort : { a : 1 } }, { $group : { _id : $a } } ]→ Results{ _id : null }SBE 计划为GROUP → SORT(simple, memLimit 104857600) → PROJECTION_SIMPLE → COLLSCAN。以场景 H 的 SBE explain 为例{ stage : GROUP }, { memLimit : 104857600, sortPattern : { a : 1 }, stage : SORT, type : simple }, { stage : PROJECTION_SIMPLE, transformBy : { _id : false, a : true } }, { direction : forward, filter : { }, nss : test.distinct_index_eligibility_md, stage : COLLSCAN }三个场景的 winning plan 中都出现filter : { }的COLLSCAN。这正是源码中if (!mayIgnoreMissing sparse) return false;这一分支的直接体现strict 模式下mayIgnoreMissing false而 sparse 索引天然漏掉不含该字段的文档若用它做 DISTINCT_SCAN 会错误地丢失_id: null这条结果。场景 I/J/Kstrict sparse 存在备用复合索引 在复合索引上 DISTINCT_SCAN在上一组集合含{b: 5}文档基础上继续创建非 sparse 的复合索引{a: 1, b: 1}使集合索引变为[ _id_, a_1, a_1_b_1 ]。此时三种 strict pipeline 全部转为在a_1_b_1上走 DISTINCT_SCANI[ { $group : { _id : $a } } ]→ Results{ _id : null }J[ { $group : { _id : $a, accum : { $last : $b } } } ]→ Results{ _id : null, accum : 5 }K[ { $sort : { a : 1 } }, { $group : { _id : $a } } ]→ Results{ _id : null }。场景 I 的 classic explain 展示了 DISTINCT_SCAN $groupByDistinctScan的完整形态{ $cursor : { rejectedPlans : [ ], winningPlan : [ { stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1 } }, { direction : forward, indexBounds : { a : [ [MinKey, MaxKey] ], b : [ [MinKey, MaxKey] ] }, indexName : a_1_b_1, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1 }, multiKeyPaths : { a : [ ], b : [ ] }, stage : DISTINCT_SCAN } ] }, { $groupByDistinctScan : { newRoot : { _id : $a } } } }注意它选择了a_1_b_1而非 sparse 的a_1因为存在满足 strict 语义的替代索引时优化器会优先把资格赋予那个能正确覆盖全部文档的索引。场景 J 的 explain 与场景 I 结构相同只是PROJECTION_COVERED的transformBy包含b、$groupByDistinctScan.newRoot为{_id: $a, accum: $b}且direction: backward$last需要反转扫描indexBounds为[MaxKey, MinKey]场景 K 则为transformBy: {_id: 0, a: 1} forward 方向。场景 Lstrictsort accum sparse 索引 无 DISTINCT_SCAN数据与索引dropIndex({a: 1})、dropIndex({a: 1, b: 1})后重建 sparse 复合索引{a: 1, b: 1}{sparse: true}集合索引为[ _id_, a_1_b_1 ]。Pipeline 为$sort {a:1, b:1}$group {_id: $a, accum: {$last: $b}}。Results{ _id : null, accum : 5 }。SBE 计划中排序、投影、分组全部在COLLSCAN之上显式完成{ stage : GROUP }, { memLimit : 104857600, sortPattern : { a : 1, b : 1 }, stage : SORT, type : simple }, { stage : PROJECTION_SIMPLE, transformBy : { _id : false, a : true, b : true } }, { direction : forward, filter : { }, nss : test.distinct_index_eligibility_md, stage : COLLSCAN }场景 Mstrictsort accum sparse 备用三字段复合索引 DISTINCT_SCAN在场景 L 基础上再创建{a: 1, b: 1, c: 1}集合索引变为[ _id_, a_1_b_1, a_1_b_1_c_1 ]。同一 pipeline 的 classic explain 立刻转为使用三字段索引{ stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1, b : 1 } }, { direction : backward, indexBounds : { a : [ [MaxKey, MinKey] ], b : [ [MaxKey, MinKey] ], c : [ [MaxKey, MinKey] ] }, indexName : a_1_b_1_c_1, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { a : 1, b : 1, c : 1 }, multiKeyPaths : { a : [ ], b : [ ], c : [ ] }, stage : DISTINCT_SCAN }, { $groupByDistinctScan : { newRoot : { _id : $a, accum : $b } } }多余的后缀字段c并不妨碍资格判定——只要 distinct 字段a与累加器字段b都被非 sparse 索引覆盖即可。场景 N!strict sparse 索引 DISTINCT_SCAN数据与索引插入{b: 5}创建 sparse 索引{a: 1}直接执行distinct(a)filter 为空。Results 为[ ]集合中没有a字段sparse 索引为空。classic explain 显示 distinct 命令可以放心使用 sparse 索引{ stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1 } }, { direction : forward, indexBounds : { a : [ [MinKey, MaxKey] ] }, indexName : a_1, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : false, isSparse : true, isUnique : false, keyPattern : { a : 1 }, multiKeyPaths : { a : [ ] }, stage : DISTINCT_SCAN }这里的核心区别是distinct 命令的语义允许忽略缺失字段mayIgnoreMissing true与 sparse 索引的行为完全一致因此isSparse: true不再是被拒绝的理由。四、场景组二Distinct 字段不在索引键模式中通配索引当 distinct 字段不在普通索引键中时唯一有机会的索引类型是通配符索引wildcard index键模式$**。此时判定进入源码的第二个分支distinct_access.cpp必须满足存在非空过滤条件且该字段被通配索引投影捕获。场景 Owildcard covered projection DISTINCT_SCAN数据与索引插入[{a: 1}, {a: 2}, {a: 3}, {b: 5}, {b: 7}]创建通配索引{$**: 1}。对a执行 distinctfilter 为{a: {$lt: 3}}。Results[ 1, 2 ]3是字符串不满足数值小于 3。explain{ stage : PROJECTION_COVERED, transformBy : { _id : 0, a : 1 } }, { direction : forward, indexBounds : { $_path : [ [\a\, \a\] ], a : [ [-inf, 3.0) ] }, indexName : $**_1, isFetching : false, isMultiKey : false, isPartial : false, isShardFiltering : false, isSparse : false, isUnique : false, keyPattern : { $_path : 1, a : 1 }, multiKeyPaths : { $_path : [ ], a : [ ] }, stage : DISTINCT_SCAN }通配索引的 explain 有几个独有特征keyPattern是{ $_path : 1, a : 1 }内部展开成$_path虚拟字段 具体字段indexBounds.$_path被约束为[a, a]即只扫描路径为a的索引项过滤条件$lt: 3被下推为a: [[-inf, 3.0)]。由于查询谓词让索引完全覆盖了 distinct 字段isFetching: false走的是全覆盖 DISTINCT_SCAN。场景 P!wildcard 无 DISTINCT_SCAN同样的数据但创建普通索引{b: 1}而非通配索引。对adistinct、filter{a: {$lt: 3}}Results 仍为[ 1, 2 ]但 explain 直接变成全表扫描{ direction : forward, filter : { a : { $lt : 3 } }, nss : test.distinct_index_eligibility_md, stage : COLLSCAN }普通索引b_1的键模式不含akeyPattern.hasField(a)为 falsewildcardProj又为空两个分支都不满足函数返回 false别无选择只能 COLLSCAN。场景 Qwildcard !covered projection 无 DISTINCT_SCAN数据与索引插入[{a: 1}, {a: 2}, {a: 3}, {a: 4, b: 3}, {b: 7}]创建通配索引{$**: 1}。对adistinct但 filter 落在另一个字段上{b: {$lt: 5}}。Results[ 4 ]。explain{ nss : test.distinct_index_eligibility_md, stage : FETCH }, { direction : forward, indexBounds : { $_path : [ [\b\, \b\] ], b : [ [-inf, 5.0) ] }, indexName : $**_1, isMultiKey : false, isPartial : false, isSparse : true, isUnique : false, keyPattern : { $_path : 1, b : 1 }, multiKeyPaths : { $_path : [ ], b : [ ] }, nss : test.distinct_index_eligibility_md, stage : IXSCAN }尽管通配索引命中了谓词字段b$_path边界为[b, b]但 distinct 的字段是a而a不在当前扫描覆盖的投影里——applyProjectionToOneField(wildcardProj, a)无法成立最终退化为FETCH IXSCAN而非 DISTINCT_SCAN。这也呼应了源码注释中的两条说明没有谓词的 distinct 不能用通配索引带谓词的 distinct 也只有在通配索引能覆盖 distinct 字段时才可用。五、判定规则速查表与实战启示综合黄金测试 17 个场景与isIndexSuitableForDistinct源码可以将 DISTINCT_SCAN 的资格判定归纳为下表条件组合结论对应场景关键源码分支flip 扫描方向 distinct 字段 multikey拒绝AflipDistinctScanDirection multikeyflip 非 multikey允许B通过strict distinct 字段 multikey拒绝C!mayUnwindArrays isAnyComponentOfPathOrProjectionMultikey(...)strict 仅非 distinct 字段 multikey允许D通过multikeyPaths 不含 distinct 字段strict sparse 索引拒绝F/G/H/L!mayIgnoreMissing sparsestrict sparse 备用复合索引允许走复合索引I/J/K/M通过替代索引非 sparse 且覆盖字段非 strictdistinct 命令 sparse允许NmayIgnoreMissing truedistinct 字段不在键中 通配索引 谓词覆盖该字段允许OwildcardProj !filter.isEmpty() applyProjectionToOneField(...)非通配索引 / 通配但不覆盖 distinct 字段拒绝P/Q两个分支均不满足从这些场景可以提炼出几条对实际调优有直接价值的经验数组字段是 DISTINCT_SCAN 的头号杀手只要 distinct 字段本身是 multikey无论flip还是strict都会否决 DISTINCT_SCAN。若业务上确实需要对数组字段去重很难指望走 DISTINCT_SCAN。multikey 是逐路径的复合索引中只要 distinct 字段所在路径不是 multikey即使其他键是数组DISTINCT_SCAN 依然可用场景 D 的isFetching: true是代价信号。sparse 索引只服务于可忽略缺失的语义distinct命令可用 sparse 索引而 strict 语义的$group不能除非同时存在一个非 sparse 的替代索引——优化器会择优选择覆盖完整数据集的索引。通配索引要谓词即所查distinct 字段必须被查询谓词命中且被通配索引投影覆盖否则只能 COLLSCAN。识别 DISTINCT_SCAN 的 explain 信号stage: DISTINCT_SCAN、伴随PROJECTION_COVEREDisFetching: false时为全覆盖、聚合场景出现$groupByDistinctScan阶段、flip 场景下direction反转为backward且边界为[MaxKey, MinKey]。如需进一步验证或扩展可直接在仓库中运行对应的黄金测试驱动文件 jstests/query_golden/distinct_index_eligibility_md.js通过buildscripts/resmoke.py run --suitesquery_golden_classic ...并对照其预期输出目录 jstests/query_golden/expected_output/featureFlagSbeFull/distinct_index_eligibility.md 与配套的 distinct 系列测试如 distinct_scan_md.js、unwind_group_to_distinct_scan_multiplanning_md.js做横向对比资格判定的最终裁决逻辑始终集中在 src/mongo/db/query/distinct_access.cpp 的isIndexSuitableForDistinct及其调用方constructCoveredDistinctScan见 distinct_access.cpp 与 query_planner.cpp。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表