绑定变量是我们目前开发系统应用中广泛使用的数据库SQL语言技术。大多数情况下,我们借助绑定变量可以有效的提高shared cursor使用的效率,减少SQL硬解析的出现。
在Oracle经典著作《Troubleshooting Oracle Performance》(简称《Troubleshooting》)中,Christian Antognini介绍了两种使用绑定变量带来的问题,一个就是“大名鼎鼎”的bind peeking,另一个就是bind graduation.
Bind peeking因其在执行“Bad”SQL时,存在强制共享非最优执行计划的可能被广泛关注。而bind graduation无论在网络还是MOS中,都是较少涉及的话题。本篇就介绍一下Oracle bind graduation技术细节。
1、 Bind Graduation简介
Bind Graduation技术主要是针对字符串绑定变量的一种SQL共享技术。在我们使用SQL绑定变量时,除了字面SQL和其他一些环境变量因素会影响到SQL子游标共享外,使用绑定变量的长度也是会影响到SQL cursor sharing的。
根据《Troubleshooting》中的介绍,Oracle对输入绑定变量字符串进行长度分区。小于32 btyes长度的为一个划分区,32 bytes到128 bytes为第二区。其他的上限分别为1000和2000。当输入的绑定变量字符串定义var在不同区内的时候,是不会发生子游标共享的。
Bind Graduation技术的推出,初衷还是为了加强共享游标的可用性和一定程度上提高执行计划最优性。实际情况是如何呢?我们下面分别进行试验验证。
2、 纯SQL语句下的graduation
下面实验使用《Troubleshooting》的案例,在Oracle 11gR2下进行。
SQL> select * from v$version;
BANNER
-----------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
SQL> create table t (n number(10), v varchar2(100));
Table created
进行绑定变量插入。
SQL> var n number;
SQL> var v varchar2(32); //定义了一个32位的字符串;
SQL> exec :n:=1; :v:='Helicon';
PL/SQL procedure successfully completed
n
---------
1
v
---------
Helicon
//第一次执行语句;
SQL> insert /*+ bind_date_length */ into t (n,v) values (:n, :v);
1 row inserted
n
---------
1
v
---------
Helicon
SQL> commit;
Commit complete
此时,我们检查游标情况。
//父游标情况;
SQL> select sql_text, sql_id, address, version_count, EXECUTIONS from v$sqlarea where sql_text like 'insert /*+ bind_date_length */%';
SQL_TEXT SQL_ID ADDRESS VERSION_COUNT EXECUTIONS
----------------------------------------------------------- -------------
insert /*+ bind_date_length */ into t (n,v) values (:n, :v) 5j0dt5yvaj59r 3F522248 1 1
//子游标情况;
SQL> select sql_id, child_number, address, child_address, sqltype from v$sql where sql_id='5j0dt5yvaj59r';
SQL_ID CHILD_NUMBER ADDRESS CHILD_ADDRESS SQLTYPE
------------- ------------ -------- ------------- ----------
5j0dt5yvaj59r 0 3F522248 475047B8 6
上面的结果中,我们发现执行语句生成了一对父子游标。此时,我们需要引入视图v$sql_bind_metadata,表示某个特定子游标的某个绑定变量元数据信息。使用子游标v$sql的child_address进行连接定位。
SQL> select * from v$sql_bind_metadata where address='475047B8';
ADDRESS POSITION DATATYPE MAX_LENGTH ARRAY_LEN BIND_NAME
-------- ---------- ---------- ---------- ----------
475047B8 2 1 4000 0 V
475047B8 1 2 22 0 N
datatype中,1表示varchar2,而2表示number。注意这里和书中实验结果不同,字符串绑定变量直接最大值为4000,是varchar2的极限大小。根据绑定变量游标内存保存机制,意味着Oracle会分配最大(但不一定立即分配)4000bytes大小的空间保存绑定变量。这样,也就意味着绑定变量元数据存在共享的空间。
下面,我们重新定义绑定变量。
SQL> var v varchar2(33);
SQL> exec :n:=4; :v:='Terminus';
PL/SQL procedure successfully completed
n
---------
4
v
---------
Terminus
SQL> insert /*+ bind_date_length */ into t (n,v) values (:n, :v);
1 row inserted
n
---------
4
v
---------
Terminus
SQL> commit;
Commit complete
我们越界定义了v值,之后查看绑定变量的情况。
SQL> select sql_text, sql_id, address, version_count, EXECUTIONS from v$sqlarea where sql_text like 'insert /*+ bind_date_length */%';
SQL_TEXT SQL_ID ADDRESS VERSION_COUNT EXECUTIONS
----------------------------------------------------------- ------------- -------- ------------- ----------
insert /*+ bind_date_length */ into t (n,v) values (:n, :v) 5j0dt5yvaj59r 3F522248 1 2
SQL> select * from v$sql_bind_metadata where address='475047B8';
ADDRESS POSITION DATATYPE MAX_LENGTH ARRAY_LEN BIND_NAME
-------- ---------- ---------- ---------- ----------
475047B8 2 1 4000 0 V
475047B8 1 2 22 0 N
注意:此时发生了SQL游标共享。相同的父游标子游标执行了两次,没有生成新的子游标。意味着发生了游标共享。子游标绑定变量元数据的max_length没有发生变化。
至此,书中的Demo演示实验在我们的环境中是不起作用的。在Oracle 11gR2环境下,直接执行SQL语句使用绑定变量,时不会发生bind graduation现象的。
那么怎么样才能激发出bind graduation呢?我们使用pl/sql程序实验。
3、PL/SQL下的bind graduation
纯SQL下是不会出现bind graduation的,那么使用PL/SQL程序中使用绑定变量,是否有这样的现象呢?
首先执行下面匿名PL/SQL块。
declare
n number(10);
v varchar2(32); //第一次声明varchar2(32)
begin
n := 14;
v := 'Tom Kyte';
execute immediate 'insert /*+ bind_data_plsql */into t values (:x, :y)'
using n, v;
commit;
end;
/
之后,检查父子游标情况如下:
SQL> select sql_text, sql_id, address, version_count, EXECUTIONS from v$sqlarea where sql_text like 'insert /*+ bind_data_plsql */%';
SQL_TEXT SQL_ID ADDRESS VERSION_COUNT EXECUTIONS
---------------------------------------------------- ------------- -------- ------------- ----------
insert /*+ bind_data_plsql */into t values (:x, :y) a765qwfk986aj 4D74ECA8 1 1
SQL> select sql_id, child_number, address, child_address, sqltype from v$sql where sql_id='a765qwfk986aj';
SQL_ID CHILD_NUMBER ADDRESS CHILD_ADDRESS SQLTYPE
------------- ------------ -------- ------------- ----------
a765qwfk986aj 0 4D74ECA8 4D74EA24 6
使用子游标地址child_address,获取本次子游标绑定变量的元数据信息。
SQL> select * from v$sql_bind_metadata where address='4D74EA24';
ADDRESS POSITION DATATYPE MAX_LENGTH ARRAY_LEN BIND_NAME
-------- ---------- ---------- ---------- ---------- ------------------------------
4D74EA24 2 1 32 0 Y
4D74EA24 1 2 22 0 X
注意,Max_length的取值不再是4000,而是传说中的32 bytes!似乎在PL/SQL下,bind graduation是存在的。
形成了一对父子游标,之后改变变量声明长度,进行试验。
SQL> declare
2 n number(10);
3 v varchar2(100); //定义一个超过128的变量。
4 begin
5 n := 14;
6 v := 'Tom Kyte';
7
8 execute immediate 'insert /*+ bind_data_plsql */into t values (:x, :y)'
9 using n, v;
10 commit;
11 end;
12 /
PL/SQL procedure successfully completed
此时父子游标情况如下:
SQL> select sql_text, sql_id, address, version_count, EXECUTIONS from v$sqlarea where sql_text like 'insert /*+ bind_data_plsql */%';
SQL_TEXT SQL_ID ADDRESS VERSION_COUNT EXECUTIONS
--------------------------------------------------- ------------- -------- ------------- ----------
insert /*+ bind_data_plsql */into t values (:x, :y) a765qwfk986aj 4D74ECA8 2 2
SQL> select sql_id, child_number, address, child_address, sqltype from v$sql where sql_id='a765qwfk986aj';
SQL_ID CHILD_NUMBER ADDRESS CHILD_ADDRESS SQLTYPE
------------- ------------ -------- ------------- ----------
a765qwfk986aj 0 4D74ECA8 4D74EA24 6
a765qwfk986aj 1 4D74ECA8 49D19D5C 6
相同的SQL语句字面和环境信息,只是声明的绑定变量长度差异。引起新子游标的生成。
SQL> select * from v$sql_bind_metadata where address='4D74EA24';
ADDRESS POSITION DATATYPE MAX_LENGTH ARRAY_LEN BIND_NAME
-------- ---------- ---------- ---------- ---------- ------------------------------
4D74EA24 2 1 32 0 Y
4D74EA24 1 2 22 0 X
SQL> select * from v$sql_bind_metadata where address='49D19D5C';
ADDRESS POSITION DATATYPE MAX_LENGTH ARRAY_LEN BIND_NAME
-------- ---------- ---------- ---------- ---------- ------------------------------
49D19D5C 2 1 128 0 Y
49D19D5C 1 2 22 0 X
我们使用子游标child_address检查绑定变量元数据信息,发现了传说中的128。也就是说,当PL/SQL执行的时候,Oracle不会将绑定变量如SQL执行时,直接判定绑定变量(字符型)为最大4000bytes,而是分区分别进行。
如果声明的绑定变量varchar2为32bytes以下,那么Oracle对bind variable的定义就是max_length为32。这样其他所有生成32bytes绑定变量的SQL起码在绑定变量元数据(bind variable metadata)的层面上,是可以共享的。
如果超过了32位,就进行128bytes上限的范围,保留绑定变量大小的空间就是128bytes了。绑定变量元数据发生了变化,自然也就不可能进行cursor sharing,子游标共享了。于是Oracle必然重新生成一个child cursor对应。
上面的实验,告诉了我们bind graduation的几个特性:
1、在纯SQL中不存在使用bind graduation的情况,而是在pl/sql中使用;
2、0-32、32-128分别为两个bind分区,不同分区的绑定变量声明值不共享游标;
在MOS[ID 258742.1]中,找到一段关于graduation的只言片语描述:
Depending on the length used, the character binds in the child cursor can all be created using the same bind length;
skipping bind graduation and keeping the child chain relatively small.
This helps to alleviate a potential cursor-sharing problem related to graduated binds.
The level of the event is the bind length to use, in bytes.
从上面的描述中,我们可以看到graduation的技术初衷还是为了缓解(而不是解决)绑定变量带来的游标共享问题。
下面,我们还有一些疑问:除了128下面的分区,还有没有其他的分区片段?超过分区限度的SQL语句如何进行共享?我们在下篇中继续讨论。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/17203031/viewspace-704144/ ,如需转载,请注明出处,否则将追究法律责任。