Oracle 統計量NO_INVALIDATE引數配置(上)

531968912發表於2016-07-22

 

Oracle統計量對於CBO執行是至關重要的。RBO是建立在資料結構的基礎上的,DDL結構、約束會將SQL語句分為不同的成本結構等級。而CBO是在資料結構的基礎上,加入資料表細粒度資訊,將成本結構細化為成本cost值。

相對於資料表的DDL結構,統計量反映了當下資料表資料分佈情況,可變性更強。我們經常遇到這樣的場景,資料匯入操作之後,原有一個執行良好的作業突然效率低下。當我們手工收集一下統計量之後,作業效率提升。這種現象也就是反映了統計量和執行計劃的關係。

SGA中的shared pool是進行執行計劃快取的位置。Shared CursorSQL語句共享的主要物件。一句SQL語句,如果在Shared Pool中有快取的執行計劃。這個時候,有新的統計量收集動作,有新統計量收集到資料字典中,進而以為了新的執行計劃需求。那麼,Oracle是如何進行抉擇呢?

答案就是dbms_statsno_invalidate引數。透過不同的引數配置,可以實現對Oracle失效共享遊標行為的控制。

 

1no_invalidate引數

 

No_invalidate引數從字面上比較糾結。Noin都是否定含義,“負負得正”。引數含義就是validate,也就是是否有效。它決定了新統計量生成之後,如何處理此時已經生成的執行計劃,也就是在Shared Pool中的執行計劃。

統計量決定SQL執行計劃,是CBO的一個特徵。但是這個過程是針對新生成的執行計劃,也就是新的Parse過程。對於已經生成的執行計劃,Oracle是透過no_invalidate引數來處理shared cursor的失效過程。

一個物件(資料表、索引)新統計量生成之後,最簡單的方法是一次性將在Shared Pool中有依賴關係的shared cursor失效。下一次再進行SQL執行的時候,必然會用新的執行計劃Parse解析過程。另一個極端是無視新統計量的差異,維持現有的Shared Cursor,不會去讓其失效。

從效能角度看,兩個極端都是有其問題的。如果是一次性將其全部失效,會引起後續作業過程的“解析峰值”。因為,如果系統負載比較高,突然間快取的執行計劃全部被失效,Oracle作業必然要進行一些額外的成本進行執行計劃重新生成。這個會體現在系統執行有一個峰值。

如果不將共享遊標失效,那麼新的統計量不會很快體現在更好執行計劃生成的過程。效能提升無從談起。

所以,是否將遊標失效,是一個“左右為難”的問題。

Oracle中,no_invalidate引數包括三個取值。

 

SQL> select * from v$version;

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE 11.2.0.1.0 Production

TNS for Linux: Version 11.2.0.1.0 - Production

NLSRTL Version 11.2.0.1.0 - Production

 

--   no_invalidate - Do not invalide the dependent cursors if set to TRUE.

--     The procedure invalidates the dependent cursors immediately

--     if set to FALSE.

--     Use DBMS_STATS.AUTO_INVALIDATE to have oracle decide when to

--     invalidate dependend cursors. This is the default. The default

--     can be changed using set_param procedure.

--     When the 'cascade' argument is specified, not pertinent with certain

--     types of indexes described in the gather_index_stats section.

 

Oracle支援truefalsedbms_stats.auto_invalidate取值。如果取值為true,表示不進行遊標失效動作,原有的shared cursor保持原有狀態。如果取值為false,表示將統計量物件相關的所有cursor全部失效。如果設定為auto_invalidate,根據官方文件,Oracle自己決定shared cursor失效動作。

10G開始,Oracle就將auto_invalidate作為預設的統計量收集行為。

 

 

SQL> select dbms_stats.get_param(pname => 'no_invalidate') from dual;

DBMS_STATS.GET_PARAM(PNAME=>'N

--------------------------------------------------------------------------------

DBMS_STATS.AUTO_INVALIDATE

 

下面,筆者將透過一系列的實驗,來證明no_invalidate引數取值的效果。

 

2no_invalidate取值為YES

 

取值為YES,表示不經心共享遊標失效動作,即使這個過程中,共享的遊標已經不是最優的執行計劃。

我們建立實驗資料表。

 

 

SQL> create table t as select * from dba_objects;

Table created

 

SQL> create index idx_t_id on t(object_id);

Index created

 

--第一次統計量收集

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

目標SQL語句,注意:出於篇幅原因,筆者將結果遮蔽。

 

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

 

統計資訊

----------------------------------

        164  recursive calls

          0  db block gets

         23  consistent gets

          0  physical reads

   (有省略……)

          1  rows processed

 

此時shared pool中情況如下,出現第一個執行計劃快取物件。

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          1             1

 

SQL> select * from table(dbms_xplan.display_cursor(sql_id=>'cnb0ktgvms6vq'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  cnb0ktgvms6vq, child number 0

-------------------------------------

select /*+demo*/object_id, owner from t where object_id=1000

Plan hash value: 514881935

--------------------------------------------------------------------------------

| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Ti

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |          |       |       |     2 (100)|

|   1 |  TABLE ACCESS BY INDEX ROWID| T        |     1 |    11 |     2   (0)| 00

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID |     1 |       |     1   (0)| 00

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("OBJECT_ID"=1000)

 

19 rows selected

 

此時,最優的執行計劃是索引路徑。在shared pool中有一個父遊標和子游標(version count=1),執行次數為1

第二次執行之後,Shared Pool中有共享現象。相同的共享遊標執行兩次。

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          2             1

 

之後,我們更新資料,修改資料分佈結構。

 

SQL> update t set object_id=1000;

72729 rows updated

 

SQL> commit;

Commit complete

 

此時,如果執行SQL語句,我們發現依然是使用原有的索引路徑。此時全部T表中object_id都是1000,走索引不是好的選擇。

 

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

已選擇72729行。

 

統計資訊

------------------------------------------

          0  recursive calls

          0  db block gets

      11157  consistent gets

          0  physical reads

     

      72729  rows processed

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          3             1

 

此時的路徑依然是Index Range Scan。這個明顯是由於統計量的過時,外加遊標共享,引起的錯誤路徑。下面我們重新收集一下統計量,採用no_invaliatetrue的情況。

 

 

SQL> exec dbms_stats.flush_database_monitoring_info;

PL/SQL procedure successfully completed

 

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true,no_invalidate => true,method_opt => 'for columns size 10 object_id');

PL/SQL procedure successfully completed

 

新統計量生成,我們使用explain plan檢視一下,此時SQL應該採用的執行計劃是什麼?

 

 

SQL> explain plan for select /*+demo*/object_id, owner from t where object_id=1000;

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------

Plan hash value: 1601196873

---------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      | 72722 |   639K|   266   (1)| 00:00:04 |

|*  1 |  TABLE ACCESS FULL| T    | 72722 |   639K|   266   (1)| 00:00:04 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("OBJECT_ID"=1000)

13 rows selected

 

此時,FTS全表掃描是更好的選擇。但是我們檢視一下實際執行時候,路徑情況。

 

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

已選擇72729行。

 

統計資訊

----------------------------------------------------------

          0  recursive calls

          0  db block gets

      10907  consistent gets

          0  physical reads

          0  redo size

      72729  rows processed

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          4             1

 

此時,Oracle依然選擇了原來的Index路徑,原有的shared cursor沒有失效!!如果我們此時將shared pool清空,新的FTS執行計劃也就生成。

 

 

SQL> alter system flush shared_pool;

System altered

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

已選擇72729行。

 

統計資訊

----------------------------------------------------------

        243  recursive calls

          0  db block gets

       5855  consistent gets

          0  physical reads

        

      72729  rows processed

 

--新的shared cursor形成

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          1             1

 

--FTS執行計劃

SQL> select * from table(dbms_xplan.display_cursor(sql_id => 'cnb0ktgvms6vq'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  cnb0ktgvms6vq, child number 0

-------------------------------------

select /*+demo*/object_id, owner from t where object_id=1000

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |       |       |   266 (100)|          |

|*  1 |  TABLE ACCESS FULL| T    | 72722 |   639K|   266   (1)| 00:00:04 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("OBJECT_ID"=1000)

18 rows selected

 

結論:當我們使用no_invalidatetrue的時候,原有的shared cursor不會被失效,可以支援共享。只有當被age out或者flush outshared pool之後,新執行計劃才能生成。

 

3no_invalidate=false

 

下面我們看看取值為false的情況,實驗場景相同。為避免影響,我們重新構建資料表。

 

 

SQL> drop table t purge;

Table dropped

 

SQL> alter system flush shared_pool;

System altered

 

SQL> create table t as select * from dba_objects;

Table created

 

SQL> create index idx_t_id on t(object_id);

Index created

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

第一次執行SQL語句,形成Index路徑執行計劃。

 

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

統計資訊

----------------------------------------------------------

        164  recursive calls

          0  db block gets

         23  consistent gets

          1  rows processed

 

 

SQL> select sql_id, executions, version_count from v$sqlarea where sql_text like 'select /*+demo*/%';

SQL_ID        EXECUTIONS VERSION_COUNT

------------- ---------- -------------

cnb0ktgvms6vq          1             1

 

SQL> select * from table(dbms_xplan.display_cursor(sql_id=>'cnb0ktgvms6vq'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  cnb0ktgvms6vq, child number 0

-------------------------------------

select /*+demo*/object_id, owner from t where object_id=1000

Plan hash value: 514881935

--------------------------------------------------------------------------------

| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Ti

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |          |       |       |     2 (100)|

|   1 |  TABLE ACCESS BY INDEX ROWID| T        |     1 |    11 |     2   (0)| 00

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID |     1 |       |     1   (0)| 00

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("OBJECT_ID"=1000)

 

19 rows selected

 

第二次執行相同SQL,我們可以看到生成的shared cursor進行共享。

 

 

SQL> select sql_id, executions, version_count, first_load_time from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT FIRST_LOAD_TIME

------------- ---------- ------------- ----------------------------------------------------------------------------

cnb0ktgvms6vq          2             1 2014-01-06/00:04:29

 

修改資料object_id取值,改變資料分佈。

 

 

SQL>  update t set object_id=1000;

72729 rows updated

 

SQL> commit;

Commit complete

 

第三次執行。

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

已選擇72729行。

統計資訊

--------------------------------------

          0  recursive calls

          0  db block gets

      11157  consistent gets

      72729  rows processed

 

此時shared cursor狀態如下:

 

 

SQL> select sql_id, executions, version_count, first_load_time from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT FIRST_LOAD_TIME

------------- ---------- ------------- ----------------------------------------------------------------------------

cnb0ktgvms6vq          3             1 2014-01-06/00:04:29

 

執行計劃是進行Index Range Scan動作。

 

 

SQL> select * from table(dbms_xplan.display_cursor(sql_id=>'cnb0ktgvms6vq'));

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  cnb0ktgvms6vq, child number 0

-------------------------------------

select /*+demo*/object_id, owner from t where object_id=1000

Plan hash value: 514881935

--------------------------------------------------------------------------------

| Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Ti

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |          |       |       |     2 (100)|

|   1 |  TABLE ACCESS BY INDEX ROWID| T        |     1 |    11 |     2   (0)| 00

|*  2 |   INDEX RANGE SCAN          | IDX_T_ID |     1 |       |     1   (0)| 00

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("OBJECT_ID"=1000)

19 rows selected

 

收集統計量,使用no_invalidatefalse取值。

 

 

SQL> exec dbms_stats.flush_database_monitoring_info;

PL/SQL procedure successfully completed

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true,no_invalidate => false,method_opt => 'for columns size 10 object_id');

PL/SQL procedure successfully completed

 

第四次執行過程。

 

 

SQL> select /*+demo*/object_id, owner from t where object_id=1000;

已選擇72729行。

 

統計資訊

----------------------------

        141  recursive calls

          0  db block gets

       5835  consistent gets

        

      72729  rows processed

 

 

SQL> select sql_id, executions, version_count, first_load_time from v$sqlarea where sql_text like 'select /*+demo*/%';

 

SQL_ID        EXECUTIONS VERSION_COUNT FIRST_LOAD_TIME

------------- ---------- ------------- -------------------

cnb0ktgvms6vq          1             1 2014-01-06/00:04:29

 

注意:在相同的sql_id情況下,version_countexecutions都為1Executions是不可能減少的。所以,這個父遊標是新生成的!

此時,執行計劃如下:

 

SQL> select * from table(dbms_xplan.display_cursor(sql_id=>'cnb0ktgvms6vq'));

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

SQL_ID  cnb0ktgvms6vq, child number 0

-------------------------------------

select /*+demo*/object_id, owner from t where object_id=1000

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |       |       |   266 (100)|          |

|*  1 |  TABLE ACCESS FULL| T    | 72722 |   639K|   266   (1)| 00:00:04 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("OBJECT_ID"=1000)

 

18 rows selected

 

這也就是說明了,新的執行計劃已經生成了!也就是原有的遊標被廢棄。

結論:當我們收集統計量使用no_invalidatefalse的時候,原有的共享遊標被失效,下一次在執行SQL的時候,Oracle會重新為其生成執行計劃,也就是一次hard parse過程。

Truefalse取值是比較簡單的。我們接下來討論dbms_stats.auto_invalidate取值情況。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25462274/viewspace-2122401/,如需轉載,請註明出處,否則將追究法律責任。

相關文章