系列导航:面试索引 · 代码评审与 Agentic 研发
数据库内核的优化要先证明语义,再比较代价;执行与维护还要交代状态所有者和失败恢复。 沿查询计划、批处理、增量维护到湖表提交追问,同一条链上的局部收益必须满足上层可见性与资源契约。
一、优化器:先证明等价,再比较代价
1. 逻辑等价与物理代价为什么必须拆开?
强答要点:先声明改写的输入输出关系是否保持同一多重集语义(或多集基数、空值与列顺序约定),再讨论代价。谓词下推(Predicate Pushdown)在内连接(Inner Join)上通常安全;一旦出现外连接(Outer Join)、反连接(Anti Join)、窗口函数分区边界或非确定性函数,就必须证明谓词是空值拒绝(Null-Rejecting)的,以及它落在保留侧还是空值供给侧。
生产 Cascades 路径会把逻辑扩展、物理实现与 Enforcer(如 Exchange/Sort)放进同一代价闭环;教学实现若只有单个 Winner、未接入物理属性,就不能声称已覆盖分布式排序与分布联合选择。对照 optd Cascades 源码剖析 与 StarRocks Optimizer:Property 与 Enforcer。
追问:“WHERE coalesce(t2.x, 0) > 0 能否下推过 t1 LEFT JOIN t2?”答:该谓词拒绝 t2.x 为 NULL 的补行,可能把外连接变成内连接语义,通常不能直接下推到 null-supplying 侧;“代价模型显示下推后更便宜,是否足以合入?”答:代价只在等价成立后才有意义;不等价的便宜计划是错误计划。
差答:“外连接也能随便下推”“CBO 选出来的一定对”“加了单元测试就等价”。
2. Cascades / Memo 与 System R 式动态规划差在哪里?
强答要点:System R 风格动态规划(Dynamic Programming,DP) 主要在固定算子集合上按连接顺序枚举并复用子问题最优解。Cascades 用备忘录(Memo)把逻辑等价类(Group)与组内表达式(Group Expression)分开,让规则探索与物理实现交错发生,并用当前最优代价做分支限界(Branch-and-Bound)。父节点还可提出物理属性要求(分布、排序),子计划不满足时插入 Enforcer 并计入代价。
大规模连接顺序搜索在实践中常配合启发式或受限枚举;PostgreSQL 对超过阈值的连接使用遗传查询优化器(Genetic Query Optimizer,GEQO),见 PostgreSQL GEQO 文档。Leis 等人 PVLDB 2015《How Good Are Query Optimizers, Really?》 用真实基数误差展示:代价模型与基数估计的偏差往往比“枚举算法名词”更能解释计划质量。
追问:“同一个 Group 为什么可能有多个 Winner?”答:不同 Required Property 对应不同最优物理计划;“规划超时后怎么保证还能给出可执行计划?”答:保留当前上界、启发式初始计划或阶段降级;需说明是否可能返回次优但语义正确的计划。
差答:把 Memo 说成“缓存 SQL 文本”;声称遗传算法一定优于 DP;用“工业界都用 Cascades”回避基数误差。
3. 空值拒绝谓词与外连接:Dynamic Filter 能不能穿?
强答要点:分开讨论生成与穿越。Filter 只能无假阴性地缩小 Probe 集合;一旦 Outer Join 改变匹配状态并补 NULL,上层谓词若依赖这些补行,穿越就可能改语义。典型反例是对补 NULL 仍为真或仍可通过的表达式(如某些 coalesce / IS NOT DISTINCT FROM 组合)。
工程对照与统一判据见已发表笔记 Dynamic Filter 穿越 Outer Join:生成侧通常不当自己的保留侧 Producer;穿越要求落到保留侧后再有向派生,并显式处理 Null-Safe 等值。
追问:“Inner Join 上 DF 的正确性条件是什么?”答:Build 摘要可有假阳性、不可有假阴性(对等值过滤语义而言);“Left Join 的保留侧与 null-supplying 侧,谁可以接收父 DF?”答:先画保留/供给,再套判据表,不背系统名。
差答:“过滤越多越正确”“某引擎下推更深所以更优”。
4. Join Reorder:何时穷举,何时启发式?
强答要点:连接数、图形状(链式 / 星型 / 环)、是否有外连接与谓词依赖,共同决定搜索策略。穷举 DP 在表数上升时组合爆炸;贪婪、GOO 类启发式或 GEQO 用可接受的次优换规划时延。坏计划的对外解释应落到基数估计、相关谓词、数据倾斜与超时降级,而不是“优化器随机”。
Neumann & Radke, PVLDB 2018《Adaptive Optimization of Very Large Join Queries》 讨论超大连接查询的自适应优化思路;与 Leis et al. 2015 一并说明:枚举能力不等于真实基数下的最优。
追问:“星型模型为何常比环状好搜?”答:维度表选择性与 FK 结构可大幅剪枝;“外连接顺序能自由交换吗?”答:一般不能;关联与空值生成顺序受语义约束。
差答:断言“我们永远最优”;把一次 TPC-H 数字当成所有客户负载。
5. 查询改写与 MV Matching:匹配成功不等于可以透明替换
强答要点:改写要同时满足结构匹配(Join / Grouping / 输出列)、补偿谓词可推导、聚合滚上(roll-up)合法,以及新鲜度(Freshness)策略允许。视图定义含非确定性函数、不完全覆盖的 WHERE、外连接方向不一致、或刷新尚未追上查询所需快照时,应拒绝或走 query-time 校验路径。
追问:“COUNT(*) 的 MV 能否回答带额外 WHERE 的查询?”答:仅当过滤可下推到保存的明细粒度或存在可补偿结构;“基表刚写入,MV 延迟 2 分钟,查询要不要用 MV?”答:由会话一致性 / 最大陈旧时间 SLA 决定,不是优化器单方面决定。
差答:“命中 MV 一定更快”“刷新成功就语义正确”。
6. 基数估计错了,代价模型还可信吗?
强答要点:代价是在估计基数上的相对比较。单列直方图对独立假设友好;列相关、连接跨列相关会系统性低估或高估。抽样受倾斜影响;上界/稳健优化在最坏情况与平均情况之间取舍。面试官要听的是:你如何发现估计误差、如何用反馈或强制统计修复,而不是背公式。
Leis et al., PVLDB 2015 表明真实负载中基数误差可跨数量级,且对计划质量影响往往大于枚举算法差异。后续工作继续讨论何时需要自适应或端到端学习估计,但生产合入仍要可回退。
追问:“两个谓词 AND 时为何不能直接乘选择率?”答:独立性假设常不成立;“连接后基数如何估计?”答:基于 FK/唯一性、直方图拼配或采样;说明失败时的保守策略。
差答:把代价数字的绝对值当成物理时间;否认相关谓词。
7. 物理属性:排序、分布与 Enforcer 成本
强答要点:父算子可能要求孩子按某键排序或按某方式分布。孩子局部最优若不满足属性,需要 Exchange/Sort 等 Enforcer,其成本必须计入。缺少属性维度的 Winner 表会漏掉全局更优组合。详见 StarRocks Optimizer 笔记。
追问:“Hash 分布与 Range 分布各利哪类操作?”答:等值连接 vs 范围/有序聚合等;“CTE 物化与属性传递如何互相影响?”答:物化点改变可复用属性与重复计算权衡。
差答:只比较算子 CPU 忽略网络;认为排序属性可以“事后再说”。
二、向量化执行与 SIMD:批处理赚的是解释开销,不是魔法倍数
8. 为什么向量化,而不是一次一行?
强答要点:收益主要来自减少虚调用与解释开销、提高指令与数据局部性、让编译器/SIMD 有机会作用在紧凑循环上。批大小(如数百到数千行)是缓存与流水线的折中。当表达式极简单且数据已在缓存,或当每行都要做复杂字典解码、大量分支、宽行物化时,向量化优势会缩小甚至被物化成本抵消。
追问:“批太大会怎样?”答:缓存失效、延迟尾部变差、内存峰值上升;“选型选算子间物化还是流水线融合?”答:融合减物化,但增大寄存器压力与代码生成复杂度。
差答:“上了 SIMD 就十倍”“向量化等于编译执行”。
9. SIMD 何时值得写?Null 位图与字典编码怎么干扰?
强答要点:先测量标量紧凑循环是否已受限于内存带宽。SIMD 对均匀、无分支、定长类型最友好;Null 位图强制掩码合流,字典编码可能先解码再比较或在字典空间比较。错误的“先解码成行存再 SIMD”可能比标量列式更慢。
Staff 应能讲清:剩余谓词、选择率、是否可延迟物化(Late Materialization)。没有剖析的 SIMD 补丁在评审里应被要求补基准与正确性(对齐、尾块、页面边界)。
追问:“选择率极低时 SIMD 过滤仍有用吗?”答:可能;但更应考虑下推、索引、zonemap / 统计跳过;“变长字符串怎么 SIMD?”答:通常先固定长度化、结束符扫描或自动机;很多路径保持标量。
差答:用合成全非空 int32 基准推广到生产宽表;忽略尾部不足一个向量的标量收尾。
三、物化视图(Materialized View,MV)、IVM 与 Dynamic Table:增量是能力,也是状态债务
10. 增量维护的代数直觉与非单调算子
强答要点:对视图 \(V=Q(DB)\),增量视图维护(Incremental View Maintenance,IVM)试图用 \(\Delta DB\) 计算 \(\Delta V\)。选择/投影等较易线性化;Join 展开为 \(\Delta R \bowtie S\)、\(R \bowtie \Delta S\) 与高阶交叉项,多表时中间状态急剧变大。MIN/MAX/COUNT DISTINCT/部分窗口函数等非单调算子在撤回(Retraction)时可能需要历史细节,空间换时间或退回分区全量。
工业界常见产品化形态是:声明新鲜度目标 + 自动调度刷新 + 有限算子覆盖。Snowflake 文档描述 Dynamic Table 以目标延迟等参数管理刷新,见 About dynamic tables。Databricks 文档将物化视图与 Delta Live Tables / 声明式流水线关联,见 Delta Live Tables。具体算子矩阵以各云当前文档为准,面试时应声明阅读日期。
追问:“只有 INSERT 的 append-only 流水线,哪些聚合可以安全增量?”答:如 SUM/COUNT 等可交换聚合;注意 NULL 与溢出;“来了一条 DELETE,MAX 怎么维护?”答:保留多重集 / Top-k 结构,或标记失效后重算该组。
差答:“IVM 总是比全量便宜”“流式系统没有一致性问题”。
11. 查询时用 MV 的正确性与运维接口
强答要点:分层: (1) 改写是否在语义上合法;(2) MV 刷新水位与表快照;(3) 会话要求的一致性;(4) 分区部分就绪时的可见性;(5) 是否存在 query-time 回退扫基表。把“旧”说成优化器 bug 之前,先拿出刷新任务状态与版本号。
【模板】:案例〔项目〕;暴露的 SLA〔填写〕;水位落后原因〔填写〕;本人改动〔检测 / 拒绝改写 / 调度〕;验证〔填写〕;未覆盖〔填写〕。
追问:“如何向用户解释 TARGET_LAG / interval?”答:用最大可接受陈旧时间,而不是“实时”;“刷新失败会不会继续提供旧数据?”答:产品策略:失败开放旧数据、失败即不可用、或混合。
差答:只调大刷新频率;不测量状态存储成本就承诺秒级全面增量。
四、存算分离:计算节点“无状态”仍拥有什么?
12. 缓存、粘滞与冷启动
强答要点:持久数据在共享存储;计算节点仍拥有:内存缓存、临时溢写、执行中算子状态、锁/租约、本地计划缓存与连接。宕机丢失的是未提交的中间态与缓存热度,不是已提交表数据——前提是写入路径的持久化语义被正确实现。
云数仓公开工程叙述常强调多计算集群共享一份存储、弹性扩缩;例如 Amazon Redshift 的演进讨论见 arXiv:2212.03970。具体产品保证以官方文档为准,不能把论文架构图当成所有引擎的行为。
追问:“缓存一致性如何维护?”答:版本号 / 世代 / 租约;更新后如何让旧缓存失效;“粘滞调度(sticky assignment)的收益与风险?”答:收益是缓存命中;风险是热点与故障转移变慢。
差答:“计算完全无状态所以随便杀”“存算分离等于没有本地盘”。
五、执行资源:倾斜、排队与取消
13. Hash Join、Shuffle 与倾斜
强答要点:先区分分区倾斜与值倾斜。Hash Join 在内存不足时溢写(Spill);Shuffle Join / Broadcast Join 的选择应回到基数与网络代价,而不是“大表小表”口诀。倾斜时加机器可能放大 Shuffle 热点;更有效的是隔离热键、两阶段聚合、临时统计或强制分散。
用一次真实慢查询说明:热键占比、Spill 字节、网络等待与 CPU 占比。没有这三项,不建议谈“优化了 Join”。
【模板】:查询〔〕;原计划〔〕;热键证据〔〕;改写 / hint / 统计修复〔〕;结果分位数〔〕。
追问:“Broadcast 的正确性条件?”答:被广播侧在该片段语义下可复制;外连接方向与空值仍要单独检查;“Spill 到本地盘与存算分离是否冲突?”答:临时状态仍可能本地化;节点丢失会重算该片段。
差答:只说“加并行度”;把 Spill 成功当成性能成功。
14. 资源组、排队与取消
强答要点:隔离维度包括 CPU、内存预留、并发槽位与扫描带宽。排队策略要公开可观测。取消必须能传播到远程片段并保证回调不再访问已释放批;否则会出现 use-after-free 式崩溃,这与 C++ 篇的生命周期题是同一类证据。
追问:“硬限与软限的差别?”答:硬限拒绝/杀死;软限降级或延后;“如何证明取消安全?”答:故障注入 + 地址消毒 / 生命周期断言。
差答:“杀掉会话就行”;不考虑远程 fragment 仍在写共享缓存。
六、湖仓与 Iceberg:表格式契约,不是执行引擎本身
15. 快照隔离与提交冲突
强答要点:读计划基于表元数据中的快照(Snapshot);数据文件列表与删除文件在规划时确定,执行期通常不追逐并发写入者的最新提交。写路径通过原子提交元数据实现乐观并发;冲突粒度在分区/文件/删除向量等层,取决于操作类型与规格版本。
Apache Iceberg 规格 定义了快照、清单、分区演化与删除文件等概念。V3 用删除向量(Deletion Vector,DV)替代新写的位置删除(Position Delete)路径(equality delete 仍有独立规则);格式 v4 是否 formal adopt 必须以社区公开材料为准,本文不宣称已采纳。V3 删除向量的并发边界与 V4 进行中变更另有未发表笔记,面试只引用规格页已写明部分,并把未合入提案表述为进行中讨论。
追问:“position delete 与 equality delete 的读代价差在哪?”答:位置删除指向文件内行;等值删除需按表达式匹配,规划与运行期代价不同;“DV 让删除更便宜后,并发语义有什么新风险?”答:冲突/合并粒度变化;不相交行更新仍可能在文件级冲突——以规格与实现为准。
差答:把 Iceberg 说成“另一种存储引擎”;声称“湖仓自动解决所有一致性”;把未采纳的 v4 提案写成现状。
16. 引擎读 Iceberg:规划期决策与运行期残余
强答要点:剩余文件的删除文件处理、DV 合并、残余谓词、加密/压缩解码、对象存储尾延迟、以及运行期过滤都可能主导。规划剪枝是必要非充分。
追问:“清单文件太多会怎样?”答:规划 CPU/RPC 成为瓶颈;“近实时写入下快照爆炸如何治理?”答:合并压缩、快照过期、水位与读写分离。
差答:只优化执行算子,不看规划元数据路径。
17. 元数据服务与规划延迟
强答要点:规划路径上的 RPC、清单迭代、串行加锁与重复反序列化常被忽视。优化方向包括清单缓存、分区统计摘要、并行 listing、以及把部分剪枝下推到元数据过滤表达式。要用规划阶段火焰图,而不是只盯着 BE CPU。
追问:“规划缓存的失效键是什么?”答:表版本 / 快照 ID / 会话时区与目录配置;“缓存击穿时如何保护元数据服务?”答:单飞(singleflight)、限流、降级扫描策略。
差答:一上来改执行算子;用扩大超时掩盖规划算法问题。
七、分布式正确性:重试、幂等与版本倾斜
18. RPC 至少一次与幂等
强答要点:网络常见至少一次(at-least-once)投递。幂等键、去重表、正好一次语义通过“至少一次 + 去重/事务状态机”逼近。查询只读路径也要考虑:重试是否重复扫描计费、是否重复取消、是否让同一请求附着两个查询 ID。
OLAP 写入(导入、刷新 MV、提交湖表)比只读 Scan 更需要显式幂等设计;面试应用一个真实导入/刷新例子画状态机。
【模板】:请求 ID〔填写〕;服务端去重状态存活期〔填写〕;超时与成功并发的处理〔填写〕;观测指标〔填写〕。
追问:“谁生成幂等键?客户端还是服务端?”答:各有代价;关键是冲突检测窗口;“去重状态丢了会怎样?”答:可能双写;需存储级别的持久去重或可重收敛数据结构。
差答:“TCP 保证正好一次”“我们用了 Raft 所以业务幂等”。
19. 版本倾斜:FE/BE、规划器/执行器、滚动升级
强答要点:序列化协议、计划片段字段、函数签名、分布方式枚举、错误码。需要:版本门闸、特性旗标、双读/双写窗口、以及“旧节点拒绝新计划”的明确错误而不是静默错结果。
追问:“向前兼容与向后兼容你保证哪边?”答:滚动方向决定;通常先升级能理解旧协议的节点;“遇到未知 plan node 怎么办?”答:失败要响亮;默认忽略会导致错结果。
差答:假设“protobuf 加字段永远兼容”;用生产集群当第一次混版实验。
20. 导入 Exactly-once 与 EOS(End of Stream)
强答要点:端到端 exactly-once 通常由“可重放源 + 幂等汇 + 对齐检查点”组成。OLAP 侧要定义:事务标签、分区提交、失败重放窗口。EOS 标记与延迟数据(late data)策略要写进契约,否则“不丢”与“实时”互相打架。
追问:“源端至少一次时,汇端如何去重?”答:主键 / 幂等键 / 序列号窗口;“检查点成功但下游未可见?”答:可见性与提交分离;读需等待发布。
差答:把 Kafka offset 提交等同于表提交;忽略跨分区事务。
八、OLAP 语境下的事务与隔离:少谈写偏斜,多谈快照与新鲜度
21. 分析读需要哪种隔离?
强答要点:多数分析读需要的是点内一致快照:同一查询看到的基表/文件集合不中途换成另一提交。写偏斜、幻读等 OLTP 考题要会,但不是每天主导 OLAP 故障。真正高频的是:长查询与并发导入之间的快照选择、MV 与基表混读的新鲜度、以及湖表提交后多久对引擎可见。
MySQL InnoDB 的 MVCC / 隔离级别材料足以证明你理解读写并发,但回答 OLAP 岗位时应主动切换到快照、分区与刷新 SLA。
追问:“查询开始后导入提交,结果包不包含新数据?”答:取决于快照时点(语句开始 / 事务开始 / 引擎特定);“两个查询是否需要彼此同一快照?”答:报表一致性需求;通常要额外协议。
差答:把 RR/RC 背诵当作内核深度;否认分析系统存在一致性问题。
九、把机制回答变成可核验材料
22. 如何用一个项目贯穿优化、执行与维护?
强答要点:选一条真实查询:展示改写前后计划、一次剖析火焰图、一次刷新/导入失败复盘。三层证据分别证明语义、性能与运维。避免三层各讲一个互不相关的故事。
【模板】:
| 字段 | 填写 |
|---|---|
| 查询 / 负载 | 〔〕 |
| 语义风险与反例 | 〔〕 |
| 计划关键决定 | 〔〕 |
| 执行瓶颈证据 | 〔〕 |
| MV / 湖表策略 | 〔〕 |
| 本人改动边界 | 〔〕 |
| 他人贡献 | 〔〕 |
| 残余限制 | 〔〕 |
追问:“拿掉你的改动,哪个指标先坏?”必须点名指标与负载;“为什么不采用更简单方案?”给出代价与否决证据。
差答:用团队总加速替代个人贡献;只有设计 PPT 没有回放数据。
附:本篇主题速查
| 主题 | 强答必须出现的词 | 最低证据 |
|---|---|---|
| 等价改写 | 反例 SQL、空值、外连接方向 | 一条挡住错误下推的测试 |
| 代价与搜索 | 基数误差、属性、超时降级 | 一次计划回归复盘 |
| 向量化 / SIMD | 批大小、剖析、编码/Null | 火焰图或计数器 |
| MV / IVM | 单调性、撤回、新鲜度 SLA | 刷新水位与改写拒绝原因 |
| 存算分离 | 缓存世代、冷启动、临时态 | 扩缩容分位数 |
| Iceberg | 快照、删除类型、提交冲突 | 规格条款 + 引擎版本 |
| 分布式 | 幂等键、混版矩阵 | 故障注入或兼容测试 |
验证边界
选一条真实 SQL,补齐计划、反例、刷新水位与本人修改;没有生产记录的部分保留【模板】。公开文档核验日期为 2026-10-24,回答时仍须注明引擎和格式版本。自测用错题库(08):其 DB 前缀各题取自本篇的机制边界,答不出成立条件或反例时回到对应题号补查。