DBA总是鼓励开发人员使用绑定变量,但是当绑定变量用于包含不均匀的数据列时,可能会导致非最优执行计划。这是因为优化器在硬解析时会窥视绑定变量的值,所以第一次的绑定变量值将会影响以后的每次执行。
Oracle 11g使用自适应游标共享(Adaptive Cursor Sharing)来解决这个问题,它允许比较不同绑定变量值的执行计划效率。如果发现当前执行计划非最优,允许使用其他绑定变量值,或者某个范围替换执行计划。该功能不需要额外的配置。
首先创建一个测试表。
DROP TABLE acs_test_tab;
CREATE TABLE acs_test_tab (
id NUMBER,
record_type NUMBER,
description VARCHAR2(50),
CONSTRAINT acs_test_tab_pk PRIMARY KEY (id)
);
CREATE INDEX acs_test_tab_record_type_i ON acs_test_tab(record_type);
DECLARE
TYPE t_acs_test_tab IS TABLE OF acs_test_tab%ROWTYPE;
l_tab t_acs_test_tab := t_acs_test_tab();
BEGIN
FOR i IN 1 .. 100000 LOOP
l_tab.extend;
IF MOD(i,2)=0 THEN
l_tab(l_tab.last).record_type := 2;
ELSE
l_tab(l_tab.last).record_type := i;
END IF;
l_tab(l_tab.last).id := i;
l_tab(l_tab.last).description := 'Description for ' || i;
END LOOP;
FORALL i IN l_tab.first .. l_tab.last
INSERT INTO acs_test_tab VALUES l_tab(i);
COMMIT;
END;
/
EXEC DBMS_STATS.gather_table_stats(USER, 'acs_test_tab', method_opt=>'for all indexed columns size skewonly', cascade=>TRUE);
通过直方图可以看到,RECORD_TYPE列是非均匀的。
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”的数据。
SET LINESIZE 200
SELECT MAX(id) FROM acs_test_tab WHERE record_type = 1;
SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);
MAX(ID)
----------
1
1 row selected.
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------
SQL_ID cgt92vnmcytb0, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = 1
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
该查询使用了期望的索引。现在重复执行该查询,但使用了绑定变量。
VARIABLE l_record_type NUMBER;
EXEC :l_record_type := 1;
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type;
SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);
MAX(ID)
----------
1
1 row selected.
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------
SQL_ID 9bmm6cmwa8saf, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
以上相当于执行了相同的查询,得到了相同的结果和执行计划。优化器通过窥视绑定变量的值为该查询挑选一个它认为的最优查询计划。问题在于,这对于其他的绑定值可能是完全错误的。
VARIABLE l_record_type NUMBER;
EXEC :l_record_type := 2;
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type;
SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);
MAX(ID)
----------
100000
1 row selected.
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------
SQL_ID 9bmm6cmwa8saf, child number 0
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type
Plan hash value: 3987223107
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
| 2 | TABLE ACCESS BY INDEX ROWID| ACS_TEST_TAB | 1 | 9 | 2 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | ACS_TEST_TAB_RECORD_TYPE_I | 1 | | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
查看V$SQL视图,可以看到IS_BIND_SENSITIVE列为‘Y’,因此Oracle认为该查询需要不同的执行计划,但是当前的IS_BIND_AWARE列为‘N’,说明Oracle还没有这样做。
SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware
FROM v$sql
WHERE sql_text = 'SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type';
SQL_ID CHILD_NUMBER I I
------------- ------------ - -
9bmm6cmwa8saf 0 Y N
如果再次使用第二个绑定变量值运行该语句,可以看到Oracle已经决定使用一个替换的、更有效的执行计划。
VARIABLE l_record_type NUMBER;
EXEC :l_record_type := 2;
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type;
SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);
MAX(ID)
----------
100000
1 row selected.
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------
SQL_ID 9bmm6cmwa8saf, child number 1
-------------------------------------
SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type
Plan hash value: 509473618
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 138 (100)| |
| 1 | SORT AGGREGATE | | 1 | 9 | | |
|* 2 | TABLE ACCESS FULL| ACS_TEST_TAB | 48031 | 422K| 138 (2)| 00:00:02 |
-----------------------------------------------------------------------------------
这种方式的改变同样反映到了V$SQL视图中,现在的IS_BIND_AWARE列为‘Y’。
SELECT sql_id, child_number, is_bind_sensitive, is_bind_aware
FROM v$sql
WHERE sql_text = 'SELECT MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type';
SQL_ID CHILD_NUMBER I I
------------- ------------ - -
9bmm6cmwa8saf 0 Y N
9bmm6cmwa8saf 1 Y Y
游标共享直方图信息、统计信息以及选择度信息分别通过V$SQL_CS_HISTOGRAM、V$SQL_CS_STATISTICS以及V$SQL_CS_SELECTIVITY显示。
SQL> SELECT * FROM v$sql_cs_histogram WHERE sql_id = '9bmm6cmwa8saf';
ADDRESS HASH_VALUE SQL_ID CHILD_NUMBER BUCKET_ID COUNT
-------- ---------- ------------- ------------ ---------- ----------
319A4A1C 4171522382 9bmm6cmwa8saf 1 0 0
319A4A1C 4171522382 9bmm6cmwa8saf 1 1 1
319A4A1C 4171522382 9bmm6cmwa8saf 1 2 0
319A4A1C 4171522382 9bmm6cmwa8saf 0 0 1
319A4A1C 4171522382 9bmm6cmwa8saf 0 1 1
319A4A1C 4171522382 9bmm6cmwa8saf 0 2 0
6 rows selected.
SQL> SELECT * FROM v$sql_cs_statistics WHERE sql_id = '9bmm6cmwa8saf';
ADDRESS HASH_VALUE SQL_ID CHILD_NUMBER BIND_SET_HASH_VALUE P EXECUTIONS ROWS_PROCESSED BUFFER_GETS CPU_TIME
-------- ---------- ------------- ------------ ------------------- - ---------- -------------- ----------- ----------
319A4A1C 4171522382 9bmm6cmwa8saf 1 2064090006 Y 1 50001 499 0
319A4A1C 4171522382 9bmm6cmwa8saf 0 2342552567 Y 1 3 3 0
2 rows selected.
SQL> SELECT * FROM v$sql_cs_selectivity WHERE sql_id = '9bmm6cmwa8saf';
ADDRESS HASH_VALUE SQL_ID CHILD_NUMBER PREDICATE RANGE_ID LOW HIGH
-------- ---------- ------------- ------------ ---------------------------------------- ---------- ---------- ----------
319A4A1C 4171522382 9bmm6cmwa8saf 1 =L_RECORD_T 0 0.432283 0.528346
1 row selected.
从11.1.0.7之后,可以使用BIND_AWARE提示跳过检测绑定敏感查询的监控。下例中的提示告诉优化器,我们认为查询是绑定敏感的,因此从第一次执行开始就应该是一绑定感知的游标共享。
SELECT /*+ BIND_AWARE */ MAX(id) FROM acs_test_tab WHERE record_type = :l_record_type;
该提示只在查询的WHERE谓词引用包含直方图的列时有效。
NO_BIND_AWARE提示告诉优化器忽略绑定敏感的查询,可以有效地隐藏自适应游标共享功能。
绑定感知的游标共享会增加一个很小的消耗,这就是为什么Oracle使用“自适应”方法确定查询是否能够从绑定感知的游标共享中获益。为不会获益的查询添加提示是一种浪费。
原文地址:http://www.oracle-base.com/articles/11g/adaptive-cursor-sharing-11gr1.php