在 SQL 执行过程中,复用计划对于提升 SQL 性能至关重要,尤其是在复杂 SQL 解析耗时较长或 SQL 执行频率较高的情况下,通过参数化实现计划复用尤为重要。然而,由于各种原因(如 in 参数个数不一致、SQL 参数类型不一致等)导致 SQL 执行时进行硬解析,这会严重影响性能。因此,找到并减少硬解析的原因非常重要。本文介绍如何判断由于输入参数类型不一致导致的硬解析。
详细说明
测试用例:
创建表 t1,并且向表中插入数据如下。
obclient [test]> create table t1(c1 int, c2 char(10), c3 varchar(20));
Query OK, 0 rows affected (2.608 sec)
obclient [test]> select * from t1;
+------+------+------+
| c1 | c2 | c3 |
+------+------+------+
| 1 | abc | abcd |
| 2 | abc | |
| 4 | abc | NULL |
| 4 | abc | 123 |
| 4 | abc | NULL |
| 4 | NULL | NULL |
| 5 | vv | dd |
+------+------+------+
7 rows in set (0.025 sec)
执行下面的 SQL,但是 null 出现的位置和 null 的个数不同。
obclient [test]> update t1 set c1 = case when c1=1 then null when c1=2 then 21 when c1=3 then 32 when c1=4 then 43 when c1=5 then 54 end;
Query OK, 7 rows affected (0.143 sec)
Rows matched: 7 Changed: 7 Warnings: 0
obclient [test]> update t1 set c1 = case when c1=1 then 12 when c1=2 then null when c1=3 then 32 when c1=4 then 43 when c1=5 then 54 end;
Query OK, 6 rows affected (0.160 sec)
Rows matched: 7 Changed: 6 Warnings: 0
obclient [test]> update t1 set c1 = case when c1=1 then 15 when c1=2 then null when c1=3 then null when c1=4 then 43 when c1=5 then 54 end;
Query OK, 0 rows affected (0.419 sec)
Rows matched: 7 Changed: 0 Warnings: 0
通过 sql_id 查看 gv$ob_plan_cache_plan_stat,注意到 SQL 并没有命中计划(hit_count=0)。
或者也可以通过 gv$ob_sql_audit 中的 is_hit_plan 字段查看是否命中计划。
obclient [oceanbase]> select sql_id, plan_hash, param_infos, plan_size, hit_count,query_sql from gv$ob_plan_cache_plan_stat where sql_id = '3E7B1CD7E7398A514E776CFCACC87E13';
+----------------------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------+-----------+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| sql_id | plan_hash | param_infos | plan_size | hit_count | query_sql |
+----------------------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------+-----------+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 3E7B1CD7E7398A514E776CFCACC87E13 | 6549277336410376177 | {1,0,0,0,5},{1,0,0,-1,0},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5} | 203912 | 0 | update t1
set c1 = case when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ? end |
| 3E7B1CD7E7398A514E776CFCACC87E13 | 6549277336410376177 | {1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,-1,0},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5} | 203912 | 0 | update t1
set c1 = case when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ? end |
| 3E7B1CD7E7398A514E776CFCACC87E13 | 6549277336410376177 | {1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,-1,0},{1,0,0,0,5},{1,0,0,-1,0},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5},{1,0,0,0,5} | 203912 | 0 | update t1
set c1 = case when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ?
when c1=? then ? end |
+----------------------------------+---------------------+---------------------------------------------------------------------------------------------------------------------------+-----------+-----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.025 sec)
进一步查看 param_infos,注意到参数的个数为 10 个,对应 when 语句 c1(5 个)和 then 结果(5个)。
对于 param_infos{1,0,0,0,5} 的 5 个字段解释如下, 其中第 5 个字段为数据类型。
params_info_.at(i).flag_.need_to_check_type_,
params_info_.at(i).flag_.need_to_check_bool_value_,
params_info_.at(i).flag_.expected_bool_value_,
params_info_.at(i).scale_,
params_info_.at(i).type_,
对比看执行的 SQL 中,第一条语句中 null 出现位置 when c1=1 then null, 第二条语句中 null 出现位置 when c1=2 then null,第三条语句中 null 出现位置 when c1=2 then null 和 when c1=3 then null。可以看到对应 null 的 type value 为 0,对应 int 类型的 type value 5。
对于第一条语句 when c1=1 then null 中的 null 值出现在第二个参数。
对于第一条语句 when c1=1 then null 中的 null 值出现在第四个参数。
对于第一条语句 when c1=1 then null 中的 null 值出现在第四和六个参数。
因此由于参数的个数不同,位置不同导致虽然执行相同的 SQL,sql_id 相同的情况下,计划是不能复用的。

在实际使用过程中,null 是一种常见的导致计划类型变动的原因。
影响租户
影响 OceanBase 数据库中的 SYS 租户和 Oracle 租户以及 MySQL 租户。
适用版本
OceanBase 数据库 V3.x、V4.x 版本