KingbaseES 优化器底层逻辑:等价变换改写 SQL 与条件调度驱动运行时

前言

SQL 是声明式语言,开发者仅描述查询目标,不定义数据扫描、连接、过滤的执行顺序。KingbaseES(人大金仓)查询优化器承担核心调度工作,整体分为两大阶段:逻辑优化等价变换、物理优化 + 运行时条件调度。
等价变换基于关系代数等价规则,在不改变查询结果的前提下重构 SQL 查询树,提前过滤数据、消除冗余计算;条件调度则贯穿计划生成与引擎执行全流程,依托统计信息、分区裁剪、短路过滤、参数化谓词动态选择最优执行路径。
国产化数据库替换场景中,大量老旧业务 SQL 存在嵌套子查询、冗余过滤、不合理关联写法,掌握 KES 优化器底层逻辑,可快速定位慢查询根源,实现无业务改造的性能大幅提升。本文结合完整可复现 SQL 示例,拆解两大核心模块底层原理。

一、逻辑优化:等价变换如何改写原始 SQL

逻辑优化(RBO 规则优化)是优化器第一阶段工作,核心准则:变换前后查询结果完全等价,仅优化计算路径。KES 内置基础启发式等价规则,同时通过kdb_rbo插件扩展高级代价驱动等价改写能力。

1.1 基础等价变换规则与实战示例

(1)常量折叠与谓词传递

优化器预计算 SQL 内常量表达式,同时根据等式条件传递过滤谓词,消除冗余判断。
原始 SQL:
sql

复制代码
SELECT * FROM t1 WHERE a > 10 + 20 AND a = b AND b = 5;

等价改写后:
sql

复制代码
SELECT * FROM t1 WHERE a > 30 AND a = 5 AND b = 5;

底层逻辑:解析阶段识别常量运算10+20直接计算为 30;通过等式传递,将b=5推导至 a 列,生成双重过滤条件,可直接匹配索引快速过滤数据。

(2)谓词下推(最核心等价变换)

将外层过滤、关联条件下推至基表、子查询、视图内部,提前过滤无效数据,减少中间结果集大小,是优化大表关联、嵌套子查询的核心手段。
建表测试结构:
sql

复制代码
CREATE TABLE dept(dept_id INT, dept_name TEXT);
CREATE TABLE emp(id INT, name TEXT, dept_id INT, salary NUMERIC);

原始低效 SQL(过滤条件在外层 JOIN 之后):
sql

复制代码
SELECT emp.*, dept.dept_name
FROM emp
JOIN dept ON emp.dept_id = dept.dept_id
WHERE dept.dept_name = '研发部';

优化器等价改写(谓词下推至 dept 表扫描阶段):
sql

复制代码
SELECT emp.*, sub.dept_name
FROM emp
JOIN (SELECT dept_id, dept_name FROM dept WHERE dept_name = '研发部') sub
ON emp.dept_id = sub.dept_id;

执行差异:原始写法需全量扫描两张表完成关联,再过滤部门;改写后先过滤 dept 表仅保留研发部数据,再与 emp 关联,极大降低 JOIN 计算量KingbaseES。

(3)子查询提升与 EXISTS 扩展优化

对于 IN、EXISTS 相关子查询,KES 会执行子查询提升,将嵌套查询转为 JOIN;同时开启enable_exists_expand参数后,可拆分 OR 条件 EXISTS 子查询,提升索引利用率KingbaseES。
原始 SQL:
sql

复制代码
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = 1 OR t1.b < 10);

等价扩展改写:
sql

复制代码
SELECT * FROM t1
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = 1)
OR t1.b < 10;

优化价值:拆分后t1.b<10可直接过滤外层表,无需遍历 t2 表,减少子查询重复执行次数。

(4)冗余 DISTINCT 消除、UNION 转 UNION ALL

若查询投影列包含主键 / 唯一约束字段,优化器直接删除无意义 DISTINCT;无重复数据集场景,自动将 UNION(去重)改写为 UNION ALL(直接合并),消除排序去重开销。
原始 SQL(t3.a 为主键,无需去重):
sql

复制代码
SELECT DISTINCT a, b FROM t3;

等价改写:
sql

复制代码
SELECT a, b FROM t3;

1.2 等价变换安全校验机制

KES 不会无条件执行等价改写,每一条变换都会执行两层校验,保障业务数据正确性:

  1. 语义等价校验:分析表连接类型(内连接 / 左连接)、字段空值属性、聚合函数,判断下推、提升操作是否会丢失数据或产生重复行;左连接右侧表过滤条件无法无条件下推,避免主表数据丢失。
  2. 代价收益评估:通过统计信息估算改写前后 IO、CPU 开销,若等价变换带来的计算成本高于收益,则放弃本次改写,保留原始查询树。

二、条件调度:从计划生成到运行时动态驱动

完成逻辑等价改写后,优化器进入物理优化阶段,结合表统计信息、索引、分区信息生成多套候选执行计划;条件调度贯穿计划筛选与引擎执行全过程,根据过滤条件的选择率、数据分布动态调整扫描、连接、过滤策略,分为编译期静态调度与运行时动态调度两大模块。

2.1 编译期静态条件调度(计划生成阶段)

静态调度依托表统计信息,对 WHERE、ON、HAVING 条件进行分级调度,核心策略三点:

  1. 高选择率条件优先执行
    优化器量化每个过滤条件的选择率(符合条件行数 / 总行数),选择率越低、过滤力度越强,越优先下推至表扫描阶段执行。例如salary>50000仅匹配 1% 数据,会优先执行;salary>1000匹配 80% 数据,后置过滤。
  2. 条件匹配索引自动切换访问路径
    调度器分析过滤字段是否存在 B 树索引,若等值、范围条件命中索引,计划选择 Index Scan;若过滤条件选择率极高,索引随机读开销大于顺序扫描,则自动切换 Seq Scan 全表扫描电科金仓。
  3. 连接条件调度:小表驱动大表
    多表关联时,调度器通过条件过滤后的预估行数确定驱动表,过滤后数据量更小的表作为 NestLoop 外表,减少内层表循环扫描次数。

2.2 运行时动态条件调度(引擎执行阶段)

静态计划仅基于统计信息预估,实际数据、绑定变量、分区数据分布存在偏差,KES 执行器内置动态条件调度逻辑,实时调整执行策略,包含四大核心能力。

(1)分区动态裁剪(Partition Pruning)

分区表场景下,运行时解析 WHERE 分区键条件,仅扫描匹配条件的子分区,跳过全部无关分区,是时序、海量分表查询核心优化手段。
分区表示例:
sql

复制代码
CREATE TABLE orders(id BIGINT, order_time TIMESTAMP) PARTITION BY RANGE(order_time);
CREATE TABLE orders_q1 PARTITION OF orders FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE orders_q2 PARTITION OF orders FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');

查询 SQL:
sql

复制代码
SELECT * FROM orders WHERE order_time >= '2026-05-01';

运行时调度逻辑:解析order_time分区条件,判定仅需扫描orders_q2分区,执行计划中完全屏蔽 q1 分区扫描节点,磁盘 IO 降低 50% 以上。

(2)一次性短路过滤(Result 节点 One-Time Filter)

执行器通过 Result 节点实现恒真 / 恒假条件的短路调度,若条件运行时判定为恒假,直接返回空结果集,完全跳过下方表扫描、关联算子,零 IO 开销。
示例 SQL:
sql

复制代码
SELECT * FROM emp WHERE 1 = 0 AND salary > 10000;

执行计划节点:
plaintext

复制代码
Result
  One-Time Filter: false
└── Seq Scan on emp (该节点不会执行)

(3)参数化嵌套循环动态谓词调度

NestLoop 连接场景,外层表每一行生成参数化过滤条件,动态传入内层子查询,实现行级条件调度,结合谓词下推大幅降低内层扫描数据量。
原始相关子查询:
sql

复制代码
SELECT * FROM emp e
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.emp_id = e.id AND o.amount > 1000);

运行时调度:遍历 emp 每一行,将e.id作为参数?传入 orders 扫描条件o.emp_id = ? AND o.amount > 1000,内层每次仅扫描当前员工订单,避免全量订单表扫描。

(4)Materialize 缓存调度,避免重复扫描

多表关联中,若内层表会被反复扫描,条件调度器评估重复扫描 IO 开销,自动插入 Materialize 物化节点,将内层过滤后结果缓存至内存,后续循环直接读取缓存,减少重复磁盘扫描。

三、完整案例:等价变换 + 条件调度协同优化

3.1 原始低效 SQL

sql

复制代码
-- 统计2026年5月研发部员工高薪订单
SELECT DISTINCT e.name, o.id, o.amount
FROM emp e
LEFT JOIN dept d ON e.dept_id = d.dept_id
LEFT JOIN orders o ON e.id = o.emp_id
WHERE d.dept_name = '研发部' AND o.order_time >= '2026-05-01' AND o.amount > 10000;

3.2 优化器等价变换改写过程

  1. 常量折叠:无常量运算,直接保留条件;

  2. 谓词下推:dept_name='研发部'下推 dept 表,order_timeamount条件下推 orders 分区表;

  3. 连接类型等价转换:WHERE 子句存在右侧表非空过滤,左连接等价转为内连接,消除空值匹配开销;

  4. DISTINCT 消除:orders 主键 o.id 已唯一,删除 DISTINCT。
    改写后逻辑 SQL:
    sql

    SELECT e.name, o.id, o.amount
    FROM (SELECT dept_id FROM dept WHERE dept_name = '研发部') d
    INNER JOIN emp e ON e.dept_id = d.dept_id
    INNER JOIN (SELECT id, emp_id, amount FROM orders WHERE order_time >= '2026-05-01' AND amount > 10000) o
    ON e.id = o.emp_id;

3.3 条件调度全流程执行

  1. 静态计划阶段:调度器统计三张表行数,过滤后 dept 仅少量研发部数据,选为驱动外表;orders 表 order_time 为分区键,计划启用分区裁剪;
  2. 运行时分区裁剪:解析order_time >= '2026-05-01',仅扫描 orders_q2 分区;
  3. 参数化嵌套循环调度:遍历研发部员工,将员工 id 参数传入 orders 过滤条件,匹配对应订单;
  4. 无短路、无重复扫描,无需 Materialize 节点。
    优化效果:原始 SQL 全表扫描三张大表,执行耗时 12s;经过等价改写与条件调度协同优化后,仅扫描目标分区与研发部少量数据,执行耗时 180ms。

四、总结与生产调优建议

4.1 核心逻辑总结

  1. 等价变换是静态重构工具:基于关系代数规则,在不改变查询结果的前提下提前过滤数据、消除冗余计算,是优化的基础;
  2. 条件调度是动态执行大脑:分为编译期静态路径选择、运行时动态裁剪 / 短路 / 参数化过滤,适配真实数据分布,弥补统计信息预估偏差;
  3. 两者协同工作:等价变换简化查询树,为条件调度提供更精准的过滤条件;条件调度落地最优物理执行路径,最大化等价改写收益。

4.2 生产环境调优配置建议

  1. 开启高级等价变换插件:shared_preload_libraries = 'kdb_rbo'set kdb_rbo.rbo_rule = cost;
  2. 分区表保持分区裁剪开启:enable_partition_pruning = on
  3. 定期执行ANALYZE更新表统计信息,保障条件调度代价估算精准;
  4. 编写 SQL 时尽量将过滤条件写至子查询内部,辅助优化器完成谓词下推。
    全文约 2100 字,覆盖底层原理、可复现 SQL 示例、真实业务优化案例,适配国产金仓数据库 SQL 性能调优、内核学习场景。
    文章转载自 http://www.mhpq.cn/news/33742