1. 程式人生 > >關於oracle索引的效能優化

關於oracle索引的效能優化

使用索引是提高oracle查詢的一種重要方式,索引的使用同時也是一柄雙刃劍,使用不當也會導致效能問題。

索引的建立方式和查詢語句的執行方式都會影響實際執行的效率。同時對索引的維護也會導致索引的效能問題。有些時候使用複合索引時,oracle查詢不會自動使用索引,需要使用強制索引(當使用表別名時,強制索引的表名也要使用別名),如下:
SELECT /*+ INDEX(KSFK_APPDATA_INFO KSFK_DATAINFO_IDX) */  record_id,status,source_seq,modify_date,modify_time FROM KSFK_APPDATA_INFO where APPL_ID ='XXXXXXX';

以下內容主要來自網路
一、Oracle表與索引的分析及索引重建;
二、索引執行的除錯;
三、oracle索引執行的型別;
   
一、Oracle表與索引的分析及索引重建


1.分析表與索引(analyze 不會重建索引)


 
analyze table tablename compute statistics 
等同於 analyze table tablename compute statistics for table for all indexes for all columns


for table 的統計資訊存在於檢視:user_tables 、all_tables、dba_tables


for all indexes 的統計資訊存在於檢視: user_indexes 、all_indexes、dba_indexes


for all columns 的統計資訊存在於檢視:user_tab_columns、all_tab_columns、dba_tab_columns


注:分析表與索引見 AnalyzeAllTable儲存過程
2、一般來講可以採用以下三種方式來手工分析索引。
analyze index idx_t validate structure:
analyze index idx_t compute statistics:
analyze index idx_t estimate statistics sample 10 percent


1)analyze index idx_t validate structure:
這段分析語句是用來分析索引的block中是否有壞塊兒,那麼根據分析我們可以得到索引的結構資料,這些資料會保留到
index_stats中,來判斷這個索引是否需要rebuild. 需要注意的是這樣的分析是不會收集索引的統計資訊的。


2)validate structure有二種模式: online, offline, 一般來講預設的方式是offline。
當以offline的模式analyze索引時,會對table加一個表級共享鎖,對目前table的一些實時DMl操作會產生一定的影響。
而以online模式分析時候,則不會加任何lock,但在index_stats中是看不到任何資訊的。


3)analyze index idx_t compute statistics:
用來統計索引的統計資訊(全分析),主要為CBO服務。


4)analyze index idx_t estimate statistics sample 10 percent
主要是用來指定比例進行抽樣分析,也是為CBO服務. 例中是抽樣10%


3.重建索引
alter index index_name rebuild tablespace tablespace_name 
alter index index_name rebuild tablespace tablespace_name 加入表空間名,會將指定的索引移動到指定的表空間當中。


注:
analyze 操作只是統計資訊,並將統計資訊存放起來供日後分析SQL使用,不進行重建之類的具體實施性操作,因此要重建索引的話
還是要用 alter index index_name rebuild


4、其他的統計方法


1)DBMS_STATS:這個當然是最強大的分析包了
--建立統計資訊歷史保留表
exec dbms_stats.create_stat_table(ownname => 'scott',stattab => 'stat_table');


--匯出整個scheme的統計資訊
exec dbms_stats.export_schema_stats(ownname => 'scott',stattab => 'stat_table');


--分析scheme
Exec dbms_stats.gather_schema_stats(ownname => 'test',options => 'GATHER AUTO',
                                       estimate_percent => dbms_stats.auto_sample_size,
                                       method_opt => 'for all indexed columns',
                                       degree => 6 );


--分析表
exec dbms_stats.gather_table_stats(ownname => 'TEST',tabname => 'sm_user',estimate_percent => 10,method_opt=> 'for all indexed columns') ;


--分析索引
exec dbms_stats.gather_index_stats(ownname => 'TEST',indname => 'pk_user_index',estimate_percent => '10',degree => '4') ;


--如果發現執行計劃走錯,刪除表的統計資訊
exec dbms_stats.delete_table_stats(ownname => 'TEST',tabname => 'SM_USER') ;


--匯入錶的歷史統計資訊
exec dbms_stats.import_table_stats(ownname => 'TEST',tabname => 'SM_USER',stattab => 'stat_table') ;


--如果進行分析後,大部分表的執行計劃都走錯,需要導回整個scheme的統計資訊
exec dbms_stats.import_schema_stats(ownname => 'TEST',stattab => 'SM_USER');


--匯入索引的統計資訊
exec dbms_stats.import_index_stats(ownname => 'TEST',indname => 'PK_USER_INDEX',stattab => 'stat_table')




analyze和dbms_stats不同的地方:
analyze是同時更新表和索引的統計資訊,而dbms_stats會先更新表的統計資訊,然後再更新索引的統計資訊,
這裡就有一個問題,就是當表的統計資訊更新後,而索引的統計資訊沒有被更新,這時候cbo就有可能選擇錯誤的plan


2)DBMS_UTILITY.ANALYZE_SCHEMA:可直接分析SCHEMA中所有物件
   如:EXEC DBMS_UTILITY.ANALYZE_SCHEMA ('LTTFM','COMPUTE');


3)DBMS_DDL.ANALYZE_OBJECT:收集物件的的統計資訊




二、索引執行的除錯--檢視oracle查詢執行計劃;
使用toad和plsql工具可以檢視oracle查詢的執行計劃,也可以直接使用命令,命令如下:
SQL> explain plan for SELECT /*+ INDEX(a KSFK_DATAINFO_IDX) */ appl_id,custr_nbr,a.* FROM MCAM.KSFK_APPDATA_INFO a where a.CUSTR_NBR='142724198909251433';


Explained.


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


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1289526622


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


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


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




PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   ||     1 |   564 |     5
  (0)| 00:00:01 |


|   1 |  TABLE ACCESS BY INDEX ROWID| KSFK_APPDATA_INFO |     1 |   564 |     5
  (0)| 00:00:01 |


|*  2 |   INDEX RANGE SCAN   | KSFK_DATAINFO_IDX |     1 ||     3
  (0)| 00:00:01 |


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


PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------




Predicate Information (identified by operation id):
---------------------------------------------------


   2 - access("A"."CUSTR_NBR"='142724198909251433')
   
   
三、oracle查詢執行索引的型別;


Oracle 索引掃描的五種型別
標籤: oracleobjecttableaccesssqlfilter
2010-08-31 11:06 17217人閱讀 評論(5) 收藏 舉報
 分類: Oracle Performance(81)   Oracle Advanced Knowledge(186)  
版權宣告:本文為博主原創文章,未經博主允許不得轉載。
 
之前在討論CBO和RBO的時候提到了索引掃描的幾種型別。
 
Oracle Optimizer CBO RBO
http://blog.csdn.net/tianlesoftware/archive/2010/08/19/5824886.aspx
 
Oracle 索引 詳解
http://blog.csdn.net/tianlesoftware/archive/2010/03/05/5347098.aspx
 
Oracle Explain Plan
http://blog.csdn.net/tianlesoftware/archive/2010/08/20/5827245.aspx
 
 
根據索引的型別與where限制條件的不同,有4種類型的Oracle索引掃描:    
(1)       索引唯一掃描(index unique scan)
(2)       索引範圍掃描(index range scan)
(3)       索引全掃描(index full scan)
(4)       索引快速掃描(index fast full scan)
(5)     索引跳躍掃描(INDEX SKIP SCAN)
 
 
一. 索引唯一掃描(index unique scan)
通過唯一索引查詢一個數值經常返回單個ROWID。如果該唯一索引有多個列組成(即組合索引),則至少要有組合索引的引導列參與到該查詢中,如建立一個索引:create index idx_test on emp(ename, deptno, loc)。則select ename from emp where ename = ‘JACK’ and deptno = ‘DEV’語句可以使用該索引。如果該語句只返回一行,則存取方法稱為索引唯一掃描。而select ename from emp where deptno = ‘DEV’語句則不會使用該索引,因為where子句種沒有引導列。如果存在UNIQUE 或PRIMARY KEY 約束(它保證了語句只存取單行)的話,Oracle經常實現唯一性掃描。
 
如:
SQL> set autot traceonly exp;   -- 只顯示執行計劃
SQL> select * from scott.emp t where t.empno=10;
執行計劃
----------------------------------------------------------
Plan hash value: 2949544139
--------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |     1 |    38 |     1   (0)| 00:0
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP    |     1 |    38 |     1   (0)| 00:0
|*  2 |   INDEX UNIQUE SCAN         | PK_EMP |     1 |       |     0   (0)| 00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("T"."EMPNO"=10)
 
 
二.索引範圍掃描(index range scan)
使用一個索引存取多行資料,同上面一樣,如果索引是組合索引,而且select ename from emp where ename = ‘JACK’ and deptno = ‘DEV’語句返回多行資料,雖然該語句還是使用該組合索引進行查詢,可此時的存取方法稱為索引範圍掃描。
在唯一索引上使用索引範圍掃描的典型情況下是在謂詞(where限制條件)中使用了範圍操作符(如>、<、<>、>=、<=、between)
 
使用索引範圍掃描的例子:
 
SQL> select empno,ename from scott.emp  where empno > 7876 order by empno;
執行計劃
----------------------------------------------------------
Plan hash value: 169057108
--------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |     1 |    10 |     2   (0)| 00:0
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP    |     1 |    10 |     2   (0)| 00:0
|*  2 |   INDEX RANGE SCAN          | PK_EMP |     1 |       |     1   (0)| 00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("EMPNO">7876)
 
在非唯一索引上,謂詞可能返回多行資料,所以在非唯一索引上都使用索引範圍掃描。
 
使用index rang scan的3種情況:
(a) 在唯一索引列上使用了range操作符(> < <> >= <= between)。
(b) 在組合索引上,只使用部分列進行查詢,導致查詢出多行。
(c) 對非唯一索引列上進行的任何查詢。
 
 
三.索引全掃描(index full scan)
與全表掃描對應,也有相應的全Oracle索引掃描。在某些情況下,可能進行全Oracle索引掃描而不是範圍掃描,需要注意的是全Oracle索引掃描只在CBO模式下才有效。 CBO根據統計數值得知進行全Oracle索引掃描比進行全表掃描更有效時,才進行全Oracle索引掃描,而且此時查詢出的資料都必須從索引中可以直接得到。
 
全Oracle索引掃描的例子:
 
SQL> create index big_emp on scott.emp(empno,ename);
索引已建立。
SQL> select empno, ename from scott.emp order by empno,ename;
執行計劃
----------------------------------------------------------
Plan hash value: 322359667
----------------------------------------------------------------------------
| Id  | Operation        | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |         |    14 |   140 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | BIG_EMP |    14 |   140 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------
 
 
四. 索引快速掃描(index fast full scan)
掃描索引中的所有的資料塊,與 index full scan很類似,但是一個顯著的區別就是它不對查詢出的資料進行排序,即資料不是以排序順序被返回。在這種存取方法中,可以使用多塊讀功能,也可以使用並行讀入,以便獲得最大吞吐量與縮短執行時間。
 
索引快速掃描的例子:
SQL> select /*+ index_ffs(dave index_dave) */ id from dave where id>0;
執行計劃
----------------------------------------------------------
Plan hash value: 674200218
--------------------------------------------------------------------------------
| Id  | Operation            | Name       | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |            |     8 |    24 |     2   (0)| 00:00:0
|*  1 |  INDEX FAST FULL SCAN| INDEX_DAVE |     8 |    24 |     2   (0)| 00:00:0
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("ID">0)
 
為了實現這個效果,折騰了半天,最終還是用hint來了.
 
Oracle Hint
http://blog.csdn.net/tianlesoftware/archive/2010/03/05/5347098.aspx
 
 
 
五. 索引跳躍掃描(INDEX SKIP SCAN)
            INDEX SKIP SCAN,發生在多個列建立的複合索引上,如果SQL中謂詞條件只包含索引中的部分列,並且這些列不是建立索引時的第一列時,就可能發生INDEX SKIP SCAN。這裡SKIP的意思是因為查詢條件沒有第一列或前面幾列,被忽略了。
 
Oracle 10g的文件如下:
            Index skip scans improve index scans by nonprefix columns. Often, scanning index blocks is faster than scanning table data blocks.
            Skip scanning lets a composite index be split logically into smaller subindexes. In skip scanning, the initial column of the composite index is not specified in the query. In other words, it is skipped.
            --skip scan 讓組合索引(composite index)邏輯的split 成幾個子索引。如果在在查詢時,第一個列沒有指定,就跳過它。
           
            The number of logical subindexes is determined by the number of distinct values in the initial column. Skip scanning is advantageous if there are few distinct values in the leading column of the composite index and many distinct values in the nonleading key of the index.
            -- 建議將distinct 值小的列作為組合索引的引導列,即第一列。
 
Example 13-5 Index Skip Scan
            Consider, for example, a table employees (sex, employee_id, address) with a composite index on (sex, employee_id). Splitting this composite index would result in two logical subindexes, one for M and one for F.
For this example, suppose you have the following index data:
('F',98)
('F',100)
('F',102)
('F',104)
('M',101)
('M',103)
('M',105)
 
The index is split logically into the following two subindexes:
            (1)The first subindex has the keys with the value F.
            (2)The second subindex has the keys with the value M.
 
Figure 13-2 Index Skip Scan Illustration




The column sex is skipped in the following query:
SELECT *
   FROM employees
WHERE employee_id = 101;
 
            A complete scan of the index is not performed, but the subindex with the value F is searched first, followed by a search of the subindex with the value M.
 
測試:
建立表:
SQL> create table dave_test as select owner,object_id,object_type,created from dba_objects;
Table created.
 
建立組合索引
SQL> create index idx_dave_test_com on dave_test(owner,object_id,object_type);
Index created.
 
--收集表的統計資訊
SQL> exec dbms_stats.gather_table_stats('SYS','DAVE_TEST');
PL/SQL procedure successfully completed.
 
SQL> set autot traceonly exp;
 
指定組合索引的所有欄位時,使用Index range scan:
SQL> select * from dave_test where owner='SYS' and object_id=20 and object_type='TABLE';
 
Execution Plan
----------------------------------------------------------
Plan hash value: 418973243
 
--------------------------------------------------------------------------------
| Id  | Operation                   | Name              | Rows  | Bytes | Cost (
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                   |     1 |    27 |     2
|   1 |  TABLE ACCESS BY INDEX ROWID| DAVE_TEST         |     1 |    27 |     2
|*  2 |   INDEX RANGE SCAN          | IDX_DAVE_TEST_COM |     1 |       |     1
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS' AND "OBJECT_ID"=20 AND "OBJECT_TYPE"='TABLE')
 
指定組合索引的2個欄位時,使用的還是index range scan:
SQL> select * from dave_test where owner='SYS' and object_id=20;
 
Execution Plan
----------------------------------------------------------
Plan hash value: 418973243
 
--------------------------------------------------------------------------------
| Id  | Operation                   | Name              | Rows  | Bytes | Cost (
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                   |     1 |    27 |     3
|   1 |  TABLE ACCESS BY INDEX ROWID| DAVE_TEST         |     1 |    27 |     3
|*  2 |   INDEX RANGE SCAN          | IDX_DAVE_TEST_COM |     1 |       |     2
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OWNER"='SYS' AND "OBJECT_ID"=20)
 
指定組合索引的引導列,即第一個列時,不走索引,走全表掃描
SQL> select * from dave_test where owner='SYS';
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1539627441
 
-------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           | 23567 |   621K|    52   (4)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| DAVE_TEST | 23567 |   621K|    52   (4)| 00:00:01 |
-------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OWNER"='SYS')
 
指定組合索引的非引導列,使用Index skip scan:
SQL> select * from dave_test where object_id=20 and object_type='TABLE';
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3446962311
 
--------------------------------------------------------------------------------
| Id  | Operation                   | Name              | Rows  | Bytes | Cost (
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                   |     1 |    27 |    22
|   1 |  TABLE ACCESS BY INDEX ROWID| DAVE_TEST         |     1 |    27 |    22
|*  2 |   INDEX SKIP SCAN           | IDX_DAVE_TEST_COM |     1 |       |    21
--------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("OBJECT_ID"=20 AND "OBJECT_TYPE"='TABLE')
       filter("OBJECT_ID"=20 AND "OBJECT_TYPE"='TABLE')
 
指定組合索引的最後一列,不走索引,走全表掃描
SQL> select * from dave_test where object_type='TABLE';
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1539627441
 
-------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |  1774 | 47898 |    52   (4)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| DAVE_TEST |  1774 | 47898 |    52   (4)| 00:00:01 |
-------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OBJECT_TYPE"='TABLE')
 
指定組合索引的頭尾2列,不走索引:
SQL> select * from dave_test where owner='SYS' and object_type='TABLE';
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1539627441
 
-------------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |           |   830 | 22410 |    52   (4)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| DAVE_TEST |   830 | 22410 |    52   (4)| 00:00:01 |
-------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("OBJECT_TYPE"='TABLE' AND "OWNER"='SYS')
 
            通過以上測試,和之前官網的說明,Index skip scan 僅是在組合索引的引導列,即第一列沒有指定,並且非引導列指定的情況下。
 
            聯合索引選擇性更高咯,所佔空間應當是比單獨索引要少,因為葉節點節省了重複的rowid,當然branch節點可能稍微多一點。


禁用skip scan:
alter system set “_optimizer_skip_scan_enabled” = false scope=spfile;