CBO规则控制

更新时间:
复制 MD 格式

SelectDBNereids优化器在生成执行计划时会自动应用一系列CBO(Cost-Based Optimization)改写规则,绝大多数情况下这些改写能提升查询效率。本文介绍两种控制方式:通过disable_nereids_rules禁用引发问题的规则,以及通过USE_CBO_RULE Hint主动启用默认未开启的优化规则。

改写规则概述

优化器在生成执行计划前,会对查询做一轮等价变换,目标是减少扫描量和中间结果集大小。改写规则覆盖三个方面:谓词优化(将过滤条件下推到算子之前执行)、投影优化(移除冗余的投影算子)和Join优化(重新排列Join顺序或改写Join类型)。这些规则默认全部启用。

规则名称是Nereids优化器的内部标识符,SET_VAR Hint会严格校验规则名,名称错误时SQL直接报错。SET语句不做校验,仅在查询时无效果。

禁用改写规则

有两种方式禁用改写规则,适用于不同场景。以下是常用的可禁用规则及其作用:

规则类别

规则名称

作用

谓词优化

PUSH_DOWN_FILTER_THROUGH_JOIN

将过滤条件下推到Join之前执行,减少参与Join的数据量

谓词优化

PUSH_DOWN_PREDICATE_THROUGH_AGGREGATION

将过滤条件下推到聚合算子之前

投影优化

ELIMINATE_UNNECESSARY_PROJECT

移除执行计划中冗余的投影算子

Join优化

REORDER_JOIN

基于代价模型重新排列多表Join的执行顺序

Join优化

ELIMINATE_OUTER_JOIN

当外连接结果等价于内连接时,将LEFT/RIGHT JOIN改写为INNER JOIN

Session变量:当前连接全局生效

通过SET disable_nereids_rules禁用规则,对当前连接内后续所有查询生效。适合在调试阶段批量对比执行计划:

-- 禁用Join重排序
SET disable_nereids_rules = 'REORDER_JOIN';

-- 同时禁用多个规则,逗号分隔
SET disable_nereids_rules = 'REORDER_JOIN,PUSH_DOWN_FILTER_THROUGH_JOIN';

-- 恢复所有规则
SET disable_nereids_rules = '';

SQL Hint:仅当前语句生效

通过/*+ SET_VAR() */注释Hint在单条SQL内禁用规则,不影响其他查询。适合嵌入生产SQL:

SELECT /*+ SET_VAR(disable_nereids_rules='REORDER_JOIN') */
    o.order_id, u.username, o.amount
FROM orders o
JOIN users u ON o.user_id = u.user_id
JOIN products p ON o.product_id = p.product_id
WHERE o.order_date >= '2024-01-01';

Hint仅对当前SQL生效,同一Session内的其他查询不受影响。

启用CBO改写规则

部分CBO优化规则因适用场景较窄,默认未开启。当查询符合特定模式时,可通过USE_CBO_RULE Hint主动启用,让优化器在Join前先做聚合或去重,减少参与Join的数据量。

规则名称

作用

适用场景

PUSH_DOWN_AGG_THROUGH_JOIN

Join两侧分别预聚合后再Join

Join两侧都是大表,聚合后数据量大幅缩减

PUSH_DOWN_AGG_THROUGH_JOIN_ONE_SIDE

仅在Join的一侧预聚合

一侧是大事实表且聚合后缩减明显,另一侧是维度表

PUSH_DOWN_DISTINCT_THROUGH_JOIN

Join前先去重,再用去重后的结果Join

Join后接DISTINCT且重复率高,先去重能大幅减少Join的行数

语法与SET_VAR类似,在SELECT后添加Hint:

-- 启用聚合下推,让优化器在Join前先预聚合
SELECT /*+ USE_CBO_RULE(PUSH_DOWN_AGG_THROUGH_JOIN) */
    o.user_id, SUM(oi.quantity * oi.price) AS total_amount
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date >= '2024-01-01'
GROUP BY o.user_id;

-- 同时启用多条规则
SELECT /*+ USE_CBO_RULE(PUSH_DOWN_AGG_THROUGH_JOIN, PUSH_DOWN_AGG_THROUGH_JOIN_ONE_SIDE) */
    ...;

USE_CBO_RULE仅对当前SQL生效。与disable_nereids_rules方向相反:前者是主动启用默认关闭的规则,后者是临时关闭默认启用的规则。

典型场景

Join重排序导致性能下降

当统计信息不准确时,优化器可能选择一个比SQL原始书写顺序更差的Join顺序。禁用REORDER_JOIN后,优化器按SQLFROM/JOIN的书写顺序执行Join:

-- 禁用Join重排序,保持SQL书写顺序:先 sales_detail JOIN product_category,再 JOIN warehouse
SELECT /*+ SET_VAR(disable_nereids_rules='REORDER_JOIN') */
    a.*, b.category_name, c.warehouse_name
FROM sales_detail a
JOIN product_category b ON a.category_id = b.id
JOIN warehouse c ON a.warehouse_id = c.id
WHERE a.sale_date = '2024-06-01';

谓词下推反而增大扫描量

聚合查询中,将HAVING或外层WHERE条件下推到聚合之前执行,可能导致扫描范围不减反增(例如失去前缀索引匹配)。此时禁用PUSH_DOWN_PREDICATE_THROUGH_AGGREGATION

-- 阻止将 total_amount > 100000 下推到聚合之前
SELECT /*+ SET_VAR(disable_nereids_rules='PUSH_DOWN_PREDICATE_THROUGH_AGGREGATION') */
    region, total_amount
FROM (
    SELECT region, SUM(amount) AS total_amount
    FROM orders
    GROUP BY region
) t
WHERE total_amount > 100000;

外连接消除导致结果不正确

优化器检测到外连接的NULL侧在后续WHERE中被过滤时,会将LEFT/RIGHT JOIN改写为INNER JOIN。但如果外层引用了NULL值做判断(如IS NULL判断用户是否有订单),这个改写会导致结果不正确。禁用ELIMINATE_OUTER_JOIN保持原始语义:

-- 保留LEFT JOIN语义,不自动转换为INNER JOIN
SELECT /*+ SET_VAR(disable_nereids_rules='ELIMINATE_OUTER_JOIN') */
    u.username, o.order_id, o.amount
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id;

大表Join后聚合:启用预聚合下推

在数据分析中,常见的模式是先Join多张大表再做聚合(GROUP BY + SUM/COUNT)。默认情况下优化器先完成全量Join,再对结果做聚合,中间结果可能非常大。启用PUSH_DOWN_AGG_THROUGH_JOIN后,优化器在Join前先分别对两侧数据预聚合,大幅减少参与Join的行数:

-- 订单明细表(千万级)Join订单表(百万级),按用户汇总金额
-- 启用预聚合下推:先在各自表上按user_id聚合,再Join聚合结果
SELECT /*+ USE_CBO_RULE(PUSH_DOWN_AGG_THROUGH_JOIN) */
    o.user_id, SUM(oi.quantity * oi.price) AS total_amount
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-06-30'
GROUP BY o.user_id;

通过EXPLAIN查看执行计划,确认聚合算子出现在Join算子之前即表示规则生效。

定位问题规则的方法

如果怀疑某条SQL因改写规则变慢,按以下步骤逐一排查:

-- 1. 查看默认执行计划
EXPLAIN SELECT o.order_id, u.username
FROM orders o JOIN users u ON o.user_id = u.user_id
WHERE o.order_date >= '2024-01-01';

-- 2. 禁用可疑规则,对比计划变化
SET disable_nereids_rules = 'REORDER_JOIN';
EXPLAIN SELECT o.order_id, u.username
FROM orders o JOIN users u ON o.user_id = u.user_id
WHERE o.order_date >= '2024-01-01';

-- 3. 实际执行对比耗时
SET disable_nereids_rules = '';
SELECT ...; -- 记录默认规则下的耗时

SET disable_nereids_rules = 'REORDER_JOIN';
SELECT ...; -- 记录禁用后的耗时

确认某条规则是问题根源后,在对应SQL中使用SET_VAR Hint禁用,而不是在Session级别全局禁用。

使用建议

  • 先执行ANALYZE TABLE更新统计信息。多数改写问题的根因是统计信息过期,更新后规则自然能做出正确判断。

  • 禁用规则是治标手段。数据库版本升级后优化器行为可能改善,定期重新测试是否仍需禁用。

  • 生产SQL中优先使用SET_VAR Hint而非Session变量,将影响控制在单条SQL内。

  • 规则名称是内部标识符,数据库大版本升级时可能变更。升级后建议重新验证现有Hint中的规则名称。