更新时间:2026-07-28 GMT+08:00
分享

DSL规则引擎整体流程

DSL规则引擎使用DSL语言对SQL的查询改写规则进行描述,通过统一的原子化接口,形成特定语法,利用这些语法的灵活编排组合,符号化的描述能力,使得查询改写规则可以插件化集成,动态加载,从而增强查询改写的易开发、易部署、灵活可扩展性。

DSL规则引擎处于SQL引擎中,且主要在运行态利用加载的DSL规则对SQL进行等价改写,从而帮助数据库在优化器阶段生成更好的执行路径,提升执行性能,DSL规则引擎整体流程如图1所示。

图1 DSL规则引擎整体流程

如上图所示,DSL规则引擎实现主要包含两个阶段:

规则添加阶段维护人员通过DSL规则管理模块添加新的DSL改写规则,并通过RELOAD机制将系统表中的内容同步至DSL规则引擎的全局缓存池,然后DSL规则引擎在规则匹配与应用框架运行阶段即可从缓存池中加载这些规则,并按照优先级策略依次遍历规则集完成匹配改写。

  • 规则添加阶段:
    • DSL规则来源分为两类:内置规则和外部发现的规则,内置规则如表1所示,对于内置规则不允许删除,只能禁用。外部规则由DBA使用DSL语言编写而来,其等价性系统无法保证,因此DSL规则引擎约束对于DSL规则本身的等价性,异常场景的匹配、替换应由规则设计者保证,对于这些外部来源的DSL规则集,引擎只能保证框架本身调度逻辑功能的正确性。
    • 新增DSL规则添加成功并执行RELOAD操作后可以被应用。DSL规则引擎在运行过程中应关注DSL规则集的生命周期变化,对于新的SQL会话请求,应使用变更后的DSL规则集进行改写流程调度;而对于已经处于改写流程中的会话则应继续使用旧的副本完成整个查询重写流程。
  • 规则使用阶段,完整流程主要包含:
    1. 用户输入原始SQL请求,数据库经过语法、语义解析后得到QueryTree。
    2. 使用步骤1获得的QueryTree作为入参进入DSL规则引擎的改写模块,该模块位于优化器的查询重写规则模型中,其应用包含以下子流程:
      1. 对于每条DSL规则,分为匹配流程和应用流程;匹配流程中将QueryTree的节点与模板进行匹配,找到满足条件的节点;然后应用规则对节点进行等价改写,生成目标QueryTree。
      2. 对于整个规则集,DSL规则引擎会按照规则的开关状态进行过滤,并按照规则的优先级进行排序,然后依次调度每条DSL规则对QueryTree执行上一步中的流程。
      3. DSL规则引擎改写的终止主要考虑两种情况:一是当规则集被完整调度两次,且均无任何改写发生时则终止调度流程;二是当规则集调度次数达到系统设置的阈值时,则强行终止改写流程。
表1 内置规则介绍

场景名称

子场景名称

简要描述

SQL示例

场景一:JOIN连接消除

  • 子场景1.1:具有DISTINCT去重语义的外连接消除

    rulename:

    distinct_enabled_outer_join_elimination

  • 子场景1.2:具有GROUP BY条件去重语义的外连接消除

    rulename:

    group_by_outer_join_elimination

  • 子场景1.1:

    对于左外连接或右外连接,当投影列中只包含外表的字段值并且有DISTINCT关键字约束时,能消除该外连接。

    规则默认开启状态:开启。

  • 子场景1.2:

    当GROUP BY条件满足DISTINCT的去重语义时也可以消除外连接。

    规则默认开启状态:开启。

  • 子场景1.1:
    SELECT DISTINCT t1.* FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1;
    其等价改写结果如下所示:
    SELECT DISTINCT t1.* FROM t1;
  • 子场景1.2:
    SELECT t1.* FROM t1 LEFT JOIN t2 ON t1.c = t2.c GROUP BY t1.a, t1.b, t1.c;
    其等价改写结果如下所示:
    SELECT t1.* FROM t1;

子场景2:JOIN条件恒为FALSE的外连接消除

rulename:

always_false_outer_join_elimination

  • 对于左外连接或右外连接,若JOIN条件恒为FALSE,外表的每一行都无法和内表找到匹配行,连接后的投影列为外表每一行均返回一次并对内表投影列补NULL值,因此能消除该外连接。
  • 规则默认开启状态:开启。

(假设T1和T2表均只有一个属性列)

SELECT * FROM t1 LEFT JOIN t2 ON 1 = 0;

其等价改写结果如下所示:

SELECT t1.c1, NULL FROM t1;

子场景3:同表同列的SEMI JOIN消除

rulename:

same_table_semi_join_elimination

  • 结合本场景SQL示例进行说明,当主查询和子查询引用的均为t1表且连接条件为同一列,其中t1表需为简单表,根据EXISTS子查询的语义,若t1.c1不全为NULL,则经过EXISTS子查询过滤后一定可返回t1.c1不为NULL的行;若t1.c1全为NULL,那么EXISTS子查询一定返回FALSE,此时结果集为空。该场景可以进行一些等价优化,消除SEMI JOIN部分。
  • 规则默认开启状态:开启。
SELECT * FROM t1 WHERE EXISTS(SELECT 1 FROM t1 tt WHERE t1.c1 = tt.c1);

其等价改写结果如下所示:

SELECT * FROM t1 WHERE t1.c1 IS NOT NULL;

场景二:LIMIT条件下压

  • 子场景1.1:外连接Limit下推子查询

    rulename:

    left_join_branch_limit_pushdown

  • 子场景1.2:交叉连接Limit下推子查询

    rulename:

    cross_join_branch_limit_pushdown

  • 如果外连接或交叉连接中没有WINDOW FUNCTION、DISTINCT、GROUP BY或HAVING,且WHERE条件或者ORDER BY仅和连接的一侧表有关时,可将LIMIT语句下压到连接的表的一侧(外连接)或多侧(多表交叉连接)。通过LIMIT 下推可有效减少连接的行数,从而降低计划执行的性能开销。
  • 规则默认开启状态:开启。
  • 子场景1.1:
    SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 LIMIT 1;
    其等价改写结果如下所示:
    SELECT * FROM (SELECT * FROM t1 LIMIT 1) t LEFT JOIN t2 ON t.c1 = t2.c1 LIMIT 1;
  • 子场景1.2:
    SELECT 1 FROM t1, t2 WHERE t1.c1 > 0 ORDER BY t1.c1 LIMIT 1;
    其等价改写结果如下所示:
    SELECT 1 FROM (SELECT 1 FROM t1 WHERE t1.c1 > 0 ORDER BY t1.c1 LIMIT 1) tm1, (SELECT 1 FROM t2 LIMIT 1) tm2 LIMIT 1;

场景三:冗余的DISTINCT消除

  • 子场景1.1:投影列全是常量时可以消除DISTINCT约束

    rulename:

    const_only_distinct_elimination_base

  • 子场景1.2:投影列全是常量,且有OFFSET且OFFSET值>0时可以消除DISTINCT约束,整个SQL语句可以规约为约束条件为FALSE

    rulename:

    const_only_distinct_elimination_limit

  • 子场景1.3:投影列全是常量,且GROUP BY约束包含所有的投影列,可以消除GROUP BY

    rulename:

    const_only_distinct_elimination_groupby

  • 子场景1.1:

    如果SELECT的投影列中只包含常量,则可以通过添加LIMIT 1约束消除DISTINCT,从而提前结束对数据的遍历。

    规则默认开启状态:开启。

  • 子场景1.2:

    如果SELECT的投影列中只包含常量,且有OFFSET且OFFSET值>0时,则可以消除约束DISTINCT,整个SQL语句可以规约为约束条件为FALSE,提前结束SQL执行。

    规则默认开启状态:关闭。

  • 子场景1.3:

    如果SELECT的投影列中只包含常量,且GROUP BY约束包含所有的投影列,没有HAVING条件、GROUPING SET时,可以消除GROUP BY增加LIMIT 1约束,从而提前结束对数据的遍历。

    规则默认开启状态:关闭。

  • 子场景1.1:
    SELECT DISTINCT 1,2 FROM t1;
    其等价改写结果如下所示:
    SELECT 1,2 FROM t1 LIMIT 1;
  • 子场景1.2:
    SELECT DISTINCT 1,2 FROM t1 OFFSET 1;
    其等价改写结果如下所示:
    SELECT 1,2 FROM t1 WHERE FALSE;
  • 子场景1.3:
    SELECT 1,2 FROM t1 GROUP BY 1,2;
    其等价改写结果如下所示:
    SELECT 1,2 FROM t1 LIMIT 1;
  • 子场景2.1:投影列包含唯一键约束时可以消除DISTINCT约束

    rulename:

    unique_key_distinct_elimination_base

  • 子场景2.2:GROUP BY包含唯一约束投影列时可以消除GROUP BY

    rulename:

    unique_key_distinct_elimination_groupby

  • 子场景2.1:

    如果SELECT语句的投影列中包含唯一性约束的列时,且FROM中不包含复杂表,则可以消除DISTINCT,假设有t1表且其列字段c1上存在唯一非空约束。

    规则默认开启状态:开启。

  • 子场景2.2:

    如果GROUP BY包含唯一约束投影列其语义上等价于DISTINCT约束,因此该场景也可以消除GROUP BY。

    规则默认开启状态:关闭。

  • 子场景2.1:
    SELECT DISTINCT c1, c2, c3 FROM t1;
    其等价改写结果如下所示:
    SELECT c1, c2, c3 FROM t1;
  • 子场景2.2:
    SELECT t1.a, t1.b, t1.c FROM t1 GROUP BY t1.a;
    其等价改写结果如下所示:
    SELECT t1.a, t1.b, t1.c FROM t1;

场景四:复杂场景扩展

子场景1:SEMI JOIN加速STREAM计划中的外连接

rulename:

semi_join_accelerated_outer_join

  • 当小表LEFT JOIN大表时,如果连接条件不是分布列,则大表必须重分布而不能BROADCAST小表,导致性能较差;此时可以将大表先和小表做一次半连接,过滤掉大部分数据后,再与小表做外连接,从而可以将小表BROADCAST。
  • 规则默认开启状态:关闭。
SELECT * FROM t2 small LEFT JOIN t1 big ON small.c1 = big.c1;

其等价改写结果如下所示:

SELECT * FROM t2 small LEFT JOIN (SELECT * FROM t1 big WHERE big.c1 IN (SELECT c1 FROM t2)) fakebig ON small.c1 = fakebig.c1;

子场景2:两个表达式子链接比较且存在子结构关系时消减子链接

rulename:

subLink_compare_elimination

  • 两个包含Count聚集函数的表达式子链接进行等值比较时,如果其中一个子链接可以表示为另一个子链接的子结构,且过滤条件仅相差一个EXISTS子链接条件时,可以对其进行优化。因为在此场景下,实际是想找到EXISTS条件返回FALSE的情况。在这种情况下两个count值是否相等取决于其他条件是否为TRUE。当其他条件为TRUE时,根据AND条件的语义,两个子链接的过滤条件相反,count()取值一定不同;否则取值相同。因此,对于这种场景可以使用NOT EXISTS等价改写消减子链接的数量。
  • 规则默认开启状态:开启。
SELECT t1.a, t1.b FROM t1,t2 WHERE t1.a = t2.a AND t1.b = t1.b AND (SELECT COUNT(*) FROM t1 as t11 WHERE t11.a = t1.a AND t11.b = t1.b) = (SELECT COUNT(*) FROM t1 AS t21 WHERE t21.a = t1.a AND t21.b = t1.b AND EXISTS(SELECT 1 FROM t3 WHERE t1.a = t3.a));

其等价改写结果如下所示:

SELECT t1.a, t1.b FROM t1,t2 WHERE t1.a = t2.a AND t1.b = t1.b AND NOT EXISTS (SELECT 1 FROM t1 as t11 WHERE t11.a = t1.a AND t11.b = t1.b AND NOT EXISTS(SELECT 1 FROM t3 WHERE t1.a = t3.a));
  • 子场景3.1:常值谓词条件下推到子查询投影列中

    rulename:

    const_predicate_pushdown_to_project

  • 子场景3.2:常值谓词条件下推到子查询过滤条件中

    rulename:

    const_predicate_pushdown_to_filter

  • 当主查询中包含常值谓词条件时,即表达式一侧是表的字段另一侧是常量,且子链接的查询条件中包含相同表字段时,则可以考虑将该常量条件下推到子链接中。
  • 规则默认开启状态:关闭。
  • 子场景3.1:
    SELECT t1.a, (SELECT sum(t2.a) FROM t2 WHERE t2.b = t1.b) FROM t1 WHERE t1.b = 1;
    其等价改写结果如下所示:
    SELECT t1.a, (SELECT sum(t2.a) FROM t2 WHERE t2.b = t1.b AND t1.b = 1) FROM t1 WHERE t1.b = 1;
  • 子场景3.2:
    SELECT t1.a FROM t1 WHERE t1.b = 1 AND (SELECT SUM(t2.a) FROM t2 WHERE t2.b = t1.b) = 0;
    其等价改写结果如下所示:
    SELECT a FROM t1 WHERE b = 1 AND (SELECT sum(t2.a) FROM t2 WHERE t2.b = t1.b AND t1.b = 1) = 0;

子场景4:相关表达式子链接转交叉连接

rulename:

subLink_to_crossJoin

  • 对于有聚集函数的等值表达式子链接,且子链接与常量比较时,可以将子链接提取为CTE表与主查询做交叉连接,在分布式场景特定的数据分布情况下,可以利用STREAM的BROADCAST提升性能
  • 规则默认开启状态:关闭。
SELECT * FROM t1 WHERE t1.a = 1 AND (SELECT count(*) FROM t2 WHERE t2.b = t1.b AND t2.b = 1) = 3;

其等价改写结果如下所示:

WITH cte AS (SELECT count(*) AS c, b FROM t2 WHERE t2.b = 1 GROUP BY b) SELECT * FROM t1, cte WHERE t1.a = 1 AND cte.b = t1.b AND cte.c = 3;

子场景5:表达式子链接聚集函数为Count与0值比较时,可以消除聚集操作

rulename:

count_zero_elimination

  • 对于表达式相关子链接,如果子链接中的投影列是count()函数,且子链接与0做等值比较,则从语义上表示只有在过滤条件不满足时才为TRUE,因此将其等价改写为LEFT JOIN,并通过对内表的列进行IS NULL过滤,从而消除聚集函数和子计划。
  • 规则默认开启状态:开启。

SELECT t1.* FROM t1 WHERE t1.a = 1 AND (SELECT count(*) FROM t2 WHERE t2.b = t1.b AND t2.b = 1) = 0;

其等价改写结果如下所示:

SELECT t1.* FROM t1 LEFT JOIN t2 ON t2.b = 1 AND t2.b = t1.b WHERE t1.a = 1 AND t2.b IS NULL;

场景五:内连接唯一约束的自连接消除

基于DSL的规则引擎实现唯一约束的自连接消除需要如下两条规则:

  • 规则一:内连接查询转换为交叉连接

    rulename:

    inner_join_to_cross_join

  • 规则二:交叉连接唯一约束的自连接消除

    rulename:

    self_crossjoin_elimination

  • 当查询块中对多表进行主键或非空唯一约束上的内连接时,可通过查询重写规则消除冗余连接操作,从而减少查询中不必要的连接运算并提升查询整体性能。其原理是相同表在主键或非空唯一约束上的内连接具有严格的一对一匹配关系,因此,其结果集存在数据冗余,所有数据均可通过单次扫描原始表获取。
  • 规则默认开启状态:开启。
SELECT * FROM t1 tt1 INNER JOIN t1 tt2 ON tt1.a = tt2.a INNER JOIN t1 tt3 ON tt1.a = tt3.a WHERE tt1.b > 100; 

其等价改写结果如下所示:

SELECT tt1.*, tt1.*,tt1.* FROM t1 tt1 WHERE tt1.b > 100 AND tt1.a IS NOT NULL;

场景六:ALL非相关子链接改写

  • 子场景1.1:过滤条件中的ALL非相关子链接支持改写为MAX聚集函数的表达式子链接

    rulename:

    allsublink_add_max_filter

  • 子场景1.2:过滤条件中的ALL非相关子链接支持改写为MIN聚集函数的表达式子链接

    rulename:

    allsublink_add_min_filter

  • 子场景1.3:JOIN条件中的ALL非相关子链接支持改写为MAX聚集函数的表达式子链接

    rulename:

    allsublink_add_max_join

  • 子场景1.4:JOIN条件中的ALL非相关子链接支持改写为MIN聚集函数的表达式子链接

    rulename:

    allsublink_add_min_join

  • 典型SQL如下:
    SELECT a FROM t1 WHERE a > ALL(SELECT a FROM t2);

    对于上述SQL查询,表示遍历t1表并且返回满足过滤条件的行:即t1表的a列大于t2表a列的所有值。从等价逻辑可推导出,该条件等价于确保t1表的a列大于t2表a列的最大值。对于过滤条件或JOIN条件中的>ALL、>= ALL、< ALL、<= ALL类型的非相关子链接比较场景,均可通过等价改写来优化性能。

  • 规则默认开启状态:关闭。
  • 子场景1.1:
    SELECT a FROM t1 WHERE a > ALL(SELECT a FROM t2);
    其等价改写结果如下所示:
    SELECT a FROM t1 WHERE CASE WHEN ((SELECT max(t2.a) AS a FROM t2)) IS NULL THEN true ELSE a > ((SELECT max(t2.a) AS a FROM t2)) END;
  • 子场景1.2:
    SELECT a FROM t1 WHERE a < ALL(SELECT a FROM t2);
    其等价改写结果如下所示:
    SELECT a FROM t1 WHERE CASE WHEN ((SELECT min(t2.a) AS a FROM t2)) IS NULL THEN true ELSE a < ((SELECT min(t2.a) AS a FROM t2)) END;
  • 子场景1.3:
    SELECT * FROM t1 LEFT JOIN t4 ON t1.b = t4.b AND t1.a > ALL(SELECT a FROM t2);
    其等价改写结果如下所示:
    SELECT * FROM t1 LEFT JOIN t4 ON t1.b = t4.b AND CASE WHEN ((SELECT max(t2.a) AS a FROM t2)) IS NULL THEN true ELSE t1.a > ((SELECT max(t2.a) AS a FROM t2)) END;
  • 子场景1.4:
    SELECT * FROM t1 LEFT JOIN t4 ON t1.b = t4.b AND t1.a < ALL(SELECT a FROM t2);
    其等价改写结果如下所示:
    SELECT * FROM t1 LEFT JOIN t4 ON t1.b = t4.b AND CASE WHEN ((SELECT min(t2.a) AS a FROM t2)) IS NULL THEN true ELSE t1.a < ((SELECT min(t2.a) AS a FROM t2)) END;
说明:

示例1.1及1.3中>ALL子链接的优化规则同样适用于>=连接的场景;同理,示例1.2及1.4使用<ALL的优化规则同样适用于<=连接的场景。

场景七:HAVING中MIN/MAX谓词下推过滤条件

子场景:支持HAVING中MIN/MAX谓词下推过滤条件

rulename:

minmax_predicate_pushdown

  • 在某些场景下,可将HAVING条件中的谓词下推至WHERE条件中,在保证结果一致性的前提下提前过滤数据,从而优化执行性能,其中一类场景为HAVING条件中包含MIN、MAX比较谓词,典型SQL示例如下:
    SELECT a, min(b) FROM t1 GROUP BY a HAVING min(b) < 10;

    分析上述SQL查询可知,其目标是筛选出min(b)<10分组后的聚合结果。若能在分组前过滤掉b<10的行,则可减少参与计算的数据量。基于此逻辑,可推导出新的谓词b<10,并将其下推至至过滤条件中。

  • 规则默认开启状态:开启。
  • 示例1:
    SELECT a, min(b) FROM t1 GROUP BY a HAVING min(b) < 10;
    其等价改写结果如下所示:
    SELECT a, min(b) FROM t1 WHERE b < 10 GROUP BY a HAVING min(b) < 10;
  • 示例2:
    SELECT a, max(b) FROM t1 GROUP BY a HAVING max(b) > 10;
    其等价改写结果如下所示:
    SELECT a, max(b) FROM t1 WHERE b > 10 GROUP BY a HAVING max(b) > 10;
说明:

示例1中HAVING谓词中使用<连接的优化规则同样适用于<=连接的场景;同理,示例2中HAVING谓词使用>连接的优化规则同样适用于>=连接的场景。

场景八:LIMIT下推UNION ALL子查询

子场景:支持LIMIT下推UNION ALL子查询

rulename:

unionall_limit_pushdown

  • 在SQL标准语法中,UNION ALL用于合并两个或多个SELECT子查询的结果集,且不对结果进行排序和去重,而LIMIT则用于限制返回最终数据集的条数。如果将ORDER BY条件和LIMIT条件均下推至子查询中,可以利用索引的有序性消除排序条件并且利用LIMIT条件提前结束每个子查询的计算,这样,主查询仅需对少量数据进行二次排序再取前n条结果即可(n为LIMIT所指定的数字)。典型SQL示例如下:
    SELECT * FROM (SELECT * FROM t1 UNION ALL SELECT * FROM t2) ORDER BY a1 LIMIT 2;
  • 升级场景规则默认状态:关闭;新安装场景规则默认状态:开启。
SELECT * FROM (SELECT * FROM t1 UNION ALL SELECT * FROM t2) LIMIT 2;
其等价改写结果如下所示:
SELECT * FROM ((SELECT * FROM t1 LIMIT 2) UNION ALL (SELECT * FROM t2 LIMIT 2)) LIMIT 2;

场景九:LIMIT下推子查询

子场景:支持LIMIT下推子查询

rulename:

subquery_limit_pushdown

  • 通常SQL语句的执行顺序如下:

    FROM -> JOIN ON -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT

    当执行至SELECT时,需对前序结果集中的每条数据进行计算,若涉及子链接等复杂计算,整体执行时间将成倍增加。从以上执行顺序可知,排序操作和LIMIT操作通常最后执行。从结果等价性角度分析,优先过滤冗余结果集后再对有效数据执行子链接计算,既可保证执行结果的正确性,又能有效提升执行性能,典型SQL示例如下:

    SELECT (SELECT min(a) FROM t2 WHERE t2.c >= t1.b) AS a1 from t1 INNER JOIN t3 ON t1.a = t3.a ORDER BY t1.a LIMIT 10;
  • 升级场景规则默认状态:关闭;新安装场景规则默认状态:开启。
SELECT (SELECT min(a) FROM t2 WHERE t2.c >= t1.b) AS a1 FROM t1 INNER JOIN t3 ON t1.a = t3.a ORDER BY t1.a LIMIT 10;
其等价改写结果如下所示:
SELECT (SELECT min(a) FROM t2 WHERE t2.c >= t.b) AS a1 FROM (SELECT t1.* FROM t1 INNER JOIN t3 ON t1.a = t3.a ORDER BY t1.a LIMIT 10) t ORDER BY t.a LIMIT 10;

相关文档