为减少执行计划生成次数,我们使用了执行计划缓存(Plan Cache)。对于同一SQL不同请求对应的参数经常是不同的,而这些请求通常能够共用相同的执行计划,为了能够将这些SQL请求在Plan Cache中命中相同的计划,我们将SQL进行参数化(即将SQL中的常量转换为参数),然后使用参数化的SQL文本作为键值在Plan Cache中获取执行计划,从而达到仅参数不同的SQL能够共用相同的计划目的,由于传统数据库在进行参数化时一般是对语法树进行参数化,然后使用参数化后的语法树作为键值在Plan Cache中获取计划,而我们是使用的词法分析对文本串直接参数化后作为Plan Cache的键值,因此叫做快速参数化。基于快速参数化的获取执行计划过程如下图所示。

    快速参数化 - 图1

    参数化过程是指把SQL查询中的常量变成变量的过程。

    例如:

    1. select * from t1 where c1 = 5 and c2 = "oceanbase";

    参数化后结果为:

    1. select * from t1 where c1 = @1 and c2 = @2;

    但在计划匹配中,不是所有常量都可以被参数化,比如order by后面的常量,表示按照select投影列中第几列进行排序。

    例如,表t1中含c1, c2列,其中c1为主键列,SQL意义要求结果按照c1列进行排序,由于c1作为主键列已是有序的,使用主键访问可以免去排序。

    1. select c1, c2 from t1 order by 1;
    2. OceanBase (root@oceanbase)> explain select c1, c2 from t1 order by 1;
    3. | ===================================
    4. |ID|OPERATOR |NAME|EST. ROWS|COST|
    5. -----------------------------------
    6. |0 |TABLE SCAN|t1 |1000 |1381|
    7. ===================================

    但如果用户执行

    1. select c1, c2 from t1 order by 2;

    则结果需要对c2排序,因此需要执行显示的排序操作,执行计划如下:

    1. OceanBase (root@oceanbase)> explain select c1, c2 from t1 order by 2;
    2. | ====================================
    3. |ID|OPERATOR |NAME|EST. ROWS|COST|
    4. ------------------------------------
    5. |0 |SORT | |1000 |1886|
    6. |1 | TABLE SCAN|t1 |1000 |1381|
    7. ====================================

    因此,不能将order by 后面的常量参数化,否则会导致不同order by的值参数化后具有相同的参数化后的SQL,从而命中错误的计划。除此以外,如下场景中的常量均不能参数化(约束条件):

    • 所有order by后常量

    • 所有group by后常量

    • 被物化的参数精度数字

    • 在优化过程中被用来做语义等价改写的恒真恒假条件

    • select投影列中常量

    • limit 后常量

    • 作为格式串的字符串常量

    为了解决上面这种可能存在的误匹配问题,在硬解析生成执行计划过程中(图1蓝色方框内)会对SQL请求使用分析语法树的方法进行参数化,并获取相应的不一致的信息。比如该语句对应的信息是”快速参数化参数数组的第3项必须为数字3“。我们可将其称为“约束条件”。

    对于如下请求SQL_A:

    1. select c1, c2, c3
    2. from t1
    3. where c1 = 1 and c2 like senior%’ order by 3;

    经过词法分析,可以得到参数化后的SQL为:

    1. 参数化后SQLselect c1, c2, c3
    2. from t1
    3. where c1 = @1 and c2 like @2 order by @3 ;
    4. 参数数组: {1,‘senior%’ 3}

    在上一节已提到,order by 后面的常量不同时,不能共用相同的执行计划,因此在通过分析语法树进行参数化时会获得另一种参数化结果:

    1. 参数化后SQLselect c1, c2, c3
    2. from t1
    3. where c1 = @1 and c2 like @2 order by 3 ;
    4. 参数化参数为:{1, senior’}
    5. 约束条件:快速参数化参数数组的第3项必须为数字3

    上面SQL_A请求新生成的参数化后的文本及约束条件和执行计划均会存入计划缓存中。

    当用户再次发出如下SQL_B请求:

    1. select c1, c2, c3
    2. from t1
    3. where c1 = 1 and c2 like senior%’ order by 2

    经过快速参数化后结果为:

    1. 参数化后SQL: select c1, c2, c3
    2. from t1
    3. where c1 = @1 and c2 like @2 order by @3
    4. 参数数组: {1,‘senior%’ 2}

    这与SQL_A请求快速参数化后SQL结果一样,但由于不满足“快速参数化参数数组的第3项必须为数字3”这个约束条件,无法匹配该计划。此时SQL_B会通过硬解析生成新的执行计划及约束条件(即快速参数化参数数组的第3项必须为数字2), 并将新的计划和约束条件加入到缓存中, 下次执行SQL_A 和 SQL_B时均可命中对应正确的执行计划。

    基于快速参数化的执行计划缓存优点:

    • 省去语法分析过程

    • 查找hash map时,对参数化后语法树的hash和比较操作,可替换为对文本串进行hash和memcmp操作,效率更高