在9i中,Oracle引入了变量窥测(bind peeking)技术,通过使用变量窥测在SQL语句第一次硬解析时,优化器可以判定where子句的选择性,从而改进生成执行计划的质量。但是使用变量窥测技术生成的执行计划在表数据分布不均衡的情况下,往往不具有通用性。
在11g中,oracle引入了一项新特征:adaptive cursor sharing 自适应游标共享。这项特征主要用来改进具有绑定变量的sql语句的执行计划,也导致了具有绑定变量的sql语句可能会生成多个游标。
自适应游标共享功能的引入,可以有效的解决这个问题。
示例:
准备测试数据:
create table t1 as select object_id as id,object_name from dba_objects where rownum<=10000;
update t1 set id=1 where rownum<10000;
update t1 set id=2 where id<>1;
commit;
create index idx_t1 on t1(id);
exec dbms_stats.gather_table_stats(user,'T1',cascade => true,method_opt => 'for columns id size 254')
SQL> select count(*) from t1 where id=2;
COUNT(*)
----------
1
SQL> select count(*) from t1 where id=1;
COUNT(*)
----------
9999
SQL> alter system flush shared_pool;
System altered.
先通过绑定变量的方式查询id为2的值
SQL> var v number;
SQL> exec :v:=2
PL/SQL procedure successfully completed.
SQL> select count(distinct(object_name)) from t1 where id=:v;
COUNT(DISTINCT(OBJECT_NAME))
----------------------------
1
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID g3y76wm0v21f7, child number 0
-------------------------------------
select count(distinct(object_name)) from t1 where id=:v
Plan hash value: 3084587832
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
| 2 | VIEW | VW_DAG_0 | 1 | 66 | 3 (34)| 00:00:01 |
| 3 | HASH GROUP BY | | 1 | 21 | 3 (34)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 21 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IDX_T1 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("ID"=:V)
22 rows selected.
SQL> select sql_id,child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware from v$sql
2 where sql_text like 'select count(distinct(object_name)) from t1 where id%';
SQL_ID CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------- ------------ ---------- ----------- - -
g3y76wm0v21f7 0 1 52 Y N
由于id=2的值只有一笔,所以oracle选择了正确的执行计划。
接着查询id为1的值
SQL> exec :v := 1
PL/SQL procedure successfully completed.
SQL> select count(distinct(object_name)) from t1 where id=:v;
COUNT(DISTINCT(OBJECT_NAME))
----------------------------
8111
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID g3y76wm0v21f7, child number 0
-------------------------------------
select count(distinct(object_name)) from t1 where id=:v
Plan hash value: 3084587832
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
| 2 | VIEW | VW_DAG_0 | 1 | 66 | 3 (34)| 00:00:01 |
| 3 | HASH GROUP BY | | 1 | 21 | 3 (34)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 21 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IDX_T1 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("ID"=:V)
22 rows selected.
SQL> select sql_id,child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware from v$sql
2 where sql_text like 'select count(distinct(object_name)) from t1 where id%';
SQL_ID CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------- ------------ ---------- ----------- - -
g3y76wm0v21f7 0 2 111 Y N
在查询id=1时,除了executions和buffer_gets有变化外,包括执行计划以及其它的所有值都没有什么变化。id=1查询时明显这个执行执行计划是错误的,可是oracle还是重用的之前的执行计划。
下面重新查询一次id=1的数据
SQL> select count(distinct(object_name)) from t1 where id=:v;
COUNT(DISTINCT(OBJECT_NAME))
----------------------------
8111
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID g3y76wm0v21f7, child number 1
-------------------------------------
select count(distinct(object_name)) from t1 where id=:v
Plan hash value: 405047221
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 14 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
| 2 | VIEW | VW_DAG_0 | 9999 | 644K| 14 (8)| 00:00:01 |
| 3 | HASH GROUP BY | | 9999 | 205K| 14 (8)| 00:00:01 |
|* 4 | TABLE ACCESS FULL| T1 | 9999 | 205K| 13 (0)| 00:00:01 |
---------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("ID"=:V)
21 rows selected.
SQL> select sql_id,child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware from v$sql
2 where sql_text like 'select count(distinct(object_name)) from t1 where id%';
SQL_ID CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------- ------------ ---------- ----------- - -
g3y76wm0v21f7 0 2 111 Y N
g3y76wm0v21f7 1 1 40 Y Y
这次执行计划发生了变化,选择了正常的全表扫描。这是因为通过前面两次的运行,oracle发现不同的变量值会引起不同的数据访问特征,因此将游标设置为bind_aware,此时oracle会根据绑定变量的选择性来选择不同的执行计划,如果某已选择性的执行计划不存在则生成新的执行计划,子游标为0的执行计划会被置为不可共享使用,不再被使用并逐渐丢弃。
现在child_number为0的子sqlis_bind_aware为0说明,此游标的执行计划已被丢弃,那么如果再执行一次id=2的会发生什么?
SQL> exec :v := 2;
PL/SQL procedure successfully completed.
SQL> select count(distinct(object_name)) from t1 where id=:v;
COUNT(DISTINCT(OBJECT_NAME))
----------------------------
1
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID g3y76wm0v21f7, child number 2
-------------------------------------
select count(distinct(object_name)) from t1 where id=:v
Plan hash value: 3084587832
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | SORT AGGREGATE | | 1 | 66 | | |
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
| 2 | VIEW | VW_DAG_0 | 1 | 66 | 3 (34)| 00:00:01 |
| 3 | HASH GROUP BY | | 1 | 21 | 3 (34)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 21 | 2 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | IDX_T1 | 1 | | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - access("ID"=:V)
22 rows selected.
SQL> select sql_id,child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware from v$sql
2 where sql_text like 'select count(distinct(object_name)) from t1 where id%';
SQL_ID CHILD_NUMBER EXECUTIONS BUFFER_GETS I I
------------- ------------ ---------- ----------- - -
g3y76wm0v21f7 0 2 111 Y N
g3y76wm0v21f7 1 1 40 Y Y
g3y76wm0v21f7 2 1 3 Y Y
这时生成了一个child_number为2的新游标,执行计划也是正确的。
下面,让我们来总结一下:
为了解决变量窥测在数据分布倾斜的列上造成的执行计划不具有通用行和效率低下,而引入了自适应游标共享。
当某游标被设置为BIND_SENSITIVE(指该游标可能会因为绑定变量的不同取值而具有不同的效率表现,因此oracle会监视bind_sensitive的游标);
当oracle发现bind_sensitive的游标确实会因为绑定变量的不同取值而表现出不同的效率时(如逻辑读的跳跃),oracle会记录该游标。在下一次调用该游标时,oracle根据绑定变量值生成新的游标,将新游标标记为BIND_AWARE,将旧游标的共享标识置为NO,即旧游标将逐渐被丢弃并置换出内存。
当oracle再次执行该语句时,会根据绑定变量的取值来计算选择性(例如,通过直方图),如果计算出的选择性已经存在于以往子游标中,则调用子游标,否则创建新的子游标;
如果新的子游标和旧子游标的执行计划相同,oracle会将其合并,使用新的子游标,并逐步丢弃旧子游标。
注意:
bind_sensitive:绑定变量可能影响执行计划,需要oracle来监视
bind_aware:绑定变量会影响执行计划,oracle会根据不同的绑定变量选择或者生产新的执行计划。
除此以外,oracle11g还增加了几个视图:
新视图 V$SQL_CS_HISTOGRAM 显示了 SQL 语句执行的次数,为每个子游标划分了三个存储区(butcket),如下所示:
SQL> select * from v$sql_cs_histogram
2 where sql_id ='g3y76wm0v21f7';
ADDRESS HASH_VALUE SQL_ID CHILD_NUMBER BUCKET_ID COUNT
---------------- ---------- ------------- ------------ ---------- ----------
0000000069519540 3249604039 g3y76wm0v21f7 2 0 1
0000000069519540 3249604039 g3y76wm0v21f7 2 1 0
0000000069519540 3249604039 g3y76wm0v21f7 2 2 0
0000000069519540 3249604039 g3y76wm0v21f7 1 0 0
0000000069519540 3249604039 g3y76wm0v21f7 1 1 1
0000000069519540 3249604039 g3y76wm0v21f7 1 2 0
0000000069519540 3249604039 g3y76wm0v21f7 0 0 1
0000000069519540 3249604039 g3y76wm0v21f7 0 1 1
0000000069519540 3249604039 g3y76wm0v21f7 0 2 0
9 rows selected.
由于自适应游标共享特性根据绑定变量的值使用正确的计划,数据库必须在某处存储这些信息。它通过另一个新视图 V$SQL_CS_SELECTIVITY 显示这些信息,该视图显示传递给绑定变量的不同值的选择性。
SQL> select * from v$sql_cs_selectivity
2 where sql_id ='g3y76wm0v21f7';
ADDRESS HASH_VALUE SQL_ID CHILD_NUMBER PREDICATE RANGE_ID LOW HIGH
---------------- ---------- ------------- ------------ ---------- ---------- ---------- ----------
0000000069519540 3249604039 g3y76wm0v21f7 2 =V 0 0.000090 0.000110
0000000069519540 3249604039 g3y76wm0v21f7 1 =V 0 0.899910 1.099890
该视图显示了大量信息。PREDICATE 列显示了用户使用的各种谓词(WHERE 条件)。LOW 和 HIGH 值显示传递的值的范围。
最后,第三个新视图 V$SQL_CS_STATISTICS 显示了标记为绑定感知或绑定敏感的游标执行的操作。
SQL> select child_number,bind_set_hash_value,peeked,executions,rows_processed,buffer_gets,cpu_time
2 from v$sql_cs_statistics
3 where sql_id ='g3y76wm0v21f7';
CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED BUFFER_GETS CPU_TIME
------------ ------------------- - ---------- -------------- ----------- ----------
2 2064090006 Y 1 5 3 0
1 2342552567 Y 1 26222 40 0
0 2064090006 Y 1 5 52 0
该视图显示了数据库记录的有关执行的统计数据。EXECUTIONS 列显示了使用绑定变量的不同的值执行查询的次数。输出中的 PEEKED 列(显示为“P”)显示优化器是否通过观察绑定变量获得适当的方案。
这些视图显示了一些额外信息,您不需要通过这些信息了解此特性的工作方式。数据库自动激活和使用自适应游标。