Oracle Database 11g Release 1中的自适应游标共享

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.

BIND_AWARE提示与NO_BIND_AWARE提示

从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


你可能感兴趣的:(Oracle,oracle,11g,自适应游标共享)