从共享游标shared cursor角度看forall批量绑定

 

Oracle PL/SQL代码的编写中,我们一直倾向于利用forall批量绑定来替代传统的for循环语句。这样做的目的在于提高PL/SQL代码的处理效果。

 

那么,这么做我们可以获取到什么好处呢?从官方文档上的陈述看,forall可以显著地减少PL/SQL引擎和SQL引擎之间的切换频率,节省这部分的成本。

 

当我们使用传统的forall循环结构的时候,在循环体(loop…end loop)中,我们可以嵌入多条SQL或者PL/SQL代码。在Oracle PL/SQL引擎解析处理这部分内容时,每次循环for变量的时候,都会进入PL/SQL引擎和SQL引擎的上下文(Context)切换。所谓的上下文(Context)切换,最直观的理解就是数据类型的转换和传输。

 

但是,在forall循环过程中,事情有了很大的不同。Forall语句循环体中,只允许我们书写一句SQL语句,而且对集合变量的使用存在一些限制(11g中取消)。但是,在使用forall语句的时候,PL/SQLSQL引擎在切换的时候,只进行一次切换过程。也就是说,多条的Forall循环体SQL语句是一次性的传送到SQL引擎进行处理的。

 

Forall批量绑定特定推出后,已经被普遍认为是一个进行PL/SQL代码语句优化的策略。那么,两种循环体结构,在游标共享cursor sharing上有什么差别呢?本篇着重从shared cursor角度进行研究分析。

 

1、试验环境构建

 

我们选择Oracle 10g环境进行试验。

 

 

SQL> select * from v$version;

 

BANNER

----------------------------------------------------------------

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod

PL/SQL Release 10.2.0.1.0 - Production

CORE        10.2.0.1.0         Production

TNS for 32-bit Windows: Version 10.2.0.1.0 - Production

NLSRTL Version 10.2.0.1.0 - Production

 

 

构建数据表T

 

 

SQL> create table t as select * from dba_objects where 1=0;

Table created

 

 

为了保证记录游标信息,我们构建两张数据表,临时保存父子游标记录。

 

 

SQL> create table sqlarea_str as select * from v$sqlarea where 1=0;

Table created

 

SQL> create table sql_str as select * from v$sql where 1=0;

Table created

 

 

下面,我们分别使用两种循环语句,来进行判断。

 

2for循环体研究

 

我们书写一个PL/SQL匿名块,向数据表中插入记录。

 

 

SQL> alter system flush buffer_cache;

系统已更改。

 

SQL> alter system flush shared_pool;

系统已更改。

 

 

SQL> declare

  2  type t_obj_ids is table of dba_objects.object_id%type index by binary_integer;

  3  type t_obj_names is table of dba_objects.object_name%type index by binary_integer;

  4  t_obj_idinfos t_obj_ids;

  5  t_obj_nameinfos t_obj_names;

  6  vc_sql varchar2(1000);

  7  begin

  8    select object_id, object_name

  9    bulk collect into t_obj_idinfos, t_obj_nameinfos

 10    from dba_objects;

 11 

 12    vc_sql := 'insert /*+ DEMO 1*/into t (object_id, object_name) values (:1, :2)';

 13 

 14    for i in t_obj_idinfos.first..t_obj_nameinfos.last loop

 15       execute immediate vc_sql

 16                         using t_obj_idinfos(i), t_obj_nameinfos(i);

 17    end loop;

 18    commit;

 19 

 20  end;

 21  /

 

PL/SQL procedure successfully completed

 

 

 

插入记录之后,我们从v$sqlareav$sql中抽取特定的父子游标记录。

 

 

SQL> select count(*) from v$sqlarea where sql_text like 'insert /*+ DEMO 1*/into t%';

 

  COUNT(*)

----------

         1

 

SQL> select count(*) from v$sql where sql_text like 'insert /*+ DEMO 1*/into t%';

 

  COUNT(*)

----------

         1

 

--保存游标

SQL> insert into sqlarea_str select * from v$sqlarea where sql_text like 'insert /*+ DEMO 1*/into t%';

1 row inserted

 

SQL> insert into sql_str select * from v$sql where sql_text like 'insert /*+ DEMO 1*/into t%';

1 row inserted

 

SQL> commit;

Commit complete

 

 

下面试验forall模式。

 

3forall循环体研究

 

相同的数据表,相同的数据量,类似的匿名块构成。

 

 

SQL> truncate table t;

Table truncated

 

SQL> alter system flush buffer_cache;

系统已更改。

 

SQL> alter system flush shared_pool;

系统已更改。

 

 

SQL> declare

  2  type t_obj_ids is table of dba_objects.object_id%type index by binary_integer;

  3  type t_obj_names is table of dba_objects.object_name%type index by binary_integer;

  4  t_obj_idinfos t_obj_ids;

  5  t_obj_nameinfos t_obj_names;

  6  vc_sql varchar2(1000);

  7  begin

  8    select object_id, object_name

  9    bulk collect into t_obj_idinfos, t_obj_nameinfos

 10    from dba_objects;

 11 

 12    vc_sql := 'insert /*+ DEMO 2*/into t (object_id, object_name) values (:1, :2)';

 13 

 14    forall i in t_obj_idinfos.first..t_obj_nameinfos.last

 15       execute immediate vc_sql

 16                         using t_obj_idinfos(i), t_obj_nameinfos(i);

 17 

 18    commit;

 19 

 20  end;

 21  /

 

PL/SQL procedure successfully completed

 

 

定位父子游标记录。

 

 

SQL> select count(*) from v$sqlarea where sql_text like 'insert /*+ DEMO 2*/into t%';

 

  COUNT(*)

----------

         1

 

SQL> select count(*) from v$sql where sql_text like 'insert /*+ DEMO 2*/into t%';

 

  COUNT(*)

----------

         1

 

SQL> insert into sqlarea_str select * from v$sqlarea where sql_text like 'insert /*+ DEMO 2*/into t%';

1 row inserted

 

SQL> insert into sql_str select * from v$sql where sql_text like 'insert /*+ DEMO 2*/into t%';

1 row inserted

 

SQL> commit;

Commit complete

 

 

4、游标分析

 

从保存的记录中,我们可以比较方便的进行分析父子共享游标的情况。

 

父游标共享对比:

 

 

SQL> col sql_text for a20;

 

SQL> select sql_text, sql_id from sql_str;

 

SQL_TEXT                                                     SQL_ID

------------------------------------------------------------ -------------

insert /*+ DEMO 1*/into t (object_id, object_name) values (: 8cg3g8kwctcuk

1, :2)                                                      

 

insert /*+ DEMO 2*/into t (object_id, object_name) values (: 9z2a0hzqs5jws

1, :2)       

 

 

两个实验语句对应不同的sql_id,各种差异指标如下:

 

 

 

SQL> select sql_id, executions, END_OF_FETCH_COUNT, PARSE_CALLS, DISK_READS, BUFFER_GETS, USER_IO_WAIT_TIME, CPU_TIME, ELAPSED_TIME  from sqlarea_str;

 

SQL_ID        EXECUTIONS END_OF_FETCH_COUNT PARSE_CALLS DISK_READS BUFFER_GETS USER_IO_WAIT_TIME   CPU_TIME ELAPSED_TIME

------------- ---------- ------------------ ----------- ---------- ----------- ----------------- ---------- ------------

8cg3g8kwctcuk      50432              50432           1         28       56561            220007     563580       782689

9z2a0hzqs5jws          1                  1           1          2        3669             11499      31462        56070

 

 

由于篇幅原因,笔者整理好特定表格进行展现:

 

#

指标

DEMO-1

(sql_id=8cg3g8kwctcuk)

DEMO-2

(sql_id=9z2a0hzqs5jws)

 

Executions

50432

1

 

END_OF_FETCH_COUNT

50432

1

 

PARSE_CALLS

1

1

 

DISK_READS

28

2

 

BUFFER_GETS

56561

3669

 

USER_IO_WAIT_TIME

220007

11499

 

CPU_TIME

563580

31462

 

ELAPSED_TIME

782689

56070

 

 

 

 

 

从上面的图示中,我们可以看到父游标指标的差异。由于子游标内容和父游标相同,笔者就不单独进行阐述了。

 

在上面对比中,我们重点关注几个指标,可以体现出Forall批量绑定的特点。

 

ü  Executions数值:两个例子中,均是逐条插入了记录,且记录条数相同。但是,for语句循环体表示执行次数是50423,而forall只有1次。这个是一个非常大的差异。这个让我们可以看出,Oracle在处理forall bulk insert的时候,是采用了一次性将所有数值一次性执行插入的过程。而不是简单的在SQL引擎中逐条执行;

ü  游标生命周期fetch次数差异,Fetch次数上,forall要明显小于forall

ü  其他各种响应时间、读取数据量的差异。在处理时间和处理数据量上,forall也要明显好于for循环;

 

5、结论

 

在实际工作中,我们要注意由于使用forall而带来的性能问题“盲点”。在shared cursor机制中,我们是不能够看到哪个语句是采用forall的。那么,当我们使用自定义性能分析脚本或者AWR报告分析时,可能就会发现一条执行一次(Execution=1)的语句,消耗了很多的资源和时间。但是当我们自己执行这个语句时候,又发现不了什么问题。此时,我们可能会想到锁表等等。其实,forall批量绑定的使用,也许是主要原因。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/17203031/viewspace-742975/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/17203031/viewspace-742975/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值