ARTICLE DETAIL

资讯详情

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

MongoDB 查询系统内部机制:从 CanonicalQuery 到 QuerySolution 的解析、优化与执行架构

MongoDB 查询系统内部机制:从 CanonicalQuery 到 QuerySolution 的解析、优化与执行架构 MongoDB 查询系统内部机制从 CanonicalQuery 到 QuerySolution 的解析、优化与执行架构【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本文基于 MongoDB 源码仓库中的 查询系统内部文档 及其关联的 QO 架构指南、逻辑模型文档系统梳理 MongoDB 查询系统Query System的职责边界、从命令到执行计划的完整数据流、核心数据模型CanonicalQuery、MatchExpression、QuerySolution与执行器架构PlanExecutor三大子类并深入 Query Shape、Query Stats 等附加特性。读完后你可以掌握一条查询从客户端命令到存储引擎的完整生命周期并能在源码中快速定位查询解析、规划与执行的关键实现。查询系统的职责边界查询系统负责三件事解释用户请求、找到满足请求的最优方式、计算最终结果引自 src/mongo/db/query/README.md。它主要通过find和aggregate两条命令对外暴露同时被大量关联命令复用读命令count、distinct、mapReduce后者自 v5.0 起已废弃推荐用 aggregate 替代——这一点在 QO 架构图 中也有标注写命令update、delete、findAndModify中的 filter 部分同样需要走查询系统解析与规划。这一多命令共享的设计在源码目录结构中可以直接印证src/mongo/db/query/ 下同时存在find_command.idl、count_command.idl、distinct_command.idl、getmore_command.idl等命令定义文件以及面向写路径的shard_filterer_factory_impl.*为分片写命令构造分片过滤条件等文件。查询系统内部按团队分工可进一步拆为QOQuery Optimization查询优化与QEQuery Execution查询执行两大块QO 系统的输出是一个获胜的QuerySolution它正是 QE 系统的输入原文档 Glossary 中的原话。整体架构从命令到执行计划README_QO.md 给出了一张高层数据流图完整呈现了各组件的衔接关系。以下为其原始 mermaid 流程图引自仓库文档从这张图可以读出两条主路径find / count / distinct 路径命令解析为CanonicalQuery其 filter 部分转化为MatchExpression经optimizeMatchExpression()启发式重写后进入Plan Enumerator枚举出候选QuerySolution再由 Classic Multiplanner、面向 SBE 的 Multiplanner 或基于成本的 Cost Based Ranker辅以 Cardinality Estimation 基数估计与 Cost Model 代价模型选出获胜计划。aggregate 路径命令先解析为PipelineDocumentSource列表由Pipeline::optimize()判断能否下推pushdown到 find——能下推则转化为CanonicalQuery走上面的 find 路径否则交给DocumentSourceExecution最终两条路径都汇聚到PlanExecutor再访问 Storage API。QO 团队维护的核心组件QO 架构指南 按职责列出了查询优化系统的组件清单每个组件在仓库中都有对应的文档或源码目录组件仓库位置说明解析Parsingcommands/query_cmd/README.md、STABLE_API_README.md命令级解析入口含稳定 API 约束逻辑模型Logical ModelsREADME_logical_models.mdCanonicalQuery及其子模型见下节MatchExpression 启发式重写matcher/README.mdfilter AST 上的重写规则Pipeline 启发式重写pipeline/README.md聚合阶段序列上的重写视图Viewsviews/README.md视图展开为聚合管道计划枚举Plan Enumerationquery/plan_enumerator/README.md枚举候选 QuerySolutionClassic 运行时规划exec/runtime_planners/classic_runtime_planner/README.mdClassic 引擎的运行时规划器ExplainREADME_explain.md计划解释与诊断输出计划缓存Plan Cachequery/plan_cache/README.md复用重复查询的历史计划集群规划Cluster Planning位于src/mongo/db/s/query/分片规划目录mongos 侧的跨分片规划此外该指南还列出了查询测试基础设施Golden 数据测试框架docs/golden_data_test_framework.md、QueryTester 测试库以及用于模糊测试与负载基准的 fuzzer 和性能测试工具仓库中可见 query_bm_fixture.h 等基准测试支撑代码。核心逻辑模型CanonicalQuery 及其组件逻辑模型logical model是对数据如何组织、组件如何关联的一种表示。一个查询的主要逻辑模型就是CanonicalQuery它自身又是一个容器把四个组件分别委托给四个子模型见 README_logical_models.md查询组件逻辑模型filterMatchExpressionprojectionProjectionsortSortPatterndistinctCanonicalDistinct查询规划的第一步以CanonicalQuery为输入逻辑模型的目标是通过去糖化desugaring、规范化normalization及其他重写把所有组件简化到最基础的形式。CanonicalQuery解析后的标准查询从 canonical_query.h 可以看到其实际结构CanonicalQuery构造函数接收一个CanonicalQueryParams其中包含表达式上下文ExpressionContext、解析后的 find 请求ParsedFindCommand或ParsedFindCommandParams二选一、DocumentSource管道以及isCountLike、isSearchQuery、aggWithNonEmptyPipeline等上下文标志。同一头文件还定义了两类查询形状编码QueryShapeString用于计划缓存的形状编码可对常量做移除或以参数标记替换PlanCacheCommandKey与indexFilters和planCacheClear命令配套的第二种形状编码用于查找应作用于该查询的索引过滤及应被清除的计划缓存条目。需要特别澄清 逻辑模型文档 中的一个概念ParsedFindCommand保存的是 filter、projection、sort 的原始形式而CanonicalQuery并不创建这些数据而是把它们的原始形式简化后存储。因此以下三条查询最终得到同一个CanonicalQuery因为单子节点的$and/$or会在规范化时被消解db.c.find({a: 1}) db.c.find({$and: [{a: 4}]}) db.c.find({$or: [{a: 4}]})若解析后无法生成CanonicalQuery例如聚合管道超出了可转化为 find 的范畴查询会跳过优化直接进入查询执行层。MatchExpressionfilter 的抽象语法树CanonicalQuery持有的 filter 是一棵名为MatchExpression的抽象语法树AST。MatchExpression是所有节点的抽象基类所有可能的节点类型由MatchType枚举穷举每种类型对应一个子类例如EqualityMatchExpression对应MatchType::EQ与$eq算子是 0 子节点的LeafMatchExpressionAndMatchExpression对应MatchType::AND与$and算子拥有 N 个子节点N 为$and的合取项数。以这条查询为例db.c.find({ $or: [ {a: 1}, {a: 2}, {$and: [{b: {$gte: 0}}, {c: {$lt: 5}}]} ] })解析阶段MatchExpressionParser::parse()实现在 src/mongo/db/matcher/ 目录把 filter 拆成独立的BSONElement逐个解析重建为 ASTOrMatchExpression ├── EqualityMatchExpression a 1 ├── EqualityMatchExpression a 2 └── AndMatchExpression ├── GTEMatchExpression b 0 └── LTMatchExpression c 5CanonicalQuery随后调用MatchExpression::normalize()启动简化。规范化的目标是把 AST 化到最简形式这样做有两个直接收益由此生成的QuerySolution尽可能简单计划缓存能把逻辑等价的查询识别为等价从而在初始查询写法不同的情况下也能复用缓存计划。上例中两个同字段的$eq析取项可应用$or→$in重写规则规范化后 AST 变为OrMatchExpression ├── InMatchExpression a ∈ [1, 2] └── AndMatchExpression ├── GTEMatchExpression b 0 └── LTMatchExpression c 5这一简化后的 AST 可能源自无穷多条写法更复杂的查询——这正是启发式重写详见 matcher/README.md的典型价值。Projection、SortPattern 与 CanonicalDistinctProjection同样是 AST。原始BSONObj经parseAndAnalyze()转为投影树后optimizeProjection()使用可变更访问者ProjectionASTMutableVisitor递归遍历preVisit/inVisit/postVisit各节点可原地简化。几个要点MQL 投影必须是包含型字段标1或排除型字段标0之一不能混用唯一例外是_id默认包含嵌套字段、$、$elemMatch、$slice以及表达式投影如{$add: [$a.c, 1]}会展开为更复杂的递归子树表达式投影是包含/排除之外的第三类增补型投影投影优化还会利用布尔恒等化简解除虚假依赖如{x: {$and: [false, $b]}}中x对b的依赖会被释放。SortPattern是SortPatternPart的向量每个 part 记录一个字段路径与排序方向例如db.c.find({}).sort({a: 1, b.c: -1}) → SortPattern: [ {fieldPatha, isAscendingtrue}, {fieldPathb.c, isAscendingfalse}, ]例外是$meta排序如textScore它忽略字段名而按元数据作为隐式顶层字段排序。CanonicalDistinct在distinct()查询或聚合管道命中DISTINCT_SCAN时使用它只是 distinct 键的容器——filter 仍归CanonicalQuery持有。例如db.c.distinct(x, {x: {$gt: 0}})中distinct 键x由CanonicalDistinct保存而x 0的MatchExpression由CanonicalQuery保存对应 canonical_distinct.h 等实现。去糖化DesugaringMQL 中存在大量语法糖。例如隐式语法糖显式形式{field: value}{field: {$eq: value}}{a: {$gte: 0}, b: {$lt: 5}}{$and: [{a: {$gte: 0}}, {b: {$lt: 5}}]}去糖化把简写转换回显式形式让后续优化只需处理单一代码路径例如统一处理显式$eq不必再单独处理隐式$eq。执行端PlanExecutor 与 QuerySolutionQuerySolution一棵执行计划树QuerySolution是QuerySolutionNode构成的树表示一个查询的一种可能执行计划。多种操作节点继承自QuerySolutionNode例如CollectionScanNode、FetchNode、IndexScanNode、OrNode等。通常意义上一个获胜的QuerySolution是 QO 系统的输出、QE 系统的输入。PlanExecutor计划执行器抽象PlanExecutor是把一棵QuerySolution的阶段树摇起来执行的抽象类型定义于 plan_executor.h。它有三个主要子类原文档 Glossary 原文PlanExecutorImpl—— 执行 find 阶段实现位于 plan_executor_impl.hPlanExecutorPipeline—— 执行聚合aggregation阶段其实现归属于 db/pipeline 模块PlanExecutorSBE—— 执行 SBE 计划实现位于 plan_executor_sbe.h。从源码结构看src/mongo/db/query/ 目录中还存在plan_executor_factory.*执行器工厂、plan_explainer.*系列与plan_explainer_impl/express/sbe一一对应的计划解释器以及engine_selection.*、get_executor_deferred_engine_choice*等文件印证了运行时在 Classic 与 SBE 引擎之间做延迟选择的机制plan_ranker.*、plan_ranking/、sbe_plan_ranker.h等则对应架构图中Cost Based Ranker 与基数估计、代价模型协同选优的环节。Pipeline、DocumentSource 与 PushdownPipelineDocumentSource的列表承载查询的一部分优化工作DocumentSource表示聚合管道中的一个阶段与用户定义管道中的阶段不必然一一对应重写、下推都会改变阶段数量与形态Pushdown把管道中的某个 aggregate 阶段转化为 find 阶段——这正是架构图里Can pushdown to find?分支的来源也是query_planner_pipeline_pushdown_test.cpp等测试所验证的行为。LiteParsedPipeline 与 ExpressionContextLiteParsedPipeline通过半解析semi-parse构建的极简管道模型只解析到足以把各阶段拆分的程度——它既没有校验输入良构性也没有解析表达式或阶段细节参数。它服务于在决定是否构建完整模型之前先检查请求的场景ExpressionContext保存整个查询生命周期中可能有用、但与其他操作无关的状态包括 collation、时区数据库、若干随机布尔与运行状态等。从 canonical_query.h 可见它以ExpressionContext指针的形式作为CanonicalQueryParams的一部分贯穿规划过程。附加查询特性主 README 列出了一组附加查询特性每项在仓库中都有独立的文档与实现目录Query Shape查询形状query_shape/README.md 定义了查询形状命令的变形版本其中字面值被替换为规范 BSON 类型占位符。同一命令的不同实例一旦抽象掉字面值后相同就具有相同的形状db.example.findOne({x: 24}); // 同形状 db.example.findOne({x: 53}); // 同形状而不同字面值同形状不同 BSON类型则不同形状{x: 53}与{x: string}形状不同。该概念覆盖大部分 CRUD 命令读命令distinct、count、aggregate与写命令update、delete、insert都有各自的形状。update/delete 像读命令一样对 filter 形状化并额外纳入写专属组件如 delete 的multi标志、update 的修改语句与multi/upsert标志insert 没有谓词其形状即集合名加恒定为?array?object的documents字段。形状由一组形状组件类决定每个命令哪些字段参与定形类层次为CmdSpecificShapeComponents→FindCmdShapeComponents、AggCmdShapeComponents、CountCmdShapeComponents、DistinctCmdShapeComponents、DeleteCmdShapeComponents、InsertCmdShapeComponents、UpdateCmdShapeComponents、LetShapeComponent等一一对应 query_shape/ 目录下的find_cmd_shape.h、agg_cmd_shape.h等文件。形状化有三种序列化选项SerializationOptionskUnchanged字面值原样保留{x: 5, y: hello}→{x: 5, y: hello}kToDebugTypeString输出人类可读类型串→{x: ?number, y: ?string}kToRepresentativeParseableValue每种类型序列化为一个固定代表值且必须可解析→{x: 1, y: ?}为保证可解析性做了特殊处理例如正则{x: {$regex: ^p.*}}会序列化为{x: {$regex: \\?}}因为?本身不是合法正则。计算形状哈希时采用第三种选项——同类型的所有字面量收敛为同一值从而产生同一哈希实现shapify分组。Query Stats查询统计query_stats/README.md 描述了运行时查询统计基础设施。核心是QueryStatsStore一个分片哈希表以查询统计键Query Stats Key的哈希为键聚合每次成功执行后的指标。查询统计键比查询形状粒度更细——例如db.example.find({x: 55}).batchSize(2)与batchSize(3)形状相同但batchSize作为额外维度会使其落入不同的统计条目。关键配置项服务端参数参数含义与默认值internalQueryStatsCacheSize统计存储上限可写 4MB 或 1% 形式默认机器总内存的 1%存储为分片 LRU 缓存超限按 LRU 驱逐internalQueryStatsRateLimit基于窗口的限流每秒最大记录数整数默认 0禁用收集-1 表示不限流internalQueryStatsSampleRate基于采样的限流0.0–1.0 的小数表示被记录查询的比例默认 0两者同时非零时采样策略优先internalQueryStatsWriteCmdSampleRate整数 0/1控制写命令是否参与统计仅在上述限流已启用时生效logComponentVerbosity.queryStats控制日志行为1 级记录带hmac-sha-256的$queryStats调用密钥脱敏3 级记录其全部结果统计的采集流程规划阶段调用registerRequest按形状加各维度生成键并挂在opDebug上支持getMore的命令同时挂在游标上以便跨批次累计执行完成后writeQueryStats更新或新建条目。写命令不走游标且update/delete按语句逐个注册每条语句有独立q谓词与独立计划insert则按命令整体注册。统计通过$queryStats聚合阶段从 admin 库读取必须位于管道首位可选transformIdentifiers仅支持hmac-sha-256算法需hmacKey对字段/集合/库名做单向令牌化实现能识别两条查询用了同一标识符但永不泄露标识符本身。输出文档包含key、keyHash、queryShapeHash可与持久化查询设置交叉引用、asOf及metrics总执行时长、CPU 时间、keysExamined/docsExamined/bytesRead、规划器指标fromPlanCache/planningTimeMicros、写指标nMatched/nDeleted等完整字段表见 query_stats/README.md。权限上由queryStatsRead与queryStatsReadTransformed两个动作控制。serverStatus.metrics.queryStats还会暴露numEvicted、numRateLimitedRequests、queryStatsStoreSizeEstimateBytes等运行计数。其他特性Change Streams查询统计对 change stream 有特殊行为——每次getMore被当作独立查询记录而非累计在游标上一次性记录Field-Level Encryptionfle/目录位于 query 目录下承载字段级加密与可查询加密Queryable Encryption相关查询逻辑Geo地理查询支持Searchsearch/README.md 是搜索特性的入口页其中 search 是 mongot 管道阶段$search、$vectorSearch、$searchMeta的统称涵盖技术概览、基于 mongot-mock 与真实 mongot 二进制的三类测试方式以及 Hybrid Search$rankFusion基于 RRF、$scoreFusion基于自定义得分组合与scoreDetails四阶段构建机制Timeseriestimeseries/README.md 讲述时序集合的查询翻译与优化9.0 前的viewful时序集合中用户命名空间是一个视图find/count/distinct/aggregate会被改写为对底层system.buckets.namespace集合、并在管道首部插入$_internalUnpackBucket阶段的聚合请求9.0 起为单一命名空间的 viewless 时序集合不再有桶集合与视图解析步骤。术语表Glossary以下术语表完整继承自 src/mongo/db/query/README.md 的 Glossary 章节中文为补充解释术语定义Aggregation聚合运行 aggregate 阶段的子系统BSON类 JSON 文档的二进制序列化格式MongoDB 核心数据表示所用CanonicalQuery查询的 BSON 标准形式承载解析后的 query、projection、sort 三部分其中 filter 部分被解析为MatchExpressionDocumentSource表示聚合管道中的一个阶段与用户定义管道中的阶段不必然一一对应ExpressionContext保存整个查询生命周期内可能有用、与其他操作无关的状态对象collation、时区数据库、各种随机布尔与状态等Find运行 find 阶段与已下推pushed-down聚合阶段的子系统IDL接口定义语言用 YAML 文件生成 C 代码如命令定义*.idl文件LiteParsedPipeline通过半解析构建的极简聚合管道模型只拆分出所涉阶段既未验证输入良构也未解析表达式或详细参数用于在构建完整模型前检查请求MatchExpression查询 filter 部分解析得到的抽象语法树ASTMQLMongoDB Query LanguagePlan Cache计划缓存存储历史生成的查询计划使重复查询免于重新生成与评分候选计划PlanExecutor通过驱动一棵QuerySolution的阶段树执行计划来执行计划的抽象类型三个主要子类PlanExecutorImplfind 阶段、PlanExecutorPipeline聚合阶段、PlanExecutorSBESBE 计划PipelineDocumentSource列表处理优化的一部分工作Pushdown下推把管道中的 aggregate 阶段转化为 find 阶段QuerySolutionQuerySolutionNode的树结构表示一个查询的一种可能执行计划CollectionScanNode、FetchNode、IndexScanNode、OrNode等操作节点继承自QuerySolutionNode一个获胜的QuerySolution通常就是 QO 系统的输出与 QE 系统的输入延伸阅读关键文档与源码索引内容路径查询系统总览本文主体文档src/mongo/db/query/README.mdQO 架构指南含高层数据流图src/mongo/db/query/README_QO.md逻辑模型详解src/mongo/db/query/README_logical_models.mdExplain 机制src/mongo/db/query/README_explain.md查询功能特性开关src/mongo/db/query/README_query_feature_flags.mdQuery Shape vs Query Stats 辨析src/mongo/db/query/README_query_shape_disambiguation.mdCanonicalQuery定义src/mongo/db/query/canonical_query.hPlanExecutor抽象src/mongo/db/query/plan_executor.h计划缓存src/mongo/db/query/plan_cache/README.md计划枚举src/mongo/db/query/plan_enumerator/README.mdQueryTester 测试库src/mongo/db/query/query_tester/README.mdGolden 数据测试框架docs/golden_data_test_framework.md以上路径均可在当前仓库中直接查阅配合本文的架构脉络可以完整走读一条查询从命令解析、逻辑模型构建、计划枚举与评分到PlanExecutor执行与结果返回的全链路实现。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表