Hint概述
基於代價的優化器是很聰明的,在絕大多數情況下它會選擇正確的優化器,減輕了DBA的負擔。但有時它也聰明反被聰明誤,選擇了很差的執行計劃,使某個語句的執行變得奇慢無比。
此時就需要DBA進行人為的干預,告訴優化器使用我們指定的存取路徑或連接類型生成執行計劃,從 而使語句高效的運行。例如,如果我們認為對於一個特定的語句,執行全表掃描要比執行索引掃描更有效,則我們就可以指示優化器使用全表掃描。在Oracle 中,是通過為語句添加 Hints(提示)來實現干預優化器優化的目的。
不建議在代碼中使用hint,在代碼使用hint使得CBO無法根據實際的數據狀態選擇正確的執行計劃。畢竟 數據是不斷變化的, 10g以後的CBO也越來越完善,大多數情況下我們該讓Oracle自行決定采用什麼執行計劃。
Oracle Hints是一種機制,用來告訴優化器按照我們的告訴它的方式生成執行計劃。我們可以用Oracle Hints來實現:
1) 使用的優化器的類型
2) 基於代價的優化器的優化目標,是all_rows還是first_rows。
3) 表的訪問路徑,是全表掃描,還是索引掃描,還是直接利用rowid。
4) 表之間的連接類型
5) 表之間的連接順序
6) 語句的並行程度
除了”RULE”提示外,一旦使用的別的提示,語句就會自動的改為使用CBO優化器,此時如果你的數據字典中沒有統計數據,就會使用缺省的統計數據。所以建議大家如果使用CBO或Hints提示,則最好對表和索引進行定期的分析。
如何使用Hints:
Hints只應用在它們所在sql語句塊(statement block,由select、update、delete關鍵字標識)上,對其它SQL語句或語句的其它部分沒有影響。如:對於使用union操作的2個sql語句,如果只在一個sql語句上有Hints,則該Hints不會影響另一個sql語句。
我們可以使用注釋(comment)來為一個語句添加Hints,一個語句塊只能有一個注釋,而且注釋只能放在SELECT, UPDATE, or DELETE關鍵字的後面
使用Oracle Hints的語法:
{DELETE|INSERT|SELECT|UPDATE} /*+ hint [text] [hint[text]]... */
or
{DELETE|INSERT|SELECT|UPDATE} --+ hint [text] [hint[text]]...
注解:
1) DELETE、INSERT、SELECT和UPDATE是標識一個語句塊開始的關鍵字,包含提示的注釋只能出現在這些關鍵字的後面,否則提示無效。
2) “+”號表示該注釋是一個Hints,該加號必須立即跟在”/*”的後面,中間不能有空格。
3) hint是下面介紹的具體提示之一,如果包含多個提示,則每個提示之間需要用一個或多個空格隔開。
4) text 是其它說明hint的注釋性文本
5)使用表別名。如果在查詢中指定了表別名,那麼提示必須也使用表別名。例如:select /*+ index(e,dept_idx) */ * from emp e;
6)不要在提示中使用模式名稱:如果在提示中指定了模式的所有者,那麼提示將被忽略。例如:
select /*+ index(scott.emp,dept_idx) */ * from emp
注意:如果你沒有正確的指定Hints,Oracle將忽略該Hints,並且不會給出任何錯誤。
hint被忽略
如果CBO認為使用hint會導致錯誤的結果時,hint將被忽略,詳見下例
SQL> select /*+ index(t t_ind) */ count(*) from t;
Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522
-------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 57 (2)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | | |
| 2 | TABLE ACCESS FULL| T | 50366 | 57 (2)| 00:00:01 |
-------------------------------------------------------------------
因為我們是對記錄求總數,且我們並沒有在建立索引時指定不能為空,索引如果CBO選擇在索引上進行count時,但索引字段上的值為空時,結果將不准確,故CBO沒有選擇索引。
SQL> select /*+ index(t t_ind) */ count(id) from t;
Execution Plan
----------------------------------------------------------
Plan hash value: 646498162
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 285 (1)| 00:00:04 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
| 2 | INDEX FULL SCAN| T_IND | 50366 | 245K| 285 (1)| 00:00:04 |
--------------------------------------------------------------------------
因為我們只對id進行count,這個動作相當於count索引上的所有id值,這個操作和對表上的id字段進行count是一樣的(組函數會忽略null值)
Hint的具體用法
和優化器相關的hint
1、/*+ ALL_ROWS */
表明對語句塊選擇基於開銷的優化方法,並獲得最佳吞吐量,使資源消耗最小化.
SELECT /*+ ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
2、/*+ FIRST_ROWS(n) */
表明對語句塊選擇基於開銷的優化方法,並獲得最佳響應時間,使資源消耗最小化.
SELECT /*+FIRST_ROWS(20) */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
3、/*+ RULE*/
表明對語句塊選擇基於規則的優化方法.
SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='SCOTT';
和訪問路徑相關的hint
1、/*+ FULL(TABLE)*/
表明對表選擇全局掃描的方法.
SELECT /*+FULL(A)*/ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='SCOTT';
2、/*+ INDEX(TABLE INDEX_NAME) */
表明對表選擇索引的掃描方法.
SELECT /*+INDEX(BSEMPMS SEX_INDEX) */ * FROM BSEMPMS WHERE SEX='M';
5、/*+ INDEX_ASC(TABLE INDEX_NAME)*/
表明對表選擇索引升序的掃描方法.
SELECT /*+INDEX_ASC(BSEMPMS PK_BSEMPMS) */ * FROM BSEMPMS WHERE DPT_NO='SCOTT';
6、/*+ INDEX_COMBINE*/
為指定表選擇位圖訪問路經,如果INDEX_COMBINE中沒有提供作為參數的索引,將選擇出位圖索引的布爾組合方式.
SELECT /*+INDEX_COMBINE(BSEMPMS SAL_BMI HIREDATE_BMI) */ * FROM BSEMPMS
WHERE SAL<5000000 AND HIREDATE
7、/*+ INDEX_JOIN(TABLE INDEX_NAME1 INDEX_NAME2) */
當謂詞中引用的列都有索引的時候,可以通過指定采用索引關聯的方式,來訪問數據
select /*+ index_join(t t_ind t_bm) */ id from t where id=100 and object_name='EMPLOYEES'
8、/*+ INDEX_DESC(TABLE INDEX_NAME)*/
表明對表選擇索引降序的掃描方法.
SELECT /*+INDEX_DESC(BSEMPMS PK_BSEMPMS) */ * FROM BSEMPMS WHERE DPT_NO='SCOTT';
9、/*+ INDEX_FFS(TABLE INDEX_NAME) */
對指定的表執行快速全索引掃描,而不是全表掃描的辦法.
SELECT /* + INDEX_FFS(BSEMPMS IN_EMPNAM)*/ * FROM BSEMPMS WHERE DPT_NO='TEC305';
10、/*+ INDEX_SS(T T_IND) */
從9i開始,oracle引入了這種索引訪問方式。當在一個聯合索引中,某些謂詞條件並不在聯合索引的第一列時,可以通過Index Skip Scan來訪問索引獲得數據。當聯合索引第一列的唯一值個數很少時,使用這種方式比全表掃描效率高。
SQL> create table t as select 1 id,object_name from dba_objects;
Table created.
SQL> insert into t select 2,object_name from dba_objects;
50366 rows created.
SQL> insert into t select 3,object_name from dba_objects;
50366 rows created.
SQL> insert into t select 4,object_name from dba_objects;
50366 rows created.
SQL> commit;
Commit complete.
SQL> create index t_ind on t(id,object_name);
Index created.
SQL> exec dbms_stats.gather_table_stats('HR','T',cascade=>true);
PL/SQL procedure successfully completed.
執行全表掃描
SQL> select /*+ full(t) */ * from t where object_name='EMPLOYEES';
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 135 | 215 (3)| 00:00:03 |
|* 1 | TABLE ACCESS FULL| T | 5 | 135 | 215 (3)| 00:00:03 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OBJECT_NAME"='EMPLOYEES')
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
942 consistent gets
0 physical reads
0 redo size
538 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
不采用hint
SQL> select * from t where object_name='EMPLOYEES';
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2869677071
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 135 | 5 (0)| 00:00:01 |
|* 1 | INDEX SKIP SCAN | T_IND | 5 | 135 | 5 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OBJECT_NAME"='EMPLOYEES')
filter("OBJECT_NAME"='EMPLOYEES')
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
17 consistent gets
1 physical reads
0 redo size
538 bytes sent via SQL*Net to client
385 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
當全表掃描掃描了942個塊,聯合索引只掃描了17個數據塊。可以看到聯合索引的第一個字段的值重復率很高時,即使謂詞中沒有聯合索引的第一個字段,依然會使用index_ss方式,效率遠遠高於全表掃描效率。但當 第一個字段的值重復率很低時,使用 index_ss的效率要低於 全表掃描,讀者可以自行實驗
和表的關聯相關的hint
/*+ leading(table_1,table_2) */
在多表關聯查詢中,指定哪個表作為驅動表,即告訴優化器首先要訪問哪個表上的數據。
select /*+ leading(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ order */
讓Oracle根據from後面表的順序來選擇驅動表,oracle建議使用leading,他更為靈活
select /*+ order */ t.* from t,t1 where t.id=t1.id;
/*+ use_nl(table_1,table_2) */
在多表關聯查詢中,指定使用nest loops方式進行多表關聯。
select /*+ use_nl(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ use_hash(table_1,table_2) */
在多表關聯查詢中,指定使用hash join方式進行多表關聯。
select /*+ use_hash(t,t1) */ t.* from t,t1 where t.id=t1.id;
在多表關聯查詢中,指定使用hash join方式進行多表關聯,並指定表t為驅動表。
select /*+ use_hash(t,t1) leading(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ use_merge(table_1,table_2) */
在多表關聯查詢中,指定使用merge join方式進行多表關聯。
select /*+ use_merge(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ no_use_nl(table_1,table_2) */
在多表關聯查詢中,指定不使用nest loops方式進行多表關聯。
select /*+ no_use_nl(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ no_use_hash(table_1,table_2) */
在多表關聯查詢中,指定不使用hash join方式進行多表關聯。
select /*+ no_use_hash(t,t1) */ t.* from t,t1 where t.id=t1.id;
/*+ no_use_merge(table_1,table_2) */
在多表關聯查詢中,指定不使用merge join方式進行多表關聯。
select /*+ no_use_merge(t,t1) */ t.* from t,t1 where t.id=t1.id;
其他常用的hint
/*+ parallel(table_name n) */
在sql中指定執行的並行度,這個值將會覆蓋自身的並行度
select /*+ parallel(t 4) */ count(*) from t;
/*+ no_parallel(table_name) */
在sql中指定執行的不使用並行
select /*+ no_parallel(t) */ count(*) from t;
/*+ append */以直接加載的方式將數據加載入庫
insert into t /*+ append */ select * from t;
/*+ dynamic_sampling(table_name n) */
設置sql執行時動態采用的級別,這個級別為0~10
select /*+ dynamic_sampling(t 4) */ * from t where id > 1234
/*+ cache(table_name) */
進行全表掃描時將table置於LRU列表的最活躍端,類似於table的cache屬性
select /*+ full(employees) cache(employees) */ last_name from employees
附錄hint表格
Hints for Optimization Approaches and Goals
ALL_ROWS The ALL_ROWS hint explicitly chooses the cost-based approach to optimize a statement block with a goal of best throughput (that is, minimum total resource consumption). FIRST_ROWS The FIRST_ROWS hint explicitly chooses the cost-based approach to optimize a statement block with a goal of best response time (minimum resource usage to return first row). In newer Oracle version you should give a parameter with this hint: FIRST_ROWS(n) means that the optimizer will determine an executionplan to give a fast response for returning the first n rows. CHOOSE The CHOOSE hint causes the optimizer to choose between the rule-based approach and the cost-based approach for a SQL statement based on the presence of statistics for the tables accessed by the statement RULE The RULE hint explicitly chooses rule-based optimization for a statement block. This hint also causes the optimizer to ignore any other hints specified for the statement block. The RULE hint does not work any more in Oracle 10g.Hints for Access Paths
FULL The FULL hint explicitly chooses a full table scan for the specified table. The syntax of the FULL hint is FULL(table) where table specifies the alias of the table (or table name if alias does not exist) on which the full table scan is to be performed. ROWID The ROWID hint explicitly chooses a table scan by ROWID for the specified table. The syntax of the ROWID hint is ROWID(table) where table specifies the name or alias of the table on which the table access by ROWID is to be performed. (This hint depricated in Oracle 10g) CLUSTER The CLUSTER hint explicitly chooses a cluster scan to access the specified table. The syntax of the CLUSTER hint is CLUSTER(table) where table specifies the name or alias of the table to be accessed by a cluster scan. HASH The HASH hint explicitly chooses a hash scan to access the specified table. The syntax of the HASH hint is HASH(table) where table specifies the name or alias of the table to be accessed by a hash scan. HASH_AJ The HASH_AJ hint transforms a NOT IN subquery into a hash anti-join to access the specified table. The syntax of the HASH_AJ hint is HASH_AJ(table) where table specifies the name or alias of the table to be accessed.(depricated in Oracle 10g) INDEX The INDEX hint explicitly chooses an index scan for the specified table. The syntax of the INDEX hint is INDEX(table index) where:table specifies the name or alias of the table associated with the index to be scanned and index specifies an index on which an index scan is to be performed. This hint may optionally specify one or more indexes: NO_INDEX The NO_INDEX hint explicitly disallows a set of indexes for the specified table. The syntax of the NO_INDEX hint is NO_INDEX(table index) INDEX_ASC The INDEX_ASC hint explicitly chooses an index scan for the specified table. If the statement uses an index range scan, Oracle scans the index entries in ascending order of their indexed values. INDEX_COMBINE If no indexes are given as arguments for the INDEX_COMBINE hint, the optimizer will use on the table whatever boolean combination of bitmap indexes has the best cost estimate. If certain indexes are given as arguments, the optimizer will try to use some boolean combination of those particular bitmap indexes. The syntax of INDEX_COMBINE is INDEX_COMBINE(table index). INDEX_JOIN Explicitly instructs the optimizer to use an index join as an access path. For the hint to have a positive effect, a sufficiently small number of indexes must exist that contain all the columns required to resolve the query. INDEX_DESC The INDEX_DESC hint explicitly chooses an index scan for the specified table. If the statement uses an index range scan, Oracle scans the index entries in descending order of their indexed values. INDEX_FFS This hint causes a fast full index scan to be performed rather than a full table. NO_INDEX_FFS Do not use fast full index scan (from Oracle 10g) INDEX_SS Exclude range scan from query plan (from Oracle 10g) INDEX_SS_ASC Exclude range scan from query plan (from Oracle 10g) INDEX_SS_DESC Exclude range scan from query plan (from Oracle 10g) NO_INDEX_SS The NO_INDEX_SS hint causes the optimizer to exclude a skip scan of the specified indexes on the specified table. (from Oracle 10g)Hints for Query Transformations
Hints for Join Orders
Hints for Join Operations
Hints with unknown status
MERGE_AJ The MERGE_AJ hint transforms a NOT IN subquery into a merge anti-join to access the specified table. The syntax of the MERGE_AJ hint is MERGE_AJ(table) where table specifies the name or alias of the table to be accessed.(depricated in Oracle 10g) AND_EQUAL The AND_EQUAL hint explicitly chooses an execution plan that uses an access path that merges the scans on several single-column indexes. The syntax of the AND_EQUAL hint is AND_EQUAL(table index index) where table specifies the name or alias of the table associated with the indexes to be merged. and index specifies an index on which an index scan is to be performed. You must specify at least two indexes. You cannot specify more than five. (depricated in Oracle 10g) STAR The STAR hint forces the large table to be joined last using a nested loops join on the index. The optimizer will consider different permutations of the small tables. (depricated in Oracle 10g) BITMAP Usage: BITMAP(table_name index_name) Uses a bitmap index to access the table. (depricated ?) HASH_SJ Use a Hash Anti-Join to evaluate a NOT IN sub-query. Use this hint in the sub-query, not in the main query. Use this when your high volume NOT IN sub-query is using a FILTER or NESTED LOOPS join. Try MERGE_AJ if HASH_AJ refuses to work.(depricated in Oracle 10g) NL_SJ Use a Nested Loop in a sub-query. (depricated in Oracle 10g) NL_AJ Use an anti-join in a sub-query. (depricated in Oracle 10g) ORDERED_PREDICATES (depricated in Oracle 10g) EXPAND_GSET_TO_UNION (depricated in Oracle 10g)