第4章 执行器与算子实现
执行器是数据库内核的“发动机”,负责按照计划树驱动算子拉取数据并计算。面试中常围绕火山模型、向量化执行、Join算法和聚合算子展开,重点考察对逐行处理与批量处理差异、不同Join策略取舍、内存管理与性能优化的理解。本章通过三个问答卡片和一个实战项目模板,帮你快速掌握核心考点。
5.1 火山模型 vs 向量化执行框架
面试问答卡片
Q1:请解释火山模型的工作原理及其优缺点。
面试官考察点:对经典执行器模型的理解,以及能否指出其性能瓶颈。
回答框架:
- 给出火山模型定义与核心接口(next())
- 说明算子间的管道化协作
- 列出优点:实现简单、易扩展
- 指出缺点:逐行虚函数调用、CPU cache不友好、无法利用SIMD
示例回答:火山模型基于迭代器模式,每个算子通过next()逐行返回数据。优点在于算子实现独立,便于添加新算子;缺点是每行数据都经过多次虚函数调用,导致CPU分支预测失败率高,且无法利用SIMD指令,成为现代OLAP引擎的主要瓶颈。
可能追问:火山模型在MPP架构下如何优化?能否用编译执行替代?如何减少next()调用次数?
避坑提醒:不要只说“逐行处理效率低”,要具体解释虚函数开销、缓存不命中、以及与现代硬件(SIMD、多核)的冲突。
Q2:向量化执行如何提升性能?请结合具体算子说明。
面试官考察点:对列式存储与批量处理的理解,能否延伸到实际算子优化。
回答框架:
- 解释向量化执行的核心思想(批量处理 + 列式内存布局)
- 对比火山模型,说明减少虚函数、提升数据局部性
- 举例:Hash Join中批量计算hash值、过滤算子中利用SIMD做谓词判断
- 提及实现难点(变长字段处理、向量化函数库)
示例回答:向量化执行将数据组织为列式批量(如1024行),算子内部用向量化函数一次处理整批。例如聚合算子中,对整数列用SIMD指令并行累加,相比逐行累加提速5-10倍。同时虚函数调用次数降低至1/1024,CPU流水线更稳定。
可能追问:向量化执行对内存管理有何要求?如何支持变长字符串?ClickHouse和DuckDB的实现差异?
避坑提醒:避免只说“批量处理更快”,要提到列式布局、CPU缓存行利用、SIMD指令集(SSE/AVX)等底层机制。
Q3:请比较Hash Join、Merge Join与Nested Loop Join的适用场景与优缺点。
面试官考察点:对常见Join算法复杂度、内存模型、数据分布敏感度的掌握。
回答框架:
- 简要介绍三种Join的核心思想
- 从时间复杂度、内存需求、排序要求、连接键类型等维度对比
- 给出典型选型建议:等值且小表可全内存→Hash Join;有序大表→Merge Join;非等值或数据量极小→Nested Loop
示例回答:Hash Join适合等值连接且内表可放入内存,复杂度O(N+M),但需Hash表构造时间;Merge Join需两表已排序,适合有序场景;Nested Loop Join实现简单,但最坏O(N*M),适用于小表或非等值连接。实际引擎常根据统计信息自动切换。
可能追问:Grace Hash Join如何处理内存不足?Bloom Filter如何优化Hash Join?Hybrid Hash Join的实现细节?
避坑提醒:不要直接说“Hash Join最快”,要强调内表大小、内存限制;也要注意Merge Join在外表有序但内表无序时需要排序,可能整体代价更高。
5.2 Join算法面试追问路径导图
(从Hash Join出发,面试官常沿着内存、数据倾斜、优化手段等方向追问)