Day 3|Doris 架构总览:FE、BE、MPP、列存、向量化与 Pipeline
前两天已经建立了数据系统的坐标系,也明确了 Doris 的定位、场景和边界。今天开始进入 Doris 内部,沿着一条 SQL 的实际旅程,认识 FE、BE、MPP、Fragment、Instance、Exchange、列式存储、向量化和 Pipeline。
学完这一篇,你应当能够把“Doris 为什么快”拆成若干可验证的环节,也能够在查询变慢时先判断问题位于规划、扫描、计算、网络交换,还是结果返回。

本日学习目标
完成今天的学习后,你应当能够:
- 说明 FE 与 BE 的职责边界,以及 Master、Follower、Observer 的作用;
- 描述 SQL 从客户端进入 FE,到多个 BE 并行执行,再返回结果的完整链路;
- 解释 Fragment、Instance、Exchange 之间的关系;
- 区分 MPP、向量化执行和 Pipeline,各自解决哪一层问题;
- 理解列存、编码压缩、CPU Cache、SIMD 与多核调度如何协同;
- 使用
EXPLAIN和 Query Profile 对慢查询做第一轮分层定位; - 完成一条 JOIN 加聚合 SQL 的执行链路注释图。
一、先建立一个正确的架构视角
学习数据库架构时,最容易陷入两种状态。
第一种状态是背组件名:FE 负责元数据,BE 负责存储和计算。结论没有错,但它解释不了查询为什么要经过多个阶段,也无法帮助我们定位生产问题。
第二种状态是过早钻进源码:先研究某个 Java 类、C++ 算子或线程池。实现细节会随版本变化,学习者很快失去全局方向。
更稳妥的方式是先建立三条主线。
控制路径回答“这条 SQL 应该怎样执行”。连接、鉴权、语义分析、优化、切分、调度都属于这条路径,核心工作主要发生在 FE。
数据路径回答“数据从哪里读取,经过哪些算子,如何在节点间流动”。扫描、过滤、Join、聚合、排序、Exchange 和结果输出主要发生在 BE。
资源路径回答“执行过程中消耗了什么”。磁盘与对象存储提供数据,内存承载中间状态,CPU 执行表达式和算子,网络承担 Shuffle,客户端与代理负责拉取结果。
一条 SQL 的总耗时,可以粗略拆成下面几段:
总耗时
= 连接与排队
+ FE 解析、绑定、优化和调度
+ BE 扫描与计算
+ 节点间数据交换
+ 结果合并与客户端拉取
这个公式没有覆盖每一个细节,却足以形成第一层诊断框架。后续学习存储、优化器、导入、资源治理和运维时,所有知识都能放回这三条主线中。
本文以 Doris 经典的 FE + BE 存算一体架构建立概念。Doris 4.x 同时支持存算分离形态,后者会引入共享对象存储、计算组和 Meta Service 等组件。Day 4 会专门讨论两种架构的选择,今天先把查询引擎的共同核心讲清楚。
二、FE 与 BE:控制面和数据面的职责边界

2.1 FE:理解 SQL、管理元数据、生成计划、协调执行
FE 是 Frontend 的缩写,使用 Java 实现。客户端通常通过 MySQL 协议连接 FE,JDBC、ODBC、DataGrip、BI 工具和多数 MySQL 生态客户端都可以沿用熟悉的连接方式。
FE 主要承担五组工作。
1. 协议、连接与会话
FE 接收连接,完成认证,创建会话,管理数据库上下文、时区、SQL Mode、资源组、超时和各类 Session Variable。一个看似简单的查询,首先要在这里确认用户身份、会话状态和执行环境。
生产环境通常会在多个 FE 前放置负载均衡或域名。客户端无需记住某一台 FE 的地址,也能在节点维护或故障时切换到可用节点。连接池仍要配置合理的重试、连接超时和读写超时,避免故障期间形成重连风暴。
2. 元数据管理
数据库、表、分区、Tablet、副本、用户、权限、Catalog 和作业状态都属于元数据。查询计划必须知道表有哪些列、分区在哪里、Tablet 分布在哪些 BE、哪些副本健康,才能决定扫描范围与执行位置。
在存算一体架构中,FE 通过高可用元数据机制保持多个节点之间的一致性。生产集群常见三类 FE 角色:
| 角色 | 主要职责 | 是否参与选举 |
|---|---|---|
| Master | 处理元数据写入,协调集群状态 | 是 |
| Follower | 保存完整元数据,参与选举,可承担读请求 | 是 |
| Observer | 保存元数据副本,用于扩展读取能力 | 否 |
元数据变更需要多数有投票资格的节点确认。Master 故障后,Follower 可以参与重新选举。Observer 不增加投票成员,适合扩展 FE 读取能力或承担更多查询连接。
3. 解析、绑定与语义检查
SQL 文本进入 FE 后,会先被解析成语法结构。随后进行表列绑定、类型推导、函数解析、权限检查和语义校验。
例如:
SELECT region, SUM(amount)
FROM sales.orders
GROUP BY city;
如果 region 没有出现在合法的聚合表达式或 GROUP BY 中,语义分析阶段会直接报错;如果用户没有 sales.orders 的查询权限,查询也不会进入 BE。
这一阶段的产物已经具备明确的表、列、类型和权限含义,可以继续进入优化器。
4. 查询优化
Doris 4.x 以 Nereids 作为核心优化器。优化过程包含规则改写和基于代价的选择。
规则改写适合处理确定性的变换,例如常量折叠、谓词下推、投影裁剪、子查询改写和无效表达式消除。基于代价的优化依赖统计信息,评估不同 Join 顺序、Join 方法、数据分布和并行方式的成本。
统计信息通常包含行数、数据大小、最小值、最大值、空值数量和 NDV。NDV 表示不同值数量,对估算过滤选择率和 Join 基数很重要。统计信息过期时,优化器可能把大表当成小表,选择不合适的 Broadcast,或把内存需求巨大的表放到 Hash Join 的 Build 侧。
5. Coordinator 与调度
物理计划生成后,FE 会根据数据分布、可用副本、并行度和节点状态,将计划切分并下发到 BE。承担本次查询协调工作的 FE 会跟踪各个执行实例,汇总状态,处理错误,并将最终结果返回客户端。
这里要分清两个概念:FE 是集群组件,Coordinator 是某次查询中的协调角色。一台 FE 可以同时协调很多查询;同一个查询的主要计算则分散在多台 BE。
2.2 BE:保存数据、执行算子、交换数据、产生结果
BE 是 Backend 的缩写,使用 C++ 实现。在存算一体模式中,每台 BE 同时拥有本地存储、内存和 CPU,承担数据副本与查询执行。
BE 的工作可以拆成六组。
1. Tablet 与副本存储
Doris 将表水平切分成 Tablet。Tablet 是副本调度、迁移和修复的基本单位。每个 Tablet 内部包含多个 Rowset,Rowset 又包含一个或多个 Segment 文件。
这套层次在 Day 19 会详细展开。今天只需要记住:FE 保存“数据分布地图”,BE 保存实际数据文件。查询开始前,FE 根据分区、分桶、Tablet 和副本状态挑选扫描位置。
2. Scan 扫描
Scan 算子读取 Segment,执行分区与 Tablet 范围之外的细粒度裁剪,读取需要的列,完成解压、解码、谓词过滤和删除标记处理。
扫描速度取决于读取字节数、命中索引、磁盘吞吐、文件数量、缓存状态和数据分布。很多慢查询表面上是 CPU 高,根源却是扫描范围过大。
3. Join、聚合、排序与表达式计算
过滤后的列式 Block 进入 Join、Aggregation、Sort、Window、TopN 等算子。Hash Join 需要构建哈希表,Aggregation 需要维护分组状态,排序可能需要较大内存。这些算子会形成查询的主要 CPU 与内存消耗。
4. Exchange 与网络传输
当数据需要跨 Instance 或跨节点重新分布时,Exchange 负责发送和接收列式数据块。大表 Join、全局聚合、ORDER BY 和去重都可能产生 Shuffle。
Exchange 的成本包括序列化、网络传输、接收队列、反序列化和下游等待。数据倾斜会让某几个接收端承担过多数据,整个查询最终被最慢实例拖住。
5. Pipeline 调度
BE 把执行计划组织成可调度任务。Task 在输入可用、输出可写时运行;遇到磁盘等待、网络等待、下游背压或其他阻塞条件时,会主动让出线程。固定数量的工作线程能够服务大量 Pipeline Task,线程数量不会随着查询数线性膨胀。
6. 局部结果与返回
BE 可以先完成局部聚合、局部 TopN 或局部过滤,再把更小的结果发送到上游 Fragment。最终结果通过 Result Sink 返回 Coordinator,随后传给客户端。
局部计算是分布式分析性能的重要来源。假设三台 BE 各扫描一亿行,先在本地聚合成几十个分组,再通过网络发送几十行,网络成本会明显低于传输三亿行原始数据。
三、一条 SQL 进入 FE 后经历什么

为了让流程更具体,使用下面这条查询作为主线:
SELECT
c.region,
SUM(o.amount) AS total_amount
FROM fact_orders o
JOIN dim_city c
ON o.city_id = c.city_id
WHERE o.order_date >= '2026-08-01'
GROUP BY c.region
ORDER BY total_amount DESC;
3.1 连接与鉴权
客户端通过 MySQL 协议发送 SQL。FE 读取当前用户、默认 Catalog、Database、会话变量、Workload Group 和超时设置,确认访问权限。
这一步出现问题时,常见现象包括连接建立慢、连接池耗尽、认证失败、代理转发异常和请求排队。此时 BE 可能完全没有收到任务,扩容 BE 也不会改善情况。
3.2 解析与绑定
解析器识别 SELECT、JOIN、WHERE、GROUP BY 和 ORDER BY 的语法结构。绑定阶段将 fact_orders、dim_city 和各个列名映射到真实元数据,完成别名解析、类型检查、函数解析和权限校验。
o.city_id = c.city_id 两侧类型需要兼容,SUM(o.amount) 需要找到匹配的聚合函数签名,日期字符串也需要按规则转换为日期类型。
3.3 逻辑计划与规则优化
初始逻辑计划大致可以理解为:
Sort(total_amount DESC)
└─ Aggregate(region, SUM(amount))
└─ Join(o.city_id = c.city_id)
├─ Filter(order_date >= '2026-08-01')
│ └─ Scan fact_orders
└─ Scan dim_city
规则优化会把过滤条件尽量推向扫描端,只读取查询所需列,并简化表达式。fact_orders 如果按 order_date 分区,FE 可在调度前裁掉无关分区;如果分桶键与等值条件匹配,还可能进一步裁掉不相关 Tablet。
3.4 CBO 选择 Join 与数据分布
优化器需要决定 dim_city 是否足够小,能否广播到所有执行节点;也要估算 fact_orders 过滤后的行数、各区域分布和聚合基数。
一种常见计划是:
- 小表
dim_city在各 BE 构建 Hash Table; - 生成 Runtime Filter,并发送到事实表扫描节点;
fact_orders按日期裁剪后扫描;- 扫描阶段应用 Runtime Filter,提前丢弃无匹配
city_id的行; - 每台 BE 完成本地 Join 与局部聚合;
- 按
regionShuffle 局部结果; - 上游 Fragment 完成全局聚合和排序。
如果 dim_city 实际很大,Broadcast 会造成每台 BE 都接收一份完整数据,网络和内存压力迅速上升。准确统计信息能帮助优化器选择 Shuffle Join 或其他分布方式。
3.5 物理计划、Fragment 与调度
物理计划明确了具体算子和数据分布。FE 会以 Exchange 为边界切分 Fragment,再根据扫描范围和并行度生成 Fragment Instance。
计划描述“做什么”,Instance 表示“这份工作在哪台 BE 上运行”。一个 Fragment 可以在多台 BE 上拥有多个 Instance,所有实例执行同一类算子,处理各自的数据分片。
四、MPP:把一条查询变成集群级并行任务

4.1 Share-Nothing 的核心含义
在 Doris 存算一体集群中,每台 BE 拥有独立的 CPU、内存和本地磁盘。节点之间通过网络交换数据,单台机器不会直接访问另一台机器的内存或本地盘。
这种结构便于水平扩展。增加 BE 后,集群获得更多 CPU、内存、磁盘和并行执行槽位;副本均衡完成后,新节点也会承担数据扫描。
扩容收益受数据分布和查询形态约束。单分区只有少量 Tablet、查询总是命中一个热点 Bucket、Join Key 严重倾斜时,新增节点无法自动形成线性加速。架构提供并行能力,表设计和数据分布决定能力能否被充分利用。
4.2 Fragment:分布式执行阶段
Fragment 可以理解为一段可以独立调度的物理计划。Exchange 会形成 Fragment 之间的数据边界。
前面的 JOIN 加聚合查询,可能被切成三层:
Fragment 0
Result Sink
└─ Sort
└─ Merge Aggregate
↑ Exchange Receiver
Fragment 1
Exchange Sink (按 region 分发)
└─ Local Aggregate
└─ Hash Join Probe
└─ Scan fact_orders
Fragment 2
Broadcast Exchange Sink
└─ Hash Join Build
└─ Scan dim_city
实际计划会随着版本、数据规模、统计信息和表设计变化。这里的重点是理解边界:Fragment 2 产生小表数据或过滤信息,Fragment 1 扫描事实表并局部计算,Fragment 0 汇总最终结果。
4.3 Instance:Fragment 的物理执行副本
Fragment 是计划阶段的概念,Instance 是运行阶段的实体。假设事实表有 32 个 Tablet,集群有 4 台 BE,扫描 Fragment 可能在多台 BE 上产生若干 Instance,每个 Instance 负责一组 Scan Range。
一个 Instance 运行在单台 BE 上,内部包含一组算子和 Pipeline Task。多个 Instance 可以并行处理不同 Tablet,也可以在同一 BE 上利用多个执行任务并发运行。
查询并行度受到多种因素影响:
- 存活 BE 数量;
- 命中的 Tablet 与副本分布;
- Scan Range 数量;
- Bucket 数与分区设计;
- Pipeline 并行度设置;
- Workload Group 的并发与资源限制;
- Join 或聚合后的数据分布;
- 热点 Key 和数据倾斜;
- 节点 CPU、内存与 I/O 饱和程度。
并行度过低会浪费集群资源,过高会增加调度、内存、线程队列和网络开销。生产调优需要结合 Profile 观察每个 Instance 的工作量与耗时差异。
4.4 Exchange:连接各个执行阶段的数据通道
Exchange 负责把数据从发送端传给接收端。常见分布方式包括:
| 方式 | 数据如何流动 | 常见用途 | 主要风险 |
|---|---|---|---|
| Broadcast | 将一份小表数据发送到多个节点 | 大表 JOIN 小表 | 小表判断错误导致网络与内存放大 |
| Shuffle | 按 Join Key 或 Group Key 重新分区 | 大表 Join、全局聚合 | 数据量大、热点 Key、带宽瓶颈 |
| Gather | 将多个节点结果汇聚到少量节点 | 最终结果、全局排序 | 上游节点成为单点瓶颈 |
| Bucket / Colocate | 利用已有分桶布局减少移动 | 高频固定 Join | 需要建表阶段保持分布一致 |
Exchange 也解释了为什么网络是 MPP 系统的重要资源。查询在单机上很快,扩展到多表跨节点 Join 后变慢,常见原因正是 Shuffle 数据量和倾斜。
4.5 MPP 与 MapReduce 的差异
两者都能把任务分到多台机器。主要差异体现在执行模型和服务目标。
MapReduce 面向批处理作业,阶段边界清晰,中间结果常通过持久化文件衔接,调度启动成本较高,适合长时间离线计算。
MPP 数据库是常驻服务。查询计划被切分成多个并行阶段,数据通过网络 Exchange 以流式方式在算子之间传递,优化器会根据表统计和分布选择 Join 与聚合策略,目标是较低交互延迟和较高并发。
现代 Doris 支持 Spill,大查询在内存压力下可以把部分中间状态写入磁盘。因此,MPP 查询也可能发生落盘。关键差异在于整体执行链路围绕数据库查询、长驻进程、流水线算子和成本优化器组织。
五、BE 内部:列存、向量化、SIMD 与 Pipeline

5.1 列式存储先解决“少读数据”
分析查询经常面对宽表。表中可能有 100 列,某条 SQL 只使用日期、地区和金额三列。行式存储需要读取包含大量无关字段的数据页,列式存储可以直接访问参与计算的列。
同一列的数据类型与取值分布更接近,也更适合编码和压缩:
- 连续重复值可使用 RLE;
- 低基数字符串可使用字典编码;
- 整数和时间值可使用差值或位级编码;
- LZ4 偏向解压速度;
- ZSTD 偏向更高压缩率。
压缩减少磁盘读取和网络传输,解压会消耗 CPU。选型时需要平衡存储、I/O 与计算。高并发热数据常重视解压速度,低频历史数据更关注空间成本。
列存仍然需要配合数据裁剪。读取三列却扫描全年所有分区,成本依旧很高。分区、分桶、ZoneMap、Bloom Filter、倒排索引和 Runtime Filter 会先尽量缩小范围,列存负责高效读取剩余数据。
5.2 向量化把逐行处理改成批量处理
传统火山模型通常由上层算子反复调用下层算子的 next(),每次处理一行或很小的数据单元。大量虚函数调用、分支判断和对象访问会消耗 CPU,也不利于缓存命中。
向量化执行按列式 Block 处理一批数据。过滤算子一次判断一组值,聚合算子一次更新一批分组状态,表达式计算在连续数组上完成。
这种方式带来几类收益:
- 函数调用成本被一批数据分摊;
- 连续内存访问提高 CPU Cache 命中率;
- 基础类型数组比对象结构更紧凑;
- 编译器和手写实现更容易使用 SIMD;
- 算子之间传递列式 Block,减少格式转换。
向量化适合扫描、过滤、聚合和批量表达式计算。单行主键点查的工作量很小,规划、网络往返和索引定位占比更高,因此 Doris 另有 Prepared Statement、短路执行和行存加速路径。
5.3 SIMD 让一条指令处理多个值
SIMD 是 Single Instruction Multiple Data 的缩写。CPU 可以用一条向量指令同时处理多个整数或浮点数。列式连续数组天然适合这种执行方式。
例如,过滤 amount > 100 时,标量执行逐个读取并比较;向量化实现可以一次装载多个金额,完成并行比较,再生成选择向量或过滤掩码。
SIMD 收益取决于数据类型、算子实现、CPU 指令集和分支复杂度。字符串解析、复杂 UDF、频繁类型转换和数据依赖较强的逻辑,通常难以获得同等幅度的加速。硬件升级也无法替代合理建模和数据裁剪。
5.4 CPU Cache 亲和性
CPU 访问 L1、L2、L3 Cache 的延迟远低于访问主内存。连续读取列式数组更容易预取,缓存行利用率也更高。
行对象经常包含指针、对象头和分散内存地址,访问过程容易产生 Cache Miss。列式 Block 把相同类型的数据紧凑排列,Null 信息通常单独保存,算子可以顺序遍历。
这解释了列存与向量化经常同时出现:列式磁盘布局减少 I/O,列式内存布局提高 CPU 处理效率,两者通过 Block 在扫描与计算之间衔接。
5.5 Pipeline 解决多查询与多核调度
假设一个查询包含 Scan、Filter、Join、Aggregation 和 Exchange。某个 Scan 正在等磁盘,Exchange 正在等网络,Join 的 Build 侧尚未完成。若每个执行链长期占用独立线程,大量线程会处于等待状态,线程切换和栈内存也会不断增加。
Pipeline 将执行链拆成可调度任务,并使用固定规模的线程池。任务具备运行条件时获得 CPU;输入未就绪、输出阻塞或时间片结束时主动 Yield。Scheduler 随后运行其他可执行任务。
这套机制提高多核利用率,也限制线程膨胀。它仍受资源约束:大量查询同时维护 Hash Table、聚合状态和排序缓冲区时,内存压力依旧存在;磁盘和网络长期饱和时,Task 只能排队等待。Workload Group、并发控制、内存限制和 Spill 会在后续章节继续展开。
5.6 三层技术栈的关系
可以用一句话记住:
MPP 决定工作怎样分到多台 BE;
Pipeline 决定一台 BE 内的任务怎样共享多核;
向量化决定一个算子怎样高效处理一批列式数据。
三者层次不同,组合后形成从集群、节点到 CPU 指令的完整并行体系。
六、把 JOIN 与聚合的数据流完整串起来

继续使用区域销售额查询。一次典型执行可以分成以下步骤。
步骤 1:FE 裁剪扫描范围
order_date >= '2026-08-01' 如果命中分区字段,FE 在生成 Scan Range 时只保留相关分区。等值条件命中分桶键时,还可能减少 Tablet 数量。
步骤 2:小表构建 Hash Table
dim_city 被选择为 Build 侧。各执行节点获得小表数据并构建 Hash Table。若采用 Broadcast,每个 Probe 节点会持有一份小表。
步骤 3:生成 Runtime Filter
Build 侧可以根据 city_id 生成 IN、Min/Max 或 Bloom 类过滤信息。过滤器发送到事实表 Scan 节点,帮助扫描端提前排除无匹配数据。
Runtime Filter 到达需要时间。扫描端可以等待一小段时间,也可以先开始工作。等待过久会增加延迟,到达过晚又可能错过大量裁剪机会,具体策略由执行器和参数共同决定。
步骤 4:多台 BE 并行扫描事实表
每个 Instance 读取自己负责的 Tablet。Scan 算子依次利用 ZoneMap、Bloom Filter、倒排索引和 Runtime Filter 缩小数据,再读取参与计算的列。
步骤 5:本地 Join 与局部聚合
过滤后的订单行在本地 Probe Hash Table,得到 region。每台 BE 先按 region 计算局部 SUM(amount)。
假设每台 BE 扫描一亿行,最终只有几十个区域。局部聚合可以把需要传输的数据从亿级行压缩到几十行。
步骤 6:按分组键 Shuffle
局部结果按 region 重新分布,相同区域的数据被发送到同一组上游 Instance。Exchange 负责数据块的发送、接收和背压。
步骤 7:全局聚合与排序
上游 Fragment 合并各节点的局部和,得到全局销售额,随后执行排序和 Result Sink。Coordinator 把结果按 MySQL 协议返回客户端。
这条链路中,任何环节都可能成为瓶颈:
- 分区未裁剪,Scan 读取过多;
- 小表估算错误,Broadcast 太大;
- Runtime Filter 过滤率低;
- 某个区域占据大部分数据,Shuffle 倾斜;
- 局部聚合基数太高,内存上涨;
- 排序结果过大;
- 客户端一次拉取数百万行。
架构知识的价值就在这里:看到“SQL 运行 30 秒”,我们能够提出可验证的问题,而不会直接把原因归结为机器不够。
七、存储、内存、CPU 与网络怎样协同
查询引擎的整体效率取决于四类资源之间的配合。
7.1 存储负责提供有效数据
本地 SSD、HDD 或远端对象存储决定原始读取能力。分区、Tablet、Segment、Page 和索引裁剪决定真正需要读取多少数据。
最佳优化通常从“减少读取”开始。扫描字节下降一个数量级,CPU 解压、网络传输和内存占用也常随之下降。
7.2 内存承载执行状态
Hash Join 的 Build 表、Aggregation 的分组状态、Sort 缓冲区、Exchange 队列和缓存都会占用内存。并发查询数量增加时,单条查询内存看似合理,集群总内存仍可能触顶。
Doris 使用内存跟踪机制记录进程、查询、Fragment 和算子等层次的消耗。达到限制时,查询可能被取消;支持 Spill 的算子可以把部分状态写入磁盘,以延迟换稳定性。
7.3 CPU 完成解码与算子计算
解压、表达式求值、Hash、比较、聚合和序列化都会消耗 CPU。向量化、SIMD 和 Cache 亲和性提高单位 CPU 的处理效率。
CPU 使用率高未必意味着异常。高吞吐分析查询理应充分使用 CPU。需要关注的是:处理了多少有效数据,是否存在大量无效扫描,单核是否被热点 Instance 占满,算子是否因数据倾斜拖尾。
7.4 网络连接分布式阶段
网络负责计划下发、Exchange、Runtime Filter、心跳与结果传输。大规模 Shuffle 对带宽和延迟很敏感,跨可用区或跨机房部署会放大影响。
网络瓶颈常见于大表 Join、全局高基数聚合、Broadcast 误判、热点 Key 和超大结果集。表分桶、Colocate、局部聚合和合理 Join 顺序都能减少网络流量。
7.5 用资源乘法理解查询成本
可以用一个简化模型理解成本:
查询成本 ≈ 扫描数据量
× 每行计算复杂度
× 数据交换比例
× 并发竞争系数
减少任意一项都可能带来收益。分区裁剪降低扫描量,物化视图降低计算复杂度,Colocate 降低交换比例,Workload Group 降低并发干扰。
八、慢查询分层定位:先找阶段,再找资源

遇到慢查询时,建议按照固定顺序排查。
8.1 先确认问题是否真实发生变化
记录 SQL 文本、用户、数据库、版本、执行时间、并发、数据量、导入任务和集群变更。确认下列问题:
- SQL 是否完全相同;
- 参数和时间范围是否变化;
- 数据是否刚完成大批量导入;
- 统计信息是否更新;
- 集群是否扩缩容、升级或发生节点异常;
- 同期是否有 Compaction、Schema Change 或大导入;
- 冷缓存与热缓存是否被混在一起比较。
没有可比条件的“昨天 2 秒、今天 10 秒”很难直接归因。
8.2 查看 EXPLAIN
EXPLAIN 用来观察计划形态。重点关注:
- 分区命中数量;
- Tablet 扫描数量;
- Join 顺序与 Join 分布;
- Broadcast、Shuffle、Colocate 等选择;
- Runtime Filter;
- 物化视图改写;
- 聚合阶段与 Exchange 边界。
EXPLAIN 说明计划准备怎样执行,无法给出真实算子耗时。计划看起来合理,运行时仍可能受数据倾斜、缓存、I/O 和并发影响。
8.3 读取 Query Profile
Profile 记录实际执行情况。不同版本字段名称可能变化,观察思路保持稳定:
| 层次 | 重点观察 | 可能问题 |
|---|---|---|
| FE | Planning、Analyze、Optimize、Schedule | 元数据、统计信息、优化器、连接与排队 |
| Scan | Rows Read、Bytes Read、过滤率、I/O 时间 | 裁剪失败、索引缺失、文件过多、存储慢 |
| Join | Build 行数、Probe 行数、Hash Table 内存 | Build 侧过大、统计信息错误、倾斜 |
| Agg / Sort | 输入行数、分组数、峰值内存、Spill | 高基数、结果集过大、内存不足 |
| Exchange | Send/Receive 字节、Wait、队列 | Shuffle 大、Broadcast 错误、网络拥塞 |
| Instance | 最大耗时与平均耗时差距 | 数据倾斜、节点性能差异、热点 Tablet |
| Result | 返回行数、Sink 与 Fetch 时间 | 大结果集、客户端慢、代理限制 |
8.4 再决定优化动作
诊断结果不同,动作也应不同:
- 扫描过大:调整分区、排序键、索引或 SQL 条件;
- 计划错误:更新统计信息,检查 Join 顺序和分布;
- Shuffle 过大:局部聚合、Colocate、Bucket Shuffle 或调整模型;
- 数据倾斜:拆分热点 Key、预聚合、盐值或改写 Join;
- 内存过高:降低并发、优化 Build 侧、启用合适 Spill、调整资源组;
- I/O 受限:减少文件、治理 Compaction、优化缓存与存储介质;
- 返回过慢:减少结果集、增加分页、优化客户端 Fetch Size。
参数调优应放在明确瓶颈之后。盲目提高线程数、并行度和内存限制,可能让资源竞争更加严重。
九、动手实验:标注一条 JOIN 与聚合 SQL 的执行链路
下面的实验适合单机或测试集群。为方便本地练习,示例使用单副本;生产环境应根据高可用要求设置副本数。
9.1 创建维表
CREATE DATABASE IF NOT EXISTS day03;
USE day03;
CREATE TABLE dim_city (
city_id INT,
city_name VARCHAR(64),
region VARCHAR(32)
)
DUPLICATE KEY(city_id)
DISTRIBUTED BY HASH(city_id) BUCKETS 4
PROPERTIES (
"replication_num" = "1"
);
9.2 创建事实表
CREATE TABLE fact_orders (
order_date DATE,
city_id INT,
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(18, 2)
)
DUPLICATE KEY(order_date, city_id, order_id)
DISTRIBUTED BY HASH(city_id) BUCKETS 4
PROPERTIES (
"replication_num" = "1"
);
9.3 插入少量样例数据
INSERT INTO dim_city VALUES
(1, '北京', '华北'),
(2, '上海', '华东'),
(3, '广州', '华南'),
(4, '成都', '西南');
INSERT INTO fact_orders VALUES
('2026-08-01', 1, 1001, 501, 120.50),
('2026-08-01', 1, 1002, 502, 230.00),
('2026-08-01', 2, 1003, 503, 80.00),
('2026-08-02', 3, 1004, 504, 310.00),
('2026-08-02', 4, 1005, 505, 160.00);
9.4 查看执行计划
EXPLAIN VERBOSE
SELECT
c.region,
SUM(o.amount) AS total_amount
FROM fact_orders o
JOIN dim_city c
ON o.city_id = c.city_id
WHERE o.order_date >= '2026-08-01'
GROUP BY c.region
ORDER BY total_amount DESC;
不同版本和数据规模下,节点名称与 Join 分布可能不同。请在结果中寻找以下线索:
- 哪些部分属于 Scan;
- Join 使用了什么分布方式;
- 是否生成 Runtime Filter;
- 有几个 Exchange;
- 聚合是否分为 Local 与 Global 两层;
- 哪个 Fragment 负责 Result Sink;
- 哪些阶段由 FE 决定,哪些阶段在 BE 运行。
9.5 完成交付物
把 EXPLAIN 复制到文档中,用四种标记完成注释:
- 蓝色:FE 解析、优化与调度;
- 绿色:BE Scan、Join、Agg、Sort;
- 橙色:Exchange 与数据分布;
- 紫色:结果合并与返回。
最终产出一张《一条 SQL 的端到端链路注释图》。当后续学习 Profile、Join 调优和存储引擎时,可以继续在这张图上补充指标。
十、版本与表达边界
Doris 的 FE、BE、Fragment、Instance、Exchange、列式执行和 Pipeline 等核心概念在 4.x 中保持稳定,具体实现会持续演进。阅读旧资料时需要注意以下事项。
第一,旧课件可能把 MPP 描述成“所有中间数据都在内存中流转”。当前 Doris 支持 Spill,大查询可以使用磁盘承载部分中间状态。更准确的表达是:Doris 采用长驻 MPP 服务和流水线 Exchange,优先在内存中处理数据,并提供落盘能力保障大查询稳定性。
第二,解析器、优化器内部类名、PipelineX 名称、算子名称和 Profile 字段可能变化。学习者应抓住“解析—绑定—优化—切分—调度—执行—交换—返回”的稳定流程,再用目标版本的 EXPLAIN 和 Profile 验证细节。
第三,存算分离架构中,持久化数据位于共享对象存储,计算节点通过本地缓存加速访问,元数据组件也与经典 FE + BE 形态有所区别。今天的查询执行概念仍然适用,部署拓扑和数据路径会在 Day 4 单独说明。
第四,性能数字必须放在明确条件下理解。宽表聚合、点查、大表 Join 和日志搜索的资源路径不同,无法用一个 QPS 或一条 benchmark 代表全部业务。
十一、Knowledge Check
题目 1
FE 与 BE 各自承担哪些核心职责?
答案要点: FE 负责连接、鉴权、元数据、解析、绑定、优化、计划和调度;BE 负责数据存储、扫描、向量化算子、Pipeline 调度、Exchange 和结果生成。
题目 2
Fragment 与 Instance 有什么区别?
答案要点: Fragment 是被 Exchange 边界切分出来的逻辑执行阶段;Instance 是 Fragment 在某台 BE 上运行的物理副本。一个 Fragment 可以生成多个 Instance。
题目 3
Exchange 为什么重要?
答案要点: Exchange 连接不同 Fragment 和 Instance,承担 Broadcast、Shuffle、Gather 等数据交换。它决定跨节点网络流量,也常形成分布式查询的性能边界。
题目 4
向量化为什么能降低函数调用成本?
答案要点: 算子一次处理一个列式 Block,函数调用和分支判断由一批数据共同分摊,连续内存访问也有利于 CPU Cache 和 SIMD。
题目 5
Pipeline 解决的主要问题是什么?
答案要点: Pipeline 使用可调度 Task 和固定线程池,任务阻塞时主动让出线程,减少线程膨胀与上下文切换,提高多查询环境下的多核利用率。
题目 6
MPP 的并行度受哪些因素影响?
答案要点: BE 数量、Tablet 和 Scan Range、Bucket 数、数据副本分布、Pipeline 并行设置、Workload Group、数据倾斜和节点资源状态都会影响并行度。
题目 7
EXPLAIN 与 Profile 各自回答什么问题?
答案要点: EXPLAIN 展示计划准备怎样执行;Profile 记录计划实际怎样运行,包括扫描量、算子耗时、内存、网络等待和 Instance 差异。
十二、本日总结
今天建立了 Doris 查询架构的第一张完整地图。
FE 管理控制路径:接收连接,理解 SQL,读取元数据,完成语义检查,利用 Nereids 生成物理计划,再把 Fragment Instance 调度到 BE。
BE 承担数据路径:读取 Tablet 与 Segment,完成裁剪、解码和过滤,通过向量化算子执行 Join、聚合与排序,使用 Pipeline 调度多核任务,必要时通过 Exchange 在节点之间传输数据。
MPP、Pipeline 和向量化分别覆盖集群、节点和算子三个层次。列式存储负责减少无关 I/O,编码压缩减少数据体积,CPU Cache 与 SIMD提高批量计算效率,局部聚合和 Runtime Filter降低网络与后续算子压力。
当查询变慢时,可以沿着同一张地图定位:先区分 FE 规划、BE 执行、网络交换和结果返回,再使用 EXPLAIN 与 Profile 查找扫描量、算子时间、内存、Shuffle 和数据倾斜。
明天进入 Day 4:存算一体、存算分离与版本选择。届时会讨论本地盘性能、对象存储成本、计算组弹性、缓存冷启动,以及 Latest 与 Stable 如何进入生产治理。
官方资料
- Apache Doris 4.x:What is Apache Doris
https://doris.apache.org/docs/4.x/getting-started/what-is-apache-doris/ - Apache Doris 4.x:System Architecture
https://doris.apache.org/docs/4.x/features-architecture/system-architecture/ - Apache Doris 4.x:Product Concepts
https://doris.apache.org/docs/4.x/features-architecture/product-concepts/ - Apache Doris 4.x:MPP Architecture
https://doris.apache.org/docs/4.x/key-features/mpp/ - Apache Doris 4.x:Data Pruning
https://doris.apache.org/docs/4.x/key-features/data-pruning/ - Apache Doris 4.x:Basic Concepts of Partitioning and Bucketing
https://doris.apache.org/docs/4.x/table-design/data-partitioning/basic-concepts/
资料核对日期:2026-08-16。网站正式发布时,建议把官方链接、版本范围和最后核验时间作为页面元数据保存。