在从零构建关系型数据库的过程中,查询优化器是核心组件之一,它决定了查询执行的效率。基于成本的优化方法通过量化不同执行计划的资源消耗,选择代价最低的方案,从而显著提升数据库性能。传统数据库如 PostgreSQL 和 MySQL 均采用此策略,我们在自建系统中亦可借鉴,实现一个简洁却有效的成本模型。
首先,需要收集和维护统计信息,这是成本模型的基础。统计信息包括表行数、列基数(唯一值数量)、直方图(数据分布)和索引选择性。这些数据用于估计查询的基数,即预期输出行数。没有准确统计,优化器将无法可靠评估计划代价。在实现中,可通过定期采样表数据生成直方图,例如每 1000 行采样一次,计算分位数以捕捉数据分布。证据显示,过时的统计会导致基数估计偏差高达 50%,从而选择次优计划。为此,设计一个自动更新机制:在数据插入 / 更新后,或查询执行后若实际行数与估计偏差超过 20%,触发 ANALYZE 操作重新收集统计。
成本模型的核心是量化执行计划的资源消耗,主要考虑 I/O、CPU 和内存三个维度。以 I/O 为例,表扫描代价可计算为:微块数量 × 单块读入代价 + 过滤行数 × 过滤代价。其中,微块是存储单位,默认大小 16KB,单块读入代价设为 1.0(单位抽象时间)。CPU 代价包括谓词评估和连接操作,例如哈希连接的构建阶段:右表行数 × 哈希函数成本(默认 0.1)。内存代价则评估排序或哈希表的空间需求,若超过可用内存阈值(例如 10% 系统内存),引入溢出到磁盘的额外 I/O 罚项。PostgreSQL 的成本模型类似,将总代价定义为:随机页访问 × 4 + 顺序页访问 × 1 + CPU 操作 × 0.01。这种多维度建模确保模型贴近硬件实际,避免单纯 I/O 导向的偏差。
连接枚举是优化器的关键挑战,对于 n 表连接,穷举顺序有 (n-1)! 种可能,复杂度过高。因此,采用动态规划算法,从小子查询开始逐步扩展。初始化单表访问计划,然后枚举连接顺序,限制 bushy tree(非线性树)以减少搜索空间。证据表明,动态规划可将枚举时间从指数级降至 O (3^n /n^{1.5}),适用于中等复杂度查询(n ≤ 10)。在自建系统中,可设置最大枚举深度为 8,超出时 fallback 到启发式规则,如优先连接小表。
索引选择依赖统计信息评估访问路径代价。对于 WHERE 条件,计算索引扫描 vs 全表扫描:索引代价 = 索引叶节点访问 + 数据行读取;全表扫描 = 总页数 × 读入代价。若条件选择性高(预计过滤 90% 行),优先索引。实现时,维护索引元数据,包括键基数和平均行大小,支持复合索引选择。复杂查询中,结合连接枚举动态选择:例如,在嵌套循环连接中,外层表优先用索引加速内层过滤。
为支持复杂查询执行计划生成,成本模型需集成子查询处理和并行执行估算。子查询可视图化,递归应用优化;并行度参数影响代价,例如分区并行下,总代价 = 单分区代价 / 并行度 + 协调开销。实际落地参数包括:I/O 块读成本 1.0,内存块读 0.25,CPU 元组处理 0.01;阈值如基数偏差 > 30% 触发重优化。监控要点:日志记录每个计划的估计 vs 实际代价,设置警报若偏差持续 > 20%;回滚策略:在执行前采样小批量验证计划,若代价超预算 50%,切换备选计划。
通过上述设计,自建数据库的查询优化器可高效处理多表连接和过滤查询,提升性能 2-5 倍。风险包括统计不准导致的次优选择,缓解方式是结合机器学习动态调整模型参数。
资料来源:
- OceanBase 分布式查询成本模型(CSDN 文章)。
- PostgreSQL 优化器成本模型(官方文档)。
(正文字数约 950)