绑定变量窥视(Bind Peeking )

 

Bind Peeking and Adaptive Cursor Sharing


Bind Peeking 就是当在WHERE条件中使用绑定变量的时候,CBO会根据第一次使用的真实变量值来生成一个执行计划。在这个cursor的整个生命周期中,CBO不会 再对相同的SQL进行hard parse。这种办法的优点是:如果索引字段的值是均匀分布的,hard parse就降低了,性能提高。但是缺点也很明显:如果字段分布不均匀,并且第一次使用值不具有普遍性,那么执行计划就将非常糟糕。

Oracle11g 提供了一个新特性,Adpative Cursor Sharing,或者叫 Extended Cursor Sharing,来解决这个问题。他的核心思想是,当某个字段的histogram提供了数据不均匀的信息,CBO会在实际使用不同值的时候,尝试重新生 成更合适的执行计划。

这里我使用一个数据不均匀的例子,来对比10g和11g的处理方式。
-- preparation
drop table test1 purge;
create table test1(id int,name varchar2(100)) tablespace users;

insert into test1
  (id, name)
  select rownum as id, 'name_'||rownum as name
    from dual
  connect by rownum <= 100000;
update test1 set id = 1 where id <= 10;
update test1 set id = 2 where id between 11 and 2000;
update test1 set id = 3 where id > 2000;  
commit;
create index idx_test1_n1 on test1 (id);

-- calc the histogram
begin
  dbms_stats.gather_table_stats(user,
                                'TEST1',
                                cascade => true,
                                method_opt => 'FOR All INDEXED COLUMNS');
end;
/

-- check data count
select id,count(1) from test1 group by id order by id;

   ID   COUNT(1)
----- ----------
    1         10
    2       1990
    3      98000
 

在10g中使用的测试代码
set lines 120;
spool e:\bind_peeking_10g.log
select * from v$version;
alter session set cursor_sharing=similar;
-- histogram
select column_name, histogram from user_tab_cols where table_name='TEST1';

alter system flush shared_pool;
prompt '先大量再少量'
var id number;
exec :id := 3;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
exec :id := 1;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
 
alter system flush shared_pool;
prompt '先少量再大量'
exec :id := 1;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
exec :id := 3;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);

prompt '随机执行几次'
exec :id := 3;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
exec :id := 2;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
 
spool off

因为LOG比较长,这里对部分内容进行了删除
log_10g
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Prod
PL/SQL Release 10.2.0.4.0 - Production
CORE    10.2.0.4.0    Production
TNS for Linux: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production

。。。。。。

'先少量再大量'

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
        10 name_9


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |    36 |   468 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |    36 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------


  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
     98000 name_99999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |    36 |   468 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |    36 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

到这里我们就可以看到,因为第一次使用的值选择性高,所以CBO生成了使用索引的计划。后面使用不同的绑定值,计划不会发生改变

'随机执行几次'

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
     98000 name_99999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |    36 |   468 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |    36 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
      1990 name_999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |    36 |   468 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |    36 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
随后的几次执行中,执行计划也不会根据绑定值的变化而变化。

从上面的结果我们已经看到,在Oracle10g中,如果第一次使用的绑定值不具有普遍性,那么生成的执行计划,对后面不同绑定值的执行,可能带来灾难性的影响。比如使用值3生成full table scan计划,但是实际执行的大多数时间是使用值1,2进行查询。

11g提供了新的解决方案,先做个测试

在11g中使用的测试代码(部分),代码和10g类似,但是加入了对v$sql视图新字段的查询
。。。。
---- check adaptive cursor sharing info
select child_number, is_bind_sensitive, is_bind_aware, is_shareable
  from v$sql
 where sql_id = 'bvqnv1dsynqdj'
 order by child_number;

prompt '随机执行几次'
exec :id := 3;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);
exec :id := 2;
select count(1),max(name) from test1 where id = :id;
select * from table(dbms_xplan.display_cursor);

---- check adaptive cursor sharing info
select child_number, is_bind_sensitive, is_bind_aware, is_shareable
  from v$sql
 where sql_id = 'bvqnv1dsynqdj'
 order by child_number;

因为LOG比较长,这里对部分内容进行了删除
log_11g
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - Production
PL/SQL Release 11.1.0.6.0 - Production
CORE    11.1.0.6.0    Production
TNS for 32-bit Windows: Version 11.1.0.6.0 - Production
NLSRTL Version 11.1.0.6.0 - Production

。。。

'先少量再大量'

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
        10 name_9


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |     1 |    13 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------


  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
     98000 name_99999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 0
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |     2 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |     1 |    13 |     2   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

到这里都和10g中的情况一样,使用第一次的绑定值生成索引扫描的计划

---- check adaptive cursor sharing info
select child_number, is_bind_sensitive, is_bind_aware, is_shareable
  from v$sql
 where sql_id = 'bvqnv1dsynqdj'
 order by child_number;

CHILD_NUMBER I I I
------------ - - -
           0 Y N Y

到这里还只有一个cursor,11g新增的字段含义如下
is_bind_sensitive indicates not only whether bind variable peeking was used to generate
the execution plan but also whether the execution plan depends on the peeked value. If
this is the case, the column is set to Y; otherwise, it¡¯s set to N.
is_bind_aware indicates whether the cursor is using extended cursor sharing. If yes, the
column is set to Y; if not, it¡¯s set to N. If set to N, the cursor is obsolete, and it will no longer
be used.
is_shareable indicates whether the cursor can be shared. If it can, the column is set to Y;
otherwise, it¡¯s set to N. If set to N, the cursor is obsolete, and it will no longer be used.

'随机执行几次'

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
     98000 name_99999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 1
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 3896847026

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |       |       |   103 (100)|          |
|   1 |  SORT AGGREGATE    |       |     1 |    13 |            |          |
|*  2 |   TABLE ACCESS FULL| TEST1 | 98167 |  1246K|   103   (1)| 00:00:02 |
----------------------------------------------------------------------------
注意这里,因为使用了绑定值3,CBO重新生成了更适合的full table scan

  COUNT(1) MAX(NAME)
---------- ----------------------------------------------------------------------------------------------------
      1990 name_999


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID  bvqnv1dsynqdj, child number 2
-------------------------------------
select count(:"SYS_B_0"),max(name) from test1 where id = :id

Plan hash value: 1876089611

---------------------------------------------------------------------------------------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |              |       |       |    10 (100)|          |
|   1 |  SORT AGGREGATE              |              |     1 |    13 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| TEST1        |  1824 | 23712 |    10   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | IDX_TEST1_N1 |  1824 |       |     4   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
注意这里,因为使用了绑定值2,CBO仍然使用更适合的index range scan

CHILD_NUMBER I I I
------------ - - -
           0 Y N N
           1 Y Y Y
           2 Y Y Y
这里可以看到,原始生成的cursor已经被废弃(is_shareable=N),同时又生成了2个不同的cursor(child_number=1,2)。我们可以理解为这是2个不同的执行计划。

目 前我还不清楚Oracle是如何实现的,但是我觉得Oracle11g不会在每次执行的时候都进行hard parse。我的理解是,在实际执行中,通过新增加的几个视图(v$sql_cs_statistics, v$sql_cs_selectivity, v$sql_cs_histogram)来判断是否存在合适的计划,如果找到就直接使用,否则就 生成一个新的计划。这个还需要进一步研究和测试。

你可能感兴趣的:(bind)