为减少执行计划生成次数,我们使用了执行计划缓存(Plan Cache)。对于同一 SQL 不同请求对应的参数经常是不同的,而这些请求通常能够共用相同的执行计划,为了能够将这些 SQL 请求在 Plan Cache 中命中相同的计划,我们将 SQL 进行参数化(即将 SQL 中的常量转换为参数),然后使用参数化的 SQL 文本作为键值在 Plan Cache 中获取执行计划,从而达到仅参数不同的 SQL 能够共用相同的计划目的,由于传统数据库在进行参数化时一般是对语法树进行参数化,然后使用参数化后的语法树作为键值在 Plan Cache 中获取计划,而我们是使用的词法分析对文本串直接参数化后作为 Plan Cache 的键值,因此叫做快速参数化。
基于快速参数化的获取执行计划过程如下图所示:
参数化过程是指把 SQL 查询中的常量变成变量的过程。如下例所示:
select * from t1 where c1 = 5 and c2 = "oceanbase";
参数化后结果如下例所示:
select * from t1 where c1 = @1 and c2 = @2;
但在计划匹配中,不是所有常量都可以被参数化,比如 order by 后面的常量,表示按照 select 投影列中第几列进行排序。
如下例所示,表 t1 中含 c1, c2 列,其中c1 为主键列,SQL 意义要求结果按照 c1 列进行排序,由于 c1 作为主键列已是有序的,使用主键访问可以免去排序。
select c1, c2 from t1 order by 1;
OceanBase (root@oceanbase)> explain select c1, c2 from t1 order by 1;
| ===================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
-----------------------------------
|0 |TABLE SCAN|t1 |1000 |1381|
===================================
但如果用户执行如下命令:
select c1, c2 from t1 order by 2;
则结果需要对 c2 排序,因此需要执行显示的排序操作,执行计划如下例所示:
OceanBase (root@oceanbase)> explain select c1, c2 from t1 order by 2;
| ====================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
------------------------------------
|0 |SORT | |1000 |1886|
|1 | TABLE SCAN|t1 |1000 |1381|
====================================
因此,不能将 order by 后面的常量参数化,否则会导致不同 order b y的值参数化后具有相同的参数化后的 SQL,从而命中错误的计划。
除此以外,如下场景中的常量均不能参数化(约束条件):
- 所有 ORDER BY 后常量(例如“ORDER BY 1,2;”)
- 所有 GROUP BY 后常量(例如“GROUP BY 1,2;”)
- LIMIT 后常量(例如 “LIMIT 5;”)
- 作为格式串的字符串常量(例如“SELECT DATE_FORMAT(‘2006-06-00’, ‘%d’);“ 里面的 %d)
- 函数输入参数中,影响函数结果并最终影响执行计划的常量(例如“CAST(999.88 as NUMBER(2,1))”中的“NUMBER(2,1)”,或者“SUBSTR(‘abcd’, 1, 2)”中的“1, 2”)
- 函数输入参数中,带有隐含信息并最终影响执行计划的常量(例如“SELECT UNIX_TIMESTAMP(‘2015-11-13 10:20:19.012’); ” 里面的“2015-11-13 10:20:19.012”,指定输入时间戳的同时,隐含指定了函数处理的精度值为毫秒)
为了解决上面这种可能存在的误匹配问题,在硬解析生成执行计划过程中(图1蓝色方框内)会对 SQL 请求使用分析语法树的方法进行参数化,并获取相应的不一致的信息。比如该语句对应的信息是“快速参数化参数数组的第3项必须为数字3”,可将其称为“约束条件”。
对于下例所示的请求 SQL_A 命令:
select c1, c2, c3
from t1
where c1 = 1 and c2 like ‘senior%’ order by 3;
经过词法分析,可以得到参数化后的 SQL 如下例所示:
参数化后SQL:select c1, c2, c3
from t1
where c1 = @1 and c2 like @2 order by @3 ;
参数数组: {1,‘senior%’ ,3}
当 order by 后面的常量不同时,不能共用相同的执行计划,因此在通过分析语法树进行参数化时会获得另一种参数化结果,如下例所示:
参数化后SQL:select c1, c2, c3
from t1
where c1 = @1 and c2 like @2 order by 3 ;
参数化参数为:{1, ‘senior’}
约束条件:快速参数化参数数组的第3项必须为数字3
SQL_A 请求新生成的参数化后的文本及约束条件和执行计划均会存入计划缓存中。
当用户再次发出如下 SQL_B 请求命令:
select c1, c2, c3
from t1
where c1 = 1 and c2 like ‘senior%’ order by 2;
经过快速参数化后结果如下例所示:
参数化后SQL: select c1, c2, c3
from t1
where c1 = @1 and c2 like @2 order by @3;
参数数组: {1,‘senior%’ ,2}
这与 SQL_A 请求快速参数化后 SQL 结果一样,但由于不满足“快速参数化参数数组的第3项必须为数字3”这个约束条件,无法匹配该计划。此时 SQL_B 会通过硬解析生成新的执行计划及约束条件(即快速参数化参数数组的第3项必须为数字2), 并将新的计划和约束条件加入到缓存中, 下次执行 SQL_A 和 SQL_B 时均可命中对应正确的执行计划。
基于快速参数化的执行计划缓存优点如下:
省去语法分析过程;
查找 hash map 时,对参数化后语法树的hash和比较操作,可替换为对文本串进行 hash 和 memcmp 操作,效率更高。