暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

OceanBase管理数据库快速参数化

2023-06-21
282

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

基于快速参数化获取执行计划

同一条 SQL 语句在每次执行时可能会使用不同的参数,将这些参数做参数化处理,可以得到与具体参数无关的 SQL 字符串,并使用该字符串作为计划缓存的键值,用于在计划缓存中获取执行计划,从而达到参数不同的 SQL 能够共用相同的计划目的。

由于传统数据库在进行参数化时一般是对语法树进行参数化,然后使用参数化后的语法树作为键值在计划缓存中获取计划,而 OceanBase 数据库使用的词法分析对文本串直接参数化后作为计划缓存的键值,因此叫做快速参数化。

OceanBase 数据库支持自适应计划共享(Adaptive Cursor Sharing)功能以支持不同参数条件下的计划选择。

基于快速参数化而获取执行计划的流程如下图所示:

p179646

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

  • 节省了语法分析过程。

  • 查找 Hash Map 时,可以将对参数化后语法树的哈希和比较操作,替换为对文本串进行哈希和 MEMCMP 操作,以提高执行效率。

常量参数化及约束条件

在 OceanBase 数据库中,某些场景中的常量不能参数化,即参数化的约束条件如下:

  • 所有 ORDER BY 后常量(例如 ORDER BY 1,2;)。

  • 所有 GROUP BY 后常量(例如 GROUP BY 1,2;)。

  • LIMIT 后常量(例如 LIMIT 5;)。

  • 作为格式串的字符串常量(例如 SQL 语句 SELECT DATE_FORMAT('2006-06-00', '%d'); 中的%d)。

  • 函数输入参数中,影响函数结果并最终影响执行计划的常量(例如 SQL 语句 CAST(999.88 as NUMBER(2,1)) 中的 NUMBER(2,1),或者 SUBSTR('abcd', 1, 2) 中的 1 和 2)。

  • 函数输入参数中,带有隐含信息并最终影响执行计划的常量(例如 SQL 语句 SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19.012'); 中的"2015-11-13 10:20:19.012",指定输入时间戳的同时,隐含指定了函数处理的精度值为毫秒)。

一般常量参数化示例

现有 SQL 查询语句示例如下:

obclient> SELECT * FROM t1 WHERE c1 = 5 AND c2 ='oceanbase';

上述示例中的 SQL 查询在 OceanBase 数据库内部进行参数化处理后结果如下所示,常量 5 和 oceanbase 被参数化后变成了变量 @1 和 @2,并对比当前数据库库里已执行过的 SQL,并匹配对应的执行计划。

obclient> SELECT * FROM T1 WHERE c1 = @1 AND c2 = @2;

常量不支持参数化示例

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

创建示例表 t1 并插入适当的数据,表 t1 中包含 c1c2 列,其中 c1 为主键列。

obclient> CREATE TABLE t1(c1 INT PRIMARY KEY,c2 INT);
Query OK, 0 rows affected

obclient> INSERT INTO t1 VALUES (1,2);
Query OK, 1 row affected 

obclient> INSERT INTO t1 VALUES (2,1);
Query OK, 1 row affected 

obclient> INSERT INTO t1 VALUES (3,1);
Query OK, 1 row affected
  • 执行如下 SQL 查询,将结果按照 c1 列进行排序,由于 c1 作为主键列是有序的,所以使用主键访问可以免去排序。

    obclient> SELECT c1, c2 FROM t1 ORDER BY 1;
    +----+------+
    | C1 | C2   |
    +----+------+
    |  1 |    2 |
    |  2 |    1 |
    |  3 |    1 |
    +----+------+
    3 rows in set 
    
    obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 1\G
    *************************** 1. row ***************************
    Query Plan: 
    | ===================================
    |ID|OPERATOR  |NAME|EST. ROWS|COST|
    -----------------------------------
    |0 |TABLE SCAN|t1  |1000     |1381|
    ===================================
    Outputs & filters:
    -------------------------------------
      0 - output([T1.C1], [T1.C2]), filter(nil),
          access([T1.C1], [T1.C2]), partitions(p0)
    
  • 执行如下 SQL 查询,将结果按照 c2 列进行排序,则需要执行显示的排序操作,执行计划如下例所示:

    obclient> SELECT c1, c2 FROM t1 ORDER BY 2;
    +----+------+
    | C1 | C2   |
    +----+------+
    |  2 |    1 |
    |  3 |    1 |
    |  1 |    2 |
    +----+------+
    3 rows in set
    
    obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 2\G
    *************************** 1. row ***************************
    Query Plan: 
    | ====================================
    |ID|OPERATOR   |NAME|EST. ROWS|COST|
    ------------------------------------
    |0 |SORT       |    |1000     |1886|
    |1 | TABLE SCAN|t1  |1000     |1381|
    ====================================
    Outputs & filters:
    -------------------------------------
      0 - output([T1.C1], [T1.C2]), filter(nil), sort_keys([T1.C2, ASC])
      1 - output([T1.C1], [T1.C2]), filter(nil),
          access([T1.C1], [T1.C2]), partitions(p0)
    

因此,如果将 ORDER BY 后面的常量参数化,不同 ORDER BY 的值具有相同的参数化后的 SQL,从而导致命中错误的计划。

解决常量参数化的误匹配问题

为了解决常量参数化中可能存在的误匹配问题,在硬解析生成执行计划过程中会对 SQL 请求使用分析语法树的方法进行参数化,并获取相应的不一致的信息。例如,某语句对应的信息是"快速参数化参数数组的第 3 项必须为数字 3",可将其称为"约束条件"。

对于 Q1 查询 SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 3;,经过词法分析,可以得到参数化后的 SQL 语句如下所示,参数化数组为 {1,'senior%',3}。

obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY @3;

当 ORDER BY 后面的常量不相同时,不能共用相同的执行计划,因此在通过分析语法树进行参数化时会获得另一种参数化结果。如下例所示,Q1 查询对应的参数化数组为 {1,'senior'},约束条件为"快速参数化参数数组的第 3 项必须为数字 3"。OceanBase 数据库将 Q1 查询新生成的参数化后的文本及约束条件和执行计划均会存入计划缓存中。

obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY 3;

当用户再次发出 Q2 查询命令 SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 2; 时,经过快速参数化后结果如下例所示,对应的参数化数组为 {1,'senior%',2}。

obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 and c2 like @2 ORDER BY @3;

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

参数化执行计划的区分策略

OceanBase 数据库还支持通过 Hint 控制是否对 SQL 语句中的字面量做参数改写,详细信息参见 CURSOR_SHARING_EXACT Hint

对于一条 SQL 请求,使用如下策略区分此条 SQL 要命中参数化的计划还是要命中非参数化的计划:

  1. 查看 cursor_sharing

    首先查看 cursor_sharing 系统变量,具有不同 cursor_sharing 的 SQL 一定会命中到不同的计划。请参见 cursor_sharing

  2. 查看 query_sql

    如果 cursor_sharing 变量无法帮助我们辨别出是否为参数化计划时,则说明 cursor_sharing 属于 EXACT 模式是由 CURSOR_SHARING_EXACT Hint 控制的,由于添加了 Hint 就会影响到 query_sql 的生成,因此,我们可以通过 query_sql 来区分参数化计划和非参数化计划。query_sql 可以通过 GV$OB_SQL_AUDIT 或者 GV$OB_PLAN_CACHE_PLAN_STAT 视图查看。参数化过程是指把 SQL 查询中的常量变成变量的过程。

    基于快速参数化获取执行计划

    同一条 SQL 语句在每次执行时可能会使用不同的参数,将这些参数做参数化处理,可以得到与具体参数无关的 SQL 字符串,并使用该字符串作为计划缓存的键值,用于在计划缓存中获取执行计划,从而达到参数不同的 SQL 能够共用相同的计划目的。

    由于传统数据库在进行参数化时一般是对语法树进行参数化,然后使用参数化后的语法树作为键值在计划缓存中获取计划,而 OceanBase 数据库使用的词法分析对文本串直接参数化后作为计划缓存的键值,因此叫做快速参数化。

    OceanBase 数据库支持自适应计划共享(Adaptive Cursor Sharing)功能以支持不同参数条件下的计划选择。

    基于快速参数化而获取执行计划的流程如下图所示:

    p179646

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

    • 节省了语法分析过程。

    • 查找 Hash Map 时,可以将对参数化后语法树的哈希和比较操作,替换为对文本串进行哈希和 MEMCMP 操作,以提高执行效率。

    常量参数化及约束条件

    在 OceanBase 数据库中,某些场景中的常量不能参数化,即参数化的约束条件如下:

    • 所有 ORDER BY 后常量(例如 ORDER BY 1,2;)。

    • 所有 GROUP BY 后常量(例如 GROUP BY 1,2;)。

    • LIMIT 后常量(例如 LIMIT 5;)。

    • 作为格式串的字符串常量(例如 SQL 语句 SELECT DATE_FORMAT('2006-06-00', '%d'); 中的%d)。

    • 函数输入参数中,影响函数结果并最终影响执行计划的常量(例如 SQL 语句 CAST(999.88 as NUMBER(2,1)) 中的 NUMBER(2,1),或者 SUBSTR('abcd', 1, 2) 中的 1 和 2)。

    • 函数输入参数中,带有隐含信息并最终影响执行计划的常量(例如 SQL 语句 SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19.012'); 中的"2015-11-13 10:20:19.012",指定输入时间戳的同时,隐含指定了函数处理的精度值为毫秒)。

    一般常量参数化示例

    现有 SQL 查询语句示例如下:

    obclient> SELECT * FROM t1 WHERE c1 = 5 AND c2 ='oceanbase';
    

    上述示例中的 SQL 查询在 OceanBase 数据库内部进行参数化处理后结果如下所示,常量 5 和 oceanbase 被参数化后变成了变量 @1 和 @2,并对比当前数据库库里已执行过的 SQL,并匹配对应的执行计划。

    obclient> SELECT * FROM T1 WHERE c1 = @1 AND c2 = @2;
    

    常量不支持参数化示例

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

    创建示例表 t1 并插入适当的数据,表 t1 中包含 c1c2 列,其中 c1 为主键列。

    obclient> CREATE TABLE t1(c1 INT PRIMARY KEY,c2 INT);
    Query OK, 0 rows affected
    
    obclient> INSERT INTO t1 VALUES (1,2);
    Query OK, 1 row affected 
    
    obclient> INSERT INTO t1 VALUES (2,1);
    Query OK, 1 row affected 
    
    obclient> INSERT INTO t1 VALUES (3,1);
    Query OK, 1 row affected
    
    • 执行如下 SQL 查询,将结果按照 c1 列进行排序,由于 c1 作为主键列是有序的,所以使用主键访问可以免去排序。

      obclient> SELECT c1, c2 FROM t1 ORDER BY 1;
      +----+------+
      | C1 | C2   |
      +----+------+
      |  1 |    2 |
      |  2 |    1 |
      |  3 |    1 |
      +----+------+
      3 rows in set 
      
      obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 1\G
      *************************** 1. row ***************************
      Query Plan: 
      | ===================================
      |ID|OPERATOR  |NAME|EST. ROWS|COST|
      -----------------------------------
      |0 |TABLE SCAN|t1  |1000     |1381|
      ===================================
      Outputs & filters:
      -------------------------------------
        0 - output([T1.C1], [T1.C2]), filter(nil),
            access([T1.C1], [T1.C2]), partitions(p0)
      
    • 执行如下 SQL 查询,将结果按照 c2 列进行排序,则需要执行显示的排序操作,执行计划如下例所示:

      obclient> SELECT c1, c2 FROM t1 ORDER BY 2;
      +----+------+
      | C1 | C2   |
      +----+------+
      |  2 |    1 |
      |  3 |    1 |
      |  1 |    2 |
      +----+------+
      3 rows in set
      
      obclient> EXPLAIN SELECT c1, c2 FROM t1 ORDER BY 2\G
      *************************** 1. row ***************************
      Query Plan: 
      | ====================================
      |ID|OPERATOR   |NAME|EST. ROWS|COST|
      ------------------------------------
      |0 |SORT       |    |1000     |1886|
      |1 | TABLE SCAN|t1  |1000     |1381|
      ====================================
      Outputs & filters:
      -------------------------------------
        0 - output([T1.C1], [T1.C2]), filter(nil), sort_keys([T1.C2, ASC])
        1 - output([T1.C1], [T1.C2]), filter(nil),
            access([T1.C1], [T1.C2]), partitions(p0)
      

    因此,如果将 ORDER BY 后面的常量参数化,不同 ORDER BY 的值具有相同的参数化后的 SQL,从而导致命中错误的计划。

    解决常量参数化的误匹配问题

    为了解决常量参数化中可能存在的误匹配问题,在硬解析生成执行计划过程中会对 SQL 请求使用分析语法树的方法进行参数化,并获取相应的不一致的信息。例如,某语句对应的信息是"快速参数化参数数组的第 3 项必须为数字 3",可将其称为"约束条件"。

    对于 Q1 查询 SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 3;,经过词法分析,可以得到参数化后的 SQL 语句如下所示,参数化数组为 {1,'senior%',3}。

    obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY @3;
    

    当 ORDER BY 后面的常量不相同时,不能共用相同的执行计划,因此在通过分析语法树进行参数化时会获得另一种参数化结果。如下例所示,Q1 查询对应的参数化数组为 {1,'senior'},约束条件为"快速参数化参数数组的第 3 项必须为数字 3"。OceanBase 数据库将 Q1 查询新生成的参数化后的文本及约束条件和执行计划均会存入计划缓存中。

    obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 AND c2 LIKE @2 ORDER BY 3;
    

    当用户再次发出 Q2 查询命令 SELECT c1, c2, c3 FROM t1 WHERE c1 = 1 AND c2 LIKE 'senior%' ORDER BY 2; 时,经过快速参数化后结果如下例所示,对应的参数化数组为 {1,'senior%',2}。

    obclient> SELECT c1, c2, c3 FROM t1 WHERE c1 = @1 and c2 like @2 ORDER BY @3;
    

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

    参数化执行计划的区分策略

    OceanBase 数据库还支持通过 Hint 控制是否对 SQL 语句中的字面量做参数改写,详细信息参见 CURSOR_SHARING_EXACT Hint

    对于一条 SQL 请求,使用如下策略区分此条 SQL 要命中参数化的计划还是要命中非参数化的计划:

    1. 查看 cursor_sharing

      首先查看 cursor_sharing 系统变量,具有不同 cursor_sharing 的 SQL 一定会命中到不同的计划。请参见 cursor_sharing

    2. 查看 query_sql

      如果 cursor_sharing 变量无法帮助我们辨别出是否为参数化计划时,则说明 cursor_sharing 属于 EXACT 模式是由 CURSOR_SHARING_EXACT Hint 控制的,由于添加了 Hint 就会影响到 query_sql 的生成,因此,我们可以通过 query_sql 来区分参数化计划和非参数化计划。query_sql 可以通过 GV$OB_SQL_AUDIT 或者 GV$OB_PLAN_CACHE_PLAN_STAT 视图查看。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论