在11g中,Oracle引入了一项新特征:adaptive cursor sharing 自适应游标共享。这项特征主要用来改进具有绑定变量的sql语句的执行计划,也导致了具有绑定变量的sql语句可能会生成多个游标。在9i中,Oracle引入了变量窥测(bind peeking)技术,通过使用变量窥测在SQL语句第一次硬解析时,优化器可以判定where子句的选择性,从而改进生成执行计划的质量。但是使用变量窥测技术生成的执行计划在表数据分布不均衡的情况下,往往不具有通用性。(参见:)
自适应游标共享功能的引入,可以有效的解决这个问题。
首先看一下我们的测试环境:
SQL> desc acs_test_tab
名称 是否为空? 类型
----------------------------------------------------- -------- ------------------------------------
ID NOT NULL NUMBER
RECORD_TYPE NUMBER
DESCRIPTION VARCHAR2(50)
SQL> select count(*) from acs_test_tab;
COUNT(*)
----------
100000
SQL> select count(*) from acs_test_tab where record_type=2;
COUNT(*)
----------
50000
SQL> select count(distinct record_type) from acs_test_tab;
COUNT(DISTINCTRECORD_TYPE)
--------------------------
50001
表acs_test_Tab在列record_type上分布式是倾斜的。收集统计信息:
SQL> exec dbms_stats.gather_Table_Stats(user,'acs_test_Tab',cascade=>true,method_opt=>'for all columns size auto');
PL/SQL 过程已成功完成。
SQL> select column_name,histogram from user_tab_cols where table_name='ACS_TEST_TAB';
COLUMN_NAME HISTOGRAM
------------------------------ ---------------
ID NONE
RECORD_TYPE HEIGHT BALANCED
DESCRIPTION NONE
首先我们对record_type 为1 的列进行查询
SQL> select count(*) from acs_test_tab where record_type = 1;
COUNT(*)
----------
1
SQL> alter system flush shared_pool;
系统已更改。
SQL> var v number;
SQL> exec :v := 1
PL/SQL 过程已成功完成。
SQL> select sum(id) from acs_test_tab where record_type = :v;
SUM(ID)
----------
1
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 3p66zbwtm19bs, child number 0
-------------------------------------
select sum(id) from acs_test_tab where record_type = :v
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("RECORD_TYPE"=:V)
已选择20行。
SQL> select child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware
2 from v$sql
3 where sql_text like 'select sum(id)%';
CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------ ---------- ----------- - -
0 1 218 Y N
下面我们在查询一下record_type为2的记录,
SQL> exec :v := 2
PL/SQL 过程已成功完成。
SQL> select sum(id) from acs_test_tab where record_type = :v;
SUM(ID)
----------
2500050000
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 3p66zbwtm19bs, child number 0
-------------------------------------
select sum(id) from acs_test_tab where record_type = :v
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("RECORD_TYPE"=:V)
已选择20行。
SQL> select child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware
2 from v$sql
3 where sql_text like 'select sum(id)%';
CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------ ---------- ----------- - -
0 2 832 Y N
我们发现执行计划没有变化,但是统计信息却发生了比较大的跳跃。
再次执行上面的语句
SQL> select sum(id) from acs_test_tab where record_type = :v;
SUM(ID)
----------
2500050000
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 3p66zbwtm19bs, child number 1
-------------------------------------
select sum(id) from acs_test_tab where record_type = :v
Plan hash value: 509473618
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 137 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
|* 2 | TABLE ACCESS FULL| ACS_TEST_TAB | 48425 | 425K| 137 (1)| 00:00:02 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("RECORD_TYPE"=:V)
已选择19行。
SQL> select child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware
2 from v$sql
3 where sql_text like 'select sum(id)%';
CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------ ---------- ----------- - -
0 2 832 Y N
1 1 510 Y Y
这次执行计划发生了变化,这是因为通过前面两次的运行,oracle发现不同的变量值会引起不同的数据访问特征,因此将游标设置为bind_aware,此时oracle会根据绑定变量的选择性来选择不同的执行计划,如果某已选择性的执行计划不存在则生成新的执行计划,子游标为0的执行计划会被置为nosharable,不再被使用并逐渐丢弃。如下面
SQL> exec :v := 1
PL/SQL 过程已成功完成。
SQL> select sum(id) from acs_test_tab where record_type = :v;
SUM(ID)
----------
1
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 3p66zbwtm19bs, child number 2
-------------------------------------
select sum(id) from acs_test_tab where record_type = :v
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("RECORD_TYPE"=:V)
已选择20行。
SQL> select child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable
2 from v$sql
3 where sql_text like 'select sum(id)%';
CHILD_NUMBER EXECUTIONS BUFFER_GETS I I I
------------ ---------- ----------- - - -
0 2 832 Y N N
1 1 510 Y Y Y
2 1 131 Y Y Y
下面,让我们来总结一下:
为了解决变量窥测在数据分布倾斜的列上造成的执行计划不具有通用行和效率低下,而引入了自适应游标共享。
当某游标被设置为BIND_SENSITIVE(指该游标可能会因为绑定变量的不同取值而具有不同的效率表现,因此oracle会监视bind_sensitive的游标);
当oracle发现bind_sensitive的游标确实会因为绑定变量的不同取值而表现出不同的效率时(如逻辑读的跳跃),oracle会记录该游标。在下一次调用该游标时,oracle根据绑定变量值生成新的游标,将新游标标记为BIND_AWARE,将就游标的共享标识置为NO,即就游标将逐渐被丢弃并置换出内存。
当oracle再次执行该语句时,会根据绑定便利的取值来计算选择性(例如,通过直方图),如果计算出的选择性已经存在于以往子游标中,则调用子游标,否则创建新的子游标;
如果新的子游标和旧子游标的执行计划相同,oracle会将其合并,使用新的子游标,并逐步丢弃旧子游标。
注意:
bind_sensitive:绑定变量可能影响执行计划,需要oracle来监视
bind_aware:绑定变量会影响执行计划,oracle会根据不同的绑定变量选择或者生产新的执行计划。