七大主流数据库查询执行流程详解:从 MongoDB 到 NebulaGraph
理解一个数据库的查询执行流程,是掌握其性能特性和选型依据的关键。不同数据库面向的场景不同,其查询执行的模块划分、数据流转方式也截然不同。
本文选取七个具有代表性的数据库系统,覆盖文档数据库、搜索引擎、关系型数据库、OLAP 数据库、向量数据库和图数据库,逐一拆解一条查询从客户端发起到结果返回的完整路径。
一、MongoDB:文档行存 + B+Tree 索引
MongoDB 的查询执行流程相对经典,和 MySQL 类似,但在存储格式和聚合能力上有自己的特色。
执行流程
Client │ ▼┌─────────────┐│ mongos │ ← 分片集群时做路由(Standalone 跳过)│ (路由器) │└──────┬──────┘ │ ▼┌─────────────┐ ┌─────────────┐│ Parser │────▶│ Optimizer │ ← 生成多个候选计划,选成本最低的│ (查询解析) │ │ (计划选择) │└──────┬──────┘ └──────┬──────┘ │ ▼┌─────────────────────────────────┐│ Execution ││ ┌─────────┐ ┌─────────┐ ││ │ Index │ 或 │ CollScan│ │ ← 走索引还是全表扫描│ │ Scan │ │ (全扫描) │ ││ └────┬────┘ └────┬────┘ ││ │ │ ││ └──────┬───────┘ ││ ▼ ││ ┌─────────────────────────┐ ││ │ WiredTiger 存储引擎 │ │ ← B+Tree 索引、Page Cache、磁盘 IO│ │ - Cache (内存缓存) │ ││ │ - CheckPoint (磁盘数据) │ ││ └─────────────────────────┘ │└─────────────────────────────────┘ │ ▼ Result涉及的核心模块
| 模块 | 职责 |
|---|---|
| mongos | 分片集群的路由器,将查询分发到正确分片 |
| Parser | 解析查询语句(find、aggregate 等),生成内部表示 |
| Optimizer | 基于统计信息生成多个候选计划,选择成本最低的 |
| Execution Engine | 执行查询计划,支持单文档查询和聚合管道 |
| WiredTiger | 存储引擎,负责 B+Tree 索引维护、Page Cache、磁盘 IO |
特色说明
MongoDB 的聚合管道(Aggregation Pipeline)是其查询能力的核心。一个聚合查询会按阶段(Stage)依次执行,如 $match -> $project -> $group -> $sort,每个阶段处理上一个阶段的输出。这种设计让 MongoDB 在文档数据库中具备了不错的分析能力,但大规模聚合仍是其短板。
二、Elasticsearch:两阶段查询 + 倒排索引
Elasticsearch 的查询执行和数据库有本质区别。它的核心不是”扫描数据”,而是”通过倒排索引定位文档”。
执行流程
Client │ ▼┌─────────────────┐│ Coordinator Node │ ← 接收查询,协调分发└────────┬────────┘ │ ┌────┴────┐ ▼ ▼Shard 0 Shard 1 ... ← 每个分片并行执行【第一阶段:Query Phase】每个 Shard 内部:┌────────────────────────────────────────┐│ Lucene Segment ││ ┌─────────────┐ ┌─────────────┐ ││ │ Inverted Idx│ │ Doc Values │ ││ │ (定位文档) │ │ (聚合排序) │ ││ └──────┬──────┘ └─────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────┐ ││ │ 评分 (BM25/TF-IDF) │ ││ │ + 收集 Top-K 文档 ID │ ││ └─────────────────────────────┘ │└────────────────────────────────────────┘ │ ▼ 返回局部 Top-K 给 Coordinator │ ▼┌─────────────────────────────┐│ Coordinator 合并全局 Top-K │ ← 跨分片排序取 Top N└─────────────┬───────────────┘ │【第二阶段:Fetch Phase】 │ ┌─────────┴─────────┐ ▼ ▼Shard 0 Shard 1┌─────────────┐ ┌─────────────┐│ _source │ │ _source │ ← 根据文档 ID 拉回完整 JSON│ (行存文档) │ │ (行存文档) │└─────────────┘ └─────────────┘ │ ▼ 返回完整结果涉及的核心模块
| 模块 | 职责 |
|---|---|
| Coordinator Node | 接收客户端查询,分发给各分片,合并结果 |
| Lucene | 底层搜索引擎库,提供倒排索引、评分、聚合能力 |
| Segment | Lucene 的不可变索引段,是查询的最小单位 |
| Translog | 写入事务日志,保证数据不丢失 |
| Doc Values | 列存结构的字段值存储,支撑聚合和排序 |
特色说明
ES 的两阶段查询是其核心设计:
- Query Phase:各分片通过倒排索引找出匹配的文档 ID,按评分排序返回局部 Top-K。这个阶段不读完整的文档内容。
- Fetch Phase:Coordinator 合并全局 Top-K 后,向相关分片请求完整的
_source文档。
这种设计的代价是:如果查询需要聚合大量文档(如统计全量数据的平均值),ES 需要在 Query Phase 收集大量文档 ID,性能会下降。此时 Doc Values(列存副本)就派上用场了——聚合查询直接读 Doc Values,不经过两阶段流程。
三、PostgreSQL:经典火山模型 + 成本优化器
PostgreSQL 是开源关系型数据库中查询执行流程最清晰的代表,其 Planner 和 Executor 的设计影响了大量后续系统。
执行流程
Client │ ▼┌─────────┐│ Parser │ ← SQL 词法/语法解析,生成 Parse Tree└────┬────┘ │ ▼┌───────────┐│ Analyzer │ ← 语义分析:表/列是否存在、类型检查└─────┬─────┘ │ ▼┌───────────┐│ Rewriter │ ← 视图展开、规则重写└─────┬─────┘ │ ▼┌─────────────────────────────────────────┐│ Planner / Optimizer ││ ┌─────────────────────────────────┐ ││ │ 生成候选执行计划 │ ││ │ - Seq Scan / Index Scan │ ││ │ - Join (NestedLoop/Hash/Merge) │ ││ │ - Sort / Aggregate │ ││ └─────────────────────────────────┘ ││ │ ││ ┌────────────────┘ ││ ▼ ││ 成本估算(基于 pg_stat 统计信息) ││ 选择最低成本计划 │└─────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Executor ││ 火山模型(Volcano,逐行处理) ││ 或向量化(PG 14+,批量处理) ││ ││ ┌──────────┐ ┌──────────┐ ││ │ SeqScan │───▶│ HashJoin│ ││ │IndexScan │ │ ... │ ││ └────┬─────┘ └────┬─────┘ ││ │ │ ││ └───────┬───────┘ ││ ▼ ││ ┌─────────────────────────────────┐ ││ │ Buffer Manager │ ││ │ Shared Buffer (共享内存缓存) │ ││ │ ┌─────────┐ ┌────────────┐ │ ││ │ │ Heap │ │ Index │ │ ││ │ │ (表数据)│ │ (B+Tree等) │ │ ││ │ └────┬────┘ └────────────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ 磁盘文件(Page 粒度读取) │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘ │ ▼ Result涉及的核心模块
| 模块 | 职责 |
|---|---|
| Parser | SQL 词法/语法解析 |
| Analyzer | 语义分析,检查表/列存在性、类型匹配 |
| Rewriter | 视图展开、规则系统重写查询 |
| Planner | 基于成本的优化器(CBO),生成并选择最优执行计划 |
| Executor | 火山模型执行引擎(可扩展为向量化) |
| Buffer Manager | 管理 Shared Buffer(共享内存缓存),协调多进程访问 |
| WAL | 预写日志,保证事务持久性 |
| Access Methods | Table AM / Index AM 接口,支持自定义存储引擎 |
特色说明
PostgreSQL 的 Planner 是其最精华的部分。它维护了一套详细的统计信息(pg_stat 系统表),包括每个表的行数、每列的数据分布直方图、相关性等。优化器基于这些统计信息估算不同执行计划的代价(磁盘 IO、CPU、网络),选择成本最低的方案。
PostgreSQL 默认使用火山模型(Volcano Model):每个算子(Scan、Join、Sort)实现一个 next() 接口,父算子调用子算子的 next() 获取下一行数据。这种模型非常灵活,但函数调用开销大。PG 14+ 开始引入向量化执行,批量处理多行数据以提升性能。
四、Doris:FE/BE 分离 + MPP 并行执行
Doris 是面向分析的 MPP(大规模并行处理)数据库,其架构设计和查询流程体现了分布式 OLAP 的核心思想。
执行流程
Client │ ▼┌─────────────────────────────────────────┐│ FE (Frontend) ││ ││ ┌─────────┐ ┌─────────┐ ┌────────┐ ││ │ Parser │──▶│ Analyzer│──▶│Planner │ ││ │ │ │ │ │ (CBO) │ ││ └─────────┘ └─────────┘ └───┬────┘ ││ │ ││ 生成单节点计划 → 生成分布式计划 │ ││ (哪些 BE 执行哪些 Fragment) │ │└─────────────────────────────────────────┘ │ ▼ ┌──────────┼──────────┐ ▼ ▼ ▼ BE 1 BE 2 BE 3┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Fragment │ │ Fragment │ │ Fragment ││ Executor │ │ Executor │ │ Executor ││ │ │ │ │ ││ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ ││ │ Scan │ │ │ │ Scan │ │ │ │ Scan │ ││ │ (列存) │ │ │ │ (列存) │ │ │ │ (列存) │ ││ └────┬────┘ │ │ └────┬────┘ │ │ └────┬────┘ ││ │ │ │ │ │ │ │ ││ ▼ │ │ ▼ │ │ ▼ ││ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ ││ │Exchange │◀─┼─┼▶│Exchange │◀─┼─┼▶│Exchange │ │ ← 数据 Shuffle│ │ (网络) │ │ │ │ (网络) │ │ │ │ (网络) │ ││ └────┬────┘ │ │ └────┬────┘ │ │ └────┬────┘ ││ │ │ │ │ │ │ │ ││ ▼ │ │ ▼ │ │ ▼ ││ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ ││ │ Join │ │ │ │ Aggregate│ │ │ │ Sort │ ││ │ Hash │ │ │ │ (本地) │ │ │ │ │ ││ └────┬────┘ │ │ └────┬────┘ │ │ └────┬────┘ ││ │ │ │ │ │ │ │ ││ └───────┼─┼──────┘ │ │ │ ││ │ │ │ │ │ │└──────────────┼─┼──────────────┘ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ ┌─────────────────────────────┐ │ │ FE 合并最终结果 │◀───────┘ └─────────────────────────────┘涉及的核心模块
| 模块 | 职责 |
|---|---|
| FE (Frontend) | SQL 解析、语义分析、查询优化、分布式计划生成 |
| BE (Backend) | 执行 Fragment(查询计划片段),数据扫描和计算 |
| Fragment | 分布式执行的最小单位,一个 BE 上的查询子计划 |
| Exchange Node | BE 之间的数据交换(Shuffle/Broadcast) |
| Storage | Tablet(数据分片)、Rowset、Segment、Page Cache |
特色说明
Doris 的核心设计是 FE/BE 分离:
- FE 是无状态的查询协调者,负责 SQL 解析和计划生成。它会将查询计划切分为多个 Fragment,每个 Fragment 对应一个 BE 上的执行单元。
- BE 是实际干活的数据节点,负责从本地存储读取数据并执行计算。
MPP 的精髓在于 Exchange Node:当数据需要重新分布时(如 Hash Join 前需要将相同 Key 的数据集中到同一个 BE),BE 之间通过网络直接交换数据,不需要经过 FE。这使得 Doris 可以线性扩展到数百个节点。
Doris 同时采用了向量化执行引擎,算子内部一次处理一批数据(而非一行),配合列存格式,分析性能远超传统行存数据库。
五、ClickHouse:Pipeline 模型 + Part 不可变
ClickHouse 以其极致的分析性能著称,其查询执行流程和存储设计都有鲜明的特色。
执行流程
Client │ ▼┌─────────┐│ Parser │ ← SQL 解析└────┬────┘ │ ▼┌───────────┐│Interpreter│ ← AST 语义分析、查询重写└─────┬─────┘ │ ▼┌───────────┐│ Planner │ ← 生成执行计划└─────┬─────┘ │ ▼┌─────────────────────────────────────────┐│ Pipeline Executor ││ (多线程并行 + 向量化) ││ ││ ┌─────────────────────────────────┐ ││ │ ReadFromMergeTree │ ││ │ - 主键稀疏索引定位 Parts │ ││ │ - 跳过多余 Granules │ ││ └─────────────┬───────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────┐ ││ │ Expression / Filter │ ││ │ (向量化过滤,一次 65536 行) │ ││ └─────────────┬───────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────┐ ││ │ Join (如果有) │ ││ │ - Hash Join / Merge Join │ ││ └─────────────┬───────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────┐ ││ │ AggregatingTransform │ ││ │ (两阶段聚合:本地 + 全局) │ ││ └─────────────┬───────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────┐ ││ │ MergeTree 存储层 │ ││ │ - Parts (不可变数据块) │ ││ │ - Primary Index (稀疏索引) │ ││ │ - Skip Indexes (跳数索引) │ ││ │ - Column files (列存文件) │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘ │ ▼ Result涉及的核心模块
| 模块 | 职责 |
|---|---|
| Interpreter | 将 AST 解释为可执行的操作序列 |
| Planner | 生成逻辑和物理执行计划 |
| Pipeline Executor | 多线程并行执行,采用 Push 模型 |
| MergeTree | 存储引擎,数据按 Part 不可变存储,后台 Merge |
| Primary Index | 稀疏索引(每 8192 行一条),极小,常驻内存 |
| Skip Index | 跳数索引(如 min/max、布隆过滤器),进一步减少数据读取 |
特色说明
ClickHouse 的 Pipeline Executor 采用 Push 模型(而非 PostgreSQL 的 Pull 模型):数据从存储层被推上来,经过各级算子处理。每个算子可以有多线程实例并行执行。
MergeTree 是 ClickHouse 最核心的存储设计:
- 数据写入时生成一个不可变的 Part
- 后台线程异步合并(Merge)小 Part 为大 Part
- 查询时通过稀疏索引跳过不相关的 Granule(8192 行一组)
- 这种设计让写入极快(顺序追加),查询时只读需要的 Granule
ClickHouse 的向量化执行默认一次处理 65536 行,配合 SIMD 指令,单核分析性能极高。
六、Milvus:多角色分布式 + ANN 索引
Milvus 是专用的向量数据库,其架构设计完全围绕”高维向量相似性搜索”这一核心场景展开。
执行流程
Client │ ▼┌─────────────────────────────────────────┐│ Proxy ││ - 接收请求 ││ - 鉴权、路由 ││ - 结果合并 │└─────────────┬───────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ Query Coordinator ││ - 管理 Query Node 分布 ││ - 负载均衡 │└─────────────┬───────────────────────────┘ │ ┌───────┴───────┐ ▼ ▼ Query Node 1 Query Node 2 ...┌─────────────────────────────────────────┐│ ┌─────────────┐ ┌─────────────┐ ││ │ Segment │ │ Index │ ││ │ (原始向量) │ │ (HNSW/IVF) │ ││ │ 内存/磁盘 │ │ 内存中 │ ││ └─────────────┘ └──────┬──────┘ ││ │ ││ ┌──────────────────────────┘ ││ ▼ ││ ANN 搜索算法 ││ - HNSW: 图遍历贪心搜索 ││ - IVF: 先找最近聚类中心,再搜该桶 ││ - 计算距离 (L2/IP/Cosine) ││ - 标量过滤 (如有 metadata 条件) ││ └──────────────────────────────────── │└─────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────┐│ 结果合并 + Rerank ││ (多个 Query Node 的局部 Top-K 合并) │└─────────────────────────────────────────┘ │ ▼ Result【后台独立模块】┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Index Node │ │ Data Node │ │ Meta Store ││ (建索引) │ │ (数据写入) │ │ (etcd) │└─────────────┘ └─────────────┘ └─────────────┘涉及的核心模块
| 模块 | 职责 |
|---|---|
| Proxy | 接收客户端请求,鉴权,路由到 Query Coordinator |
| Query Coordinator | 管理 Query Node 的负载和分布 |
| Query Node | 执行向量搜索,加载索引到内存,计算相似度 |
| Index Node | 异步构建向量索引(HNSW、IVF 等) |
| Data Node | 处理数据写入,将数据持久化到对象存储 |
| Meta Store (etcd) | 存储元数据(Collection、Partition、Index 信息) |
| Object Storage | MinIO/S3,存储原始向量数据和索引文件 |
| Message Queue | Pulsar/Kafka,数据写入的缓冲队列 |
特色说明
Milvus 的查询流程和数据库完全不同:
- 没有 SQL 解析和查询优化器,查询参数直接描述搜索行为(向量 + 相似度度量 + Top-K + 标量过滤条件)
- ANN 搜索是核心:根据向量索引(HNSW 图或 IVF 聚类)快速定位最近邻,不需要全量扫描
- 标量过滤 + 向量搜索的混合查询是难点:先通过标量条件缩小候选集,再在候选集上做向量搜索
Milvus 的多角色架构让其可以独立扩展:Query Node 不够就加查询节点,索引构建慢就加 Index Node。
七、NebulaGraph:图遍历 + LSM-Tree 存储
NebulaGraph 是分布式图数据库,其查询执行流程围绕”从起点出发,沿着边遍历”这一核心操作展开。
执行流程
Client │ ▼┌─────────────────────────────────────────┐│ Graph Service (GraphD) ││ ││ ┌─────────┐ ┌─────────┐ ┌────────┐ ││ │ Parser │──▶│ Planner│──▶│Optimizer│ ││ │ (nGQL) │ │ │ │ (RBO) │ ││ └─────────┘ └─────────┘ └───┬────┘ ││ │ ││ 生成执行计划(获取起点 → 遍历边 → 过滤) │└─────────────────────────────────────────┘ │ ▼ ┌──────────┼──────────┐ ▼ ▼ ▼ StorageD 1 StorageD 2 StorageD 3┌─────────────────────────────────────────┐│ 每个 StorageD 内部: ││ ││ 1. 找起点 Vertex ││ - 如果按 VID 查:直接定位 ││ - 如果按属性查:走属性索引 → 查 VID ││ ││ 2. 遍历 Edge ││ - 根据 VID 做 prefix scan ││ - 读取出边/入边列表 ││ ││ 3. 多跳扩展 ││ - 重复步骤 1-2 ││ ││ 4. 属性过滤 ││ - 读取 Vertex/Edge 属性 ││ - WHERE 条件过滤 ││ ││ 底层:RocksDB (LSM-Tree) ││ - Tag 和 Edge 数据以 KV 形式存储 ││ - Key 格式:Type + VID + EdgeType + Rank │└─────────────────────────────────────────┘ │ ▼ GraphD 合并结果 │ ▼ Result【Meta Service (MetaD)】- Schema 信息(Tag、EdgeType 定义)- 分区分布信息(哪个 VID 在哪个 StorageD)- 索引信息- 基于 Raft 保证一致性涉及的核心模块
| 模块 | 职责 |
|---|---|
| GraphD | 查询解析、计划生成、结果合并 |
| StorageD | 图数据存储,执行顶点/边的读取和遍历 |
| MetaD | 元数据管理(Schema、分区、索引),基于 Raft 保证一致性 |
| RocksDB | StorageD 的底层存储引擎(LSM-Tree) |
| Raft | 多副本一致性协议 |
特色说明
NebulaGraph 的查询执行完全围绕图遍历设计:
- VID 直接路由:每个 Vertex 有全局唯一的 VID(Vertex ID),通过
part_id = vid % num_parts直接路由到对应的 StorageD,不需要分布式查询协调 - Prefix Scan 遍历边:给定一个 VID,StorageD 在 RocksDB 中做前缀扫描,一次性读出该顶点的所有出边/入边
- 多跳扩展:一跳查询返回的结果作为下一跳的起点,重复上述过程
和 Neo4j 不同的是,NebulaGraph 的底层存储是 RocksDB(LSM-Tree) 而非自定义文件格式。图数据被编码为 KV 对存储:
- Vertex Key:
Type + VID + TagType-> Value: 属性 - Edge Key:
Type + SrcVID + EdgeType + Rank + DstVID-> Value: 属性
这种设计的优势是可以利用成熟的 LSM-Tree 存储引擎,代价是图的遍历需要多次 KV 查询(而非 Neo4j 的直接指针跳转)。
横向对比总结
| 系统 | 架构模式 | 执行模型 | 查询核心 | 存储模型 |
|---|---|---|---|---|
| MongoDB | 单体/副本集/分片 | 管道执行 | B+Tree 索引跳转 | 行存(BSON 文档) |
| ES | 分片 + 副本 + 协调节点 | 两阶段查询 | 倒排索引 + 评分 | 行存 + 倒排索引 + Doc Values |
| PostgreSQL | 单节点/流复制 | 火山模型(逐行) | 成本优化器 + B+Tree | 行存(Heap) |
| Doris | FE + BE 分离、MPP | Pipeline + 向量化 | 分布式计划 + Shuffle | 列存 |
| ClickHouse | 单节点/分布式表 | Pipeline + 向量化 | 稀疏索引 + Part 裁剪 | 列存(MergeTree) |
| Milvus | Proxy + Node 多角色 | ANN 图遍历/聚类 | HNSW / IVF 索引 | 向量 + 标量分离 |
| NebulaGraph | Graph + Storage + Meta | 图遍历 | VID 路由 + Prefix Scan | LSM-Tree(RocksDB) |
选型启示
从查询执行流程的角度,不同类型数据库的选型逻辑可以总结为:
如果你的查询模式是”已知 ID,查详情”:选择行存数据库(MongoDB、PostgreSQL),它们通过索引一次定位、一次读取。
如果你的查询模式是”全文搜索,返回最相关的 N 个”:选择 Elasticsearch,倒排索引 + 评分机制是为此而生的。
如果你的查询模式是”扫描大量数据,做聚合分析”:选择 OLAP 数据库(Doris、ClickHouse),列存 + 向量化 + MPP 并行是核心优势。
如果你的查询模式是”找一个向量的最近邻”:选择向量数据库(Milvus),ANN 索引让十亿级向量的相似性搜索在毫秒级完成。
如果你的查询模式是”从一个点出发,沿关系找多跳邻居”:选择图数据库(NebulaGraph),图遍历的执行路径比关系数据库的多表 JOIN 高效数个数量级。
理解每个数据库的查询执行流程,就是理解它为什么适合(或不适合)你的场景。没有最好的数据库,只有最适合查询模式的数据库。
© 本文著作权归作者所有,转载请注明出处。