B*樹索引

索引是應用設計和開發的一個重要方面。node

若是有太多的索引,DML的性能就會受到影響。sql

若是索引太少,又會影響查詢(包括插入、更新和刪除)的性能。數據庫

Oracle提供了多種不一樣類型的索引以供使用:緩存

B*樹索引:B*樹的構造相似於二叉樹,能根據鍵提供一行或一個行集的快速訪問,一般只需不多的讀操做就能找到正確的行。」B*樹「中的」B「不表明二叉(binary),而表明平衡(balanced)。B*樹索引有如下子類型:網絡

索引組織表(index organized table):索引組織表以B*樹結構存儲。堆表的數據行是以一種無組織的方式存儲的(只要有可用的空間,就能夠放數據),而 IOT中的數據要按主鍵的順序存儲和排序。對應用來講,IOT表現得與「常規「表並沒有二致;須要使用SQL來正確地訪問IOT。IOT對信息獲取、空間系統和OLAP應用最爲有用。併發

B*樹聚簇索引(B*tree cluster index)這些是傳統B*樹索引的一個變體(只是稍有變化)。B*樹聚簇索引用於對聚簇鍵創建索引。在傳統B*樹中,鍵都指向一行;而B*樹聚簇不一樣,一個聚簇鍵會指向一個塊,其中包含與這個聚簇鍵相關的多行app

降序索引(descending index):降序索引容許數據在索引結構中按「從大到小「的順序(降序)排序,而不是按」從小到大「的順序(升序)排序。dom

反向鍵索引(reverse key index):這也是B*樹索引,只不過鍵中的字節會「反轉「。利用反向鍵索引,若是索引中填充的是遞增的值,索引條目在索引中能夠獲得更均勻的分佈。例如,若是使用一個序列來生成主鍵,這個序列將生成諸如987500、98750一、987502等值。這些值是順序的,因此假若使用一個傳統的B*樹索引,這些值就可能放在同一個右側塊上,這就加重了對這一塊的競爭。利用反向鍵,Oracle則會邏輯地對20578九、10578九、005789等創建索引。Oracle將數據放在索引中以前,將先把所存儲數據的字節反轉,這樣原來可能在索引中相鄰放置的值在字節反轉以後就會相距很遠。經過反轉字節,對索引的插入就會分佈到多個塊上。ide

位圖索引(bitmap index):在一顆B*樹中,一般索引條目和行之間存在一種一對一的關係:一個索引條目就指向一行。而對於位圖索引,一個索引條目則使用一個位圖同時指向多行位圖索引適用於高度重複並且一般只讀的數據(高度重複是指相對於表中的總行數,數據只有不多的幾個不一樣值)。考慮在一個有100萬行的表中,每一個列只有3個可取值:Y、N和NULL。舉例來講,若是你須要頻繁地統計多少行有值Y,這就很適合創建位圖索引。不過並非說若是這個表中某一列有11.000個不一樣的值就不能創建位圖索引,這一列固然也能夠創建位圖索引。在一個OLTP數據庫中,因爲存在併發性相關的問題,因此不能考慮使用位圖索引。注意,位圖索引要求使用Oracle企業版或我的版。函數

位圖聯結索引(bitmap join index):這爲索引結構(而不是表)中的數據提供了一種逆規範化的方法。例如,請考慮簡單的EMP和DEPT表。多少人在位於波士頓的部門工做?EMP有一個指向DEPT的外鍵,要想統計LOC值爲Boston的部門中的員工人數,一般必須完成表聯結,將LOC列聯結至EMP記錄來回答這個問題。經過使用位圖聯結索引,則能夠在EMP表上對LOC列創建索引。

基於函數的索引(function-based index):這些就是B*樹索引或位圖索引,它將一個函數計算獲得的結果存儲在行的列中,而不是存儲列數據自己。能夠把基於函數的索引看做一個虛擬列(或派生列)上的索引,換句話說,這個列並不物理存儲在表中。基於函數的索引能夠用於加快形如SELECT * FROM T WHERE FUNCTION(DATABASE_COLUMN) = SAME_VALUE這樣的查詢,由於值FUNCTION(DATABASE_COLUMN)已經提早計算並存儲在索引中。

應用域索引(application domain index):應用域索引是你本身構建和存儲的索引,可能存儲在Oracle中,也可能在Oracle以外。你要告訴優化器索引的選擇性如何,以及執行的開銷有多大,優化器則會根據你提供的信息來決定是否使用你的索引。Oracle文本索引就是應用域索引的一個例子;你也可使用構建Oracle文本索引所用的工具來創建本身的索引。須要指出,這裏建立的「索引「不須要使用傳統的索引結構。例如,Oracle文本索引就使用了一組表來實現其索引概念。

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

B*樹索引

這是數據庫中最經常使用的一類索引結構。其實現與二叉查找樹很類似。其目標是儘量減小Oracle查找數據所花費的時間。

這個樹最底層的塊稱爲葉子節點(leaf node)或葉子塊(leaf block),其中分別包含各個索引鍵以及一個rowid(指向所索引的行)。葉子節點之上的內部塊稱爲分支塊(branch block)。這些節點用於在結構中實現導航。例如,若是想在索引中找到值42,要從樹頂開始,找到左分支。咱們要檢查這個塊,並發現須要找到範圍在「42..50「的塊。這個塊將是葉子塊,其中會指示包含數42的行。索引的葉子節點實際上構成了一個雙向鏈表。一旦發現要從葉子節點中的哪裏」開始「(也就是說,一旦發現第一個值),執行值的有序掃描(也稱爲索引區間掃描(index range scan))就會很容易。咱們不用再在索引結構中導航;而只需根據須要經過葉子節點向前或向後掃描就能夠了。因此要知足諸如如下的謂詞條件將至關簡單:

where x between 20 and 30

Oracle發現第一個最小鍵值大於或等於20的索引葉子塊,而後水平地遍歷葉子節點鏈表,直到最後命中一個大於30的值。

B*樹索引中不存在非唯一(nonunique)條目。在一個非唯一索引中,Oracle會把rowid做爲一個額外的列(有一個長度字節)追加到鍵上,使得鍵唯一。例如,若是有一個CREATE INDEX I ON T(X,Y)索引,從概念上講,它就是CREATE UNIQUE INDEX I ON T(X,Y,ROWID)。在一個唯一索引中,根據定義的唯一性,Oracle不會再向索引鍵增長rowid。在非唯一索引中,數據會首先按索引鍵值排序(依索引鍵的順序)。而後按rowid升序排序。而在唯一索引中,數據只按索引鍵排序。

B*樹的特色之一是:全部葉子塊都應該在樹的同一層上。這一層也稱爲索引的高度(height),這說明全部從索引的根塊到葉子塊的遍歷都會訪問一樣數目的塊。也就是說,對於形如」SELECT INDEXED_COL FROM T WHERE INDEXED_COL = :X「的索引,要到達葉子塊來獲取第一行,不論使用的:X值是什麼,都會執行一樣數目的I/O。換句話說,索引是高度平衡的(height balanced)。大多數B*樹索引的高度都是2或者3,即便索引中有數百萬行記錄也是如此。這說明,通常來說,在索引中找到一個鍵只須要執行2或3次I/O。

Oracle在表示從索引根塊到葉子塊遍歷所涉及的塊數時用了兩個含義稍有不一樣的術語。第一個是高度(HEIGHT),這是指從根塊到葉子塊遍歷所需的塊數。使用ANALYZE INDEX <name> VALIDATE STRUCTURE命令分析索引後,能夠從INDEX_STATS視圖找到這個高度(HEIGHT)值。另外一個術語是BLEVEL,這是指分支層數,與HEIGHT相差1(BLEVEL不把葉子塊層算在內)。收集統計信息後,能夠在諸USER_INDEXES之類的常規字典表中找到BLEVEL值。

例如,假設有一個11,000,000行的表,其主鍵索引創建在一個數字列上:

scott@ORCL>select index_name, blevel, num_rows
  2  from user_indexes
  3  where table_name = 'BIG_TABLE';

INDEX_NAME                         BLEVEL   NUM_ROWS
------------------------------ ---------- ----------
BIG_TABLE_PK                            1     100000

BLEVEL爲1,這說明HEIGHT爲2,要找到葉子須要1個I/O(訪問葉子自己還須要第2個I/O)。因此,要從這個索引中獲取任何給定的鍵值,共須要2個I/O:

scott@ORCL>select id from big_table where id = 42;

        ID
----------
        42


執行計劃
----------------------------------------------------------
Plan hash value: 1688966252

--------------------------------------------------------------------------------
--
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time
 |
--------------------------------------------------------------------------------
--
|   0 | SELECT STATEMENT  |              |     1 |     5 |     1   (0)| 00:00:01
 |
|*  1 |  INDEX UNIQUE SCAN| BIG_TABLE_PK |     1 |     5 |     1   (0)| 00:00:01
 |
--------------------------------------------------------------------------------
--

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

   1 - access("ID"=42)


統計信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          2  consistent gets
          0  physical reads
          0  redo size
        521  bytes sent via SQL*Net to client
        520  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

scott@ORCL>select id from big_table where id = 12345;

        ID
----------
     12345


執行計劃
----------------------------------------------------------
Plan hash value: 1688966252

--------------------------------------------------------------------------------
--
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time
 |
--------------------------------------------------------------------------------
--
|   0 | SELECT STATEMENT  |              |     1 |     5 |     1   (0)| 00:00:01
 |
|*  1 |  INDEX UNIQUE SCAN| BIG_TABLE_PK |     1 |     5 |     1   (0)| 00:00:01
 |
--------------------------------------------------------------------------------
--

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

   1 - access("ID"=12345)


統計信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          1  physical reads
          0  redo size
        523  bytes sent via SQL*Net to client
        520  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

scott@ORCL>select id from big_table where id = 1234567;

未選定行


執行計劃
----------------------------------------------------------
Plan hash value: 1688966252

--------------------------------------------------------------------------------
--
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time
 |
--------------------------------------------------------------------------------
--
|   0 | SELECT STATEMENT  |              |     1 |     5 |     1   (0)| 00:00:01
 |
|*  1 |  INDEX UNIQUE SCAN| BIG_TABLE_PK |     1 |     5 |     1   (0)| 00:00:01
 |
--------------------------------------------------------------------------------
--

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

   1 - access("ID"=1234567)


統計信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          2  consistent gets
          1  physical reads
          0  redo size
        331  bytes sent via SQL*Net to client
        509  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

B*樹是一個絕佳的通用索引機制,不管是大表仍是小表都很適用,隨着底層表大小的增加,獲取數據的性能只會稍有惡化(或者根本不會惡化)。

1 索引鍵壓縮

對於B*樹索引,從串聯(多列)索引去除冗餘。
壓縮鍵索引(compressed key index)的基本概念是,每一個鍵條目分解爲兩個部分:「前綴」和「後綴」。前綴創建在串聯索引(concatenated index)的前幾列上,這些列有許多重複的值。後綴則在索引鍵的後幾列上,這是前綴所在索引條目中的唯一部分(即有區別的部分)。

下面經過一個例子來講明,建立一個表和一個串聯索引,並使用ANALYZE INDEX測量無壓縮時所用的空間。而後利用索引壓縮建立這個索引,分別壓縮不一樣數目的鍵條目,查看有什麼差異。下面先來看這個表和索引:

sys@ORCL>create table t
  2  as
  3  select * from all_objects;

表已建立。

sys@ORCL>create index t_idx on
  2  t(owner,object_type,object_name);

索引已建立。

sys@ORCL>analyze index t_idx validate structure;

索引已分析

而後建立一個INX_STATS表,在這裏存放INDEX_STATS信息,咱們把表中的行標記爲「未壓縮」(noncompressed):

sys@ORCL>create table idx_stats
  2  as
  3  select 'noncompressed' what, a.*
  4  from index_stats a;

表已建立。

如今能夠看到,OWNER部分重複了屢次,這說明,這個索引中的一個索引塊可能有數十個條目,

能夠從中抽取出重複的OWNER列

全部者(owner)名在葉子塊上只出現了一次,而不是在每一個重複的條目上都出現一次。

運行如下腳本,傳入數字 1 做爲參數來從新建立這個索引,在此索引使用了第一列的壓縮:

drop index t_idx;
create index t_idx on
t(owner,object_type,object_name)
compress &1;
analyze index t_idx validate structure;
insert into idx_stats
select 'compress &1', a.*
from index_stats a;

爲了進行比較,咱們不只在壓縮一列的基礎上運行了這個腳本,還分別使用了兩個和3個壓縮列來運行這個腳本,查看會發生什麼狀況。最終,咱們將查詢IDX_STATS,應該能觀察到如下信息:

sys@ORCL>select what, height, lf_blks, br_blks,
  2  btree_space, opt_cmpr_count, opt_cmpr_pctsave
  3  from idx_stats
  4  /

WHAT              HEIGHT	LF_BLKS    BR_BLKS BTREE_SPACE 	OPT_CMPR_COUNT 	OPT_CM	PR_PCTSAVE
-------------			------	-------		------- ----------- 	-------------- 	----------------
noncompressed     3				503          3     4048096			2								28
compress 1        3				446          3     3590312			2								19
compress 2        3				359          3     2894660			2								0
compress 3        3				562          4     4525880			2								35

能夠看到,COMPRESS 1索引的大小大約是無壓縮索引的89%(經過比較BTREE_SPACE得出)。葉子塊數大幅度降低。更進一步,使用COMPRESS 2時,節省的幅度更爲顯著。所獲得的索引大約是原索引(無壓縮索引)的72%.實際上,利用列OPT_CMPR_PCTSAVE的信息(這表明最優的節省壓縮百分比(optimum compression percent saved)或指望從壓縮獲得的節省幅度)。咱們能夠猜想出COMPRESS 2索引的大小:

sys@ORCL>select 4048096*(0.28) from dual;

4048096*(0.28)
--------------
    1133466.88

sys@ORCL>select 4048096-2894660 from dual;

4048096-2894660
---------------
        1153436

對無壓縮索引執行ANALYZE命令時,會填寫OPT_CMPR_PCTSAVE/OPT_CMPR_COUNT列,並估計出:利用COMPRESS 2,能夠節省28%的空間;

不過,再看看COMPRESS 3。若是壓縮3列,所獲得的索引實際上會更大:是原來索引大小的110%。這是由於:每刪除一個重複的前綴,能節省N個副本的空間,可是做爲壓縮機制的一部分,這會在葉子塊上增長4字節的開銷。把OBJECT_NAME列增長到壓縮鍵後,則使得這個鍵是唯一的;在這種狀況下,則說明沒有重複的副本能夠提取。所以,最後的結果就是:咱們只是向每一個索引鍵條目增長了4個字節,而不能提取出任何重複的數據。IDX_STATS中的OPT_CMPR_COUNT列真是精準無比,確實給出了可用的最佳壓縮數,OPT_COMPR_PCTSAVE則指出了能夠獲得多大的節省幅度。
對如今來講,這種壓縮不是免費的。如今壓縮索引比原來更復雜了。Oracle會花更多的時間來處理這個索引結構中的數據,不光在修改期間維護索引更耗時,查詢期間搜索索引也更花時間。利用壓縮,塊緩衝區緩存比之前能存放更多的索引條目,緩衝命中率可能會上升,物理I/O應該降低,可是要多佔用一些CPU時間來處理索引,還會增長塊競爭的可能性。對於散列聚簇,獲取100萬個隨機的行可能佔用更多的CPU時間,可是I/O數會減半;這裏也是同樣,咱們必須清楚存在的這種折中。若是你如今已經在大量佔用CPU時間,在增長壓縮鍵索引只能拔苗助長,這會減慢處理的速度。另外一方面,若是目前的I/O操做不少,使用壓縮鍵索引就能加快處理速度。

2 反向鍵索引

B*樹索引的另外一個特色是可以將索引鍵「反轉」。 實現B*樹索引的目的是爲了減小「右側」索引中對索引葉子塊的競爭,好比在一個Oracle RAC環境中,某些列用一個序列值或時間戳填充,這些列上創建的索引就屬於「右側」(right-hand-side)索引。

RAC是一種Oracle配置,其中多個實例能夠裝載和打開同一個數據庫。若是兩個實例須要同時修改同一個數據塊,它們會經過一個硬件互連(interconnect)來回傳遞這個塊來實現共享,互連是兩個(或多個)機器之間的一條專用網絡鏈接。若是某個利用一個序列填充,這個列上有一個主鍵索引,那麼每一個人插入新值時,都會視圖修改目前索引結構右側的左塊(索引中較高的值都放在右側,而較低的值放在左側)。若是對用序列填充的列上的索引進行修改,就會彙集在不多的一組葉子塊上。假若將索引的鍵反轉,對索引進行插入時,就能在索引中的全部葉子鍵上分佈開(不過這每每會使索引不能獲得充分地填充)。

反向鍵能夠用做一種減小競爭的方法(即便只有一個Oracle實例)。反向鍵主要用於緩解忙索引右側的緩衝區忙等待

在介紹如何度量反向鍵索引的影響以前,咱們先來討論物理上反向鍵索引會作什麼。反向鍵索引只是將索引鍵中各個列的字節反轉。若是考慮9010一、90102和90103這樣幾個數,使用Oracle DUMP函數查看其內部表示,能夠看到這幾個數的表示以下:

scott@ORCL>select 90101, dump(90101,11.) from dual
  2  union all
  3  select 90102, dump(90102,11.) from dual
  4  union all
  5  select 90103, dump(90103,11.) from dual
  6  /

     90101 DUMP(90101,11.)
---------- -----------------------
     90101 Typ=2 Len=4: 195,10,2,2
     90102 Typ=2 Len=4: 195,10,2,3
     90103 Typ=2 Len=4: 195,10,2,4

每一個數的長度都是4字節,它們只是最後一個字節有所不一樣。這些數最後可能在一個索引結構中向右依次放置。不過,若是反轉這些數的字節,Oracle就會插入如下值:

scott@ORCL>select 90101, dump(reverse('90101'),11.) from dual
  2  union all
  3  select 90102, dump(reverse('90102'),11.) from dual
  4  union all
  5  select 90103, dump(reverse('90103'),11.) from dual
  6  /

     90101 DUMP(REVERSE('90101'),11.)
---------- ----------------------------
     90101 Typ=96 Len=5: 49,48,49,48,57
     90102 Typ=96 Len=5: 50,48,49,48,57
     90103 Typ=96 Len=5: 51,48,49,48,57

這些數彼此之間最後會「相距很遠「。這樣訪問同一個塊(最右邊的塊)的RAC實例個數就能減小,相應地,在RAC實例之間傳輸的塊數也會減小。反向鍵索引的缺點之一是:能用常規索引的地方不必定能用反向鍵索引。例如,在回答如下謂詞時,X上的反向鍵索引就沒有:

where x > 5

存儲以前,數據不是按X在索引中排序,而是按REVERSE(X)排序,所以,對X>5的區間掃描不能使用這個索引。另外一方面,有些區間掃描確實能夠在反向鍵索引上完成。若是在(X,Y)上有一個串聯索引,如下謂詞就可以利用反向鍵索引,並對它執行「區間掃描「:

where x = 5

這是由於,首先將X的字節反轉,而後再將Y的字節反轉。Oracle並非將(X||Y)的字節反轉,而是會存儲(REVERSE(X) || REVERSE(Y))。這說明, X = 5的全部值會存儲在一塊兒,因此Oracle能夠對這個索引執行區間掃描來找到全部這些數據。

下面,假設在一個用序列填充的表上有一個代理主鍵(surrogate primary key),並且不須要在這個(主鍵)索引上使用區間掃描,也就是說,不須要作MAX(primary_key)、MIN(primary_key)、WHERE primary_key < 100等查詢,在有大量插入操做的狀況下,即便只有一個Oracle實例,也能夠考慮使用反向鍵索引。我創建了兩個不一樣的測試,一個是在純PL/SQL環境中進行測試,另外一個使用了Pro*C,我想經過這兩個測試來展現反向鍵索引和傳統索引對插入的不一樣影響,即若是一個表的主鍵上有一個反向鍵索引,與有一個傳統索引的狀況相比,完成插入時會有什麼差異。在這兩種狀況下,所用的表都是用如下DDL建立的(這裏使用了ASSM來避免表塊的競爭,這樣能夠把索引塊的競爭隔離開):

scott@ORCL>create table t tablespace assm
  2  as
  3  select 0 id, a.*
  4  from all_objects a
  5  where 1<0;

表已建立。

scott@ORCL>alter table t
  2  add constraint t_pk
  3  primary key (id)
  4  using index (create index t_pk on t(id) REVERSE tablespace assm);

表已更改。

scott@ORCL>create sequence s cache 1000;

序列已建立。

scott@ORCL>create or replace procedure do_sql
  2  as
  3  begin
  4     for x in ( select rownum r, all_objects.* from all_objects )
  5     loop
  6             insert into t
  7                     ( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME,
  8                     OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED,
  9                     LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,
 10                     GENERATED, SECONDARY )
 11             values
 12                     ( s.nextval, x.OWNER, x.OBJECT_NAME, x.SUBOBJECT_NAME,
 13                     x.OBJECT_ID, x.DATA_OBJECT_ID, x.OBJECT_TYPE, x.CREATED,

 14                     x.LAST_DDL_TIME, x.TIMESTAMP, x.STATUS, x.TEMPORARY,
 15                     x.GENERATED, x.SECONDARY );
 16             if ( mod(x.r,100) = 0 )
 17             then
 18                     commit;
 19             end if;
 20     end loop;
 21     commit;
 22  end;
 23  /

過程已建立。

使用了Pro*C來模擬一個數據倉庫抽取、轉換和加載(extract, transform, load, ETL)例程,它會在提交之間一次成批地處理100行(即每次提交前都處理100行):

exec sql declare c cursor for select * from all_objects;
exec sql open c;
exec sql whenever notfound do break;
for(;;)
{
		exec sql
		fetch c into :owner:owner_i,
		:object_name:object_name_i, :subobject_name:subobject_name_i,
		:object_id:object_id_i, :data_object_id:data_object_id_i,
		:object_type:object_type_i, :created:created_i,
		:last_ddl_time:last_ddl_time_i, :timestamp:timestamp_i,
		:status:status_i, :temporary:temporary_i,
		:generated:generated_i, :secondary:secondary_i;

	exec sql

	insert into t
		( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME,
		OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED,
		LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,
		GENERATED, SECONDARY )
	values
		( s.nextval, :owner:owner_i, :object_name:object_name_i,
		:subobject_name:subobject_name_i, :object_id:object_id_i,
		:data_object_id:data_object_id_i, :object_type:object_type_i,
		:created:created_i, :last_ddl_time:last_ddl_time_i,
		:timestamp:timestamp_i, :status:status_i,
		:temporary:temporary_i, :generated:generated_i,
		:secondary:secondary_i );
	if ( (++cnt%100) == 0 )
	{
		exec sql commit;
	}
}
exec sql whenever notfound continue;
exec sql commit;
exec sql close c;

3 降序索引

它容許在索引中以降序(從大到小的順序)存儲一列,而不是升序(從小到大)存儲。在以前的Oracle版本(即Oracle8i之前的版本)中,儘管語法上也支持DESC(降序)關鍵字,可是通常都會將其忽略,這個關鍵字對於索引中數據如何存儲或使用沒有任何影響。不過,在Oracle8i及以上版本中,DESC關鍵字確實會改變建立和使用索引的方式。

例如,若是使用先前的表T,並以下查詢這個表:

scott@ORCL>set autotrace traceonly explain
scott@ORCL>select owner, object_type
  2  from t
  3  where owner between 'T' and 'Z'
  4  and object_type is not null
  5  order by owner DESC, object_type DESC;

執行計劃
----------------------------------------------------------
Plan hash value: 961378228

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |    28 |     3  (34)| 00:00:01 |
|   1 |  SORT ORDER BY     |      |     1 |    28 |     3  (34)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| T    |     1 |    28 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

   2 - filter("OBJECT_TYPE" IS NOT NULL AND "OWNER">='T' AND
              "OWNER"<='Z')


scott@ORCL>create index desc_t_idx on t(owner desc,object_type asc);

索引已建立。

scott@ORCL>exec dbms_stats.gather_index_stats( user, 'DESC_T_IDX' );

PL/SQL 過程已成功完成。

scott@ORCL>select owner, object_type
  2  from t
  3  where owner between 'T' and 'Z'
  4  and object_type is not null
  5  order by owner DESC, object_type ASC;

執行計劃
----------------------------------------------------------
Plan hash value: 2494308350

-------------------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |     1 |    28 |     0   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| DESC_T_IDX |     1 |    28 |     0   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   1 - access(SYS_OP_DESCEND("OWNER")>=HEXTORAW('A5FF')  AND
              SYS_OP_DESCEND("OWNER")<=HEXTORAW('ABFF') )
       filter("OBJECT_TYPE" IS NOT NULL AND
              SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))>='T' AND
              SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))<='Z')

4 什麼狀況下應該使用B*樹索引?

僅當要經過索引訪問表中不多的一部分行(只佔一個很小的百分比)時,才使用B*樹在列上創建索引。
若是要處理表中的多行,並且可使用索引而不用表,就可使用一個B*樹索引。
根據以上建議,有兩種使用索引的方法:
索引用於訪問表中的行:經過讀索引來訪問表中的行。此時你但願訪問表中不多的一部分行(只佔一個很小的百分比)。
索引用於回答一個查詢:索引包含了足夠的信息來回答整個查詢,我根本不用去訪問表。在這種狀況下,索引則用做一個「較瘦「版本的表。

第一種狀況(也就是說,爲了訪問表中不多的一部分行而使用索引)是指,若是有一個表T(仍是使用前面的表T),並有以下的一個查詢計劃:

scott@ORCL>set autotrace traceonly explain
scott@ORCL>select owner, status
  2  from t
  3  where owner = USER;

執行計劃
----------------------------------------------------------
Plan hash value: 1049179052

--------------------------------------------------------------------------------
----------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)|
Time     |
--------------------------------------------------------------------------------
----------
|   0 | SELECT STATEMENT            |            |     1 |    22 |     0   (0)|
00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T          |     1 |    22 |     0   (0)|
00:00:01 |
|*  2 |   INDEX RANGE SCAN          | DESC_T_IDX |     1 |       |     0   (0)|
00:00:01 |
--------------------------------------------------------------------------------
----------

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

   2 - access(SYS_OP_DESCEND("OWNER")=SYS_OP_DESCEND(USER@!))
       filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))=USER@!)

那你就應該只訪問這個表的不多一部分行(只佔一個很小的百分比)。這裏要注意TABLE ACCESS BY INDEX ROWID後面的INDEX (RANGE SCAN)。這說明,Oracle會讀索引,而後會對索引條目執行一個數據庫塊讀(邏輯或物理I/O)來獲得行數據。若是你要經過索引訪問T中的大量行(佔很大的百分比),這就不是最高效的方法了。

在第二種狀況下(也就是說,能夠用索引而沒必要用表),你能夠經過索引處理表中100%的行(或者實際上能夠是任何比例)。使用索引能夠只是爲了建立一個「較瘦「版本的表。如下查詢演示了這個概念:

scott@ORCL>select count(*)
  2  from t
  3  where owner = user;

執行計劃
----------------------------------------------------------
Plan hash value: 2221593640

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

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

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

|   0 | SELECT STATEMENT  |            |     1 |    17 |     0   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE   |            |     1 |    17 |            |          |

|*  2 |   INDEX RANGE SCAN| DESC_T_IDX |     1 |    17 |     0   (0)| 00:00:01 |

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


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

   2 - access(SYS_OP_DESCEND("OWNER")=SYS_OP_DESCEND(USER@!))
       filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("OWNER"))=USER@!)

在此,只使用了索引來回答查詢,如今訪問多少行都不要緊,由於咱們只會使用索引。從查詢計劃能夠看出,這裏從未訪問底層表;咱們只是掃描了索引結構自己。

若是必須完成TABLE ACCESS BY INDEX ROWID,就必須確保只訪問表中不多的一部分塊(只佔很小的百分比),這一般對應爲不多的一部分行,或者須要可以儘量快地獲取前面的幾行。若是咱們訪問的行太多(所佔百分比過大,不在總行數的1%~20%之間),那麼與全表掃描相比,經過B*樹索引來訪問這些數據一般要花更長的時間。

對於第二種類型的查詢,即答案徹底能夠在索引中找到,狀況就徹底不一樣了。咱們會讀一個索引塊,選出許多「行「來處理,而後再到另外一個索引塊,如此繼續,在此從不訪問表。某些狀況下,還能夠在索引上執行一個快速全面掃描,從而更快地回答這類查詢。快速全面掃描是指,數據庫不按特定的順序讀取索引塊,而只是開始 讀取它們。這裏再也不是把索引只用做一個索引,此時索引更像是一個表。若是採用快速全面掃描,將再也不按索引條目的順序來獲得行。

通常來說,B*樹索引會放在頻繁使用查詢謂詞的列上,並且咱們但願從表中只返回少許的數據(只佔很小的百分比),或者最終用戶請求當即獲得反饋。在一個瘦(thin)表(也就是說,只有不多的幾個列,或者列很小)上,這個百分比可能至關小。使用這個索引的查詢應該只獲取表中2%~3%(或者更少)的行。在一個胖(fat)表中(也就是說,這個表有不少列,或者列很寬),百分比則可能會上升到表的20%~25%。以上建議不必定直接適用於每個人;這個比例並不直觀,但很精確。索引按索引鍵的順序存儲。索引會按鍵的有序順序進行訪問。索引指向的塊則隨機地存儲在堆中。所以,咱們經過索引訪問表時,會執行大量分散、隨機的I/O。這裏「分散「(scattered)是指,索引會告訴咱們讀取塊1,而後是塊11.000、塊20五、塊32一、塊一、塊11.03二、塊1,等等,它不會要求咱們按一種連續的方式讀取塊一、而後是塊2,接着是塊3.咱們將以一種很是隨意的方式讀取和從新讀取塊。這種塊I/O可能很是慢。

1. 物理組織

假設有一個表,其中的行主鍵由一個序列來填充。向這個表增長數據時,序列號相鄰的行通常存儲位置也會彼此「相鄰「。

表會很天然地按主鍵順序聚簇(由於數據或多或少就是已這種屬性增長的)。固然,它不必定嚴格按照鍵聚簇(要想作到這一點,必須使用一個IOT),可是,通常來說,主鍵值彼此接近的行的物理位置也會「靠「在一塊兒。若是發出如下查詢:

select * from T where primary_key between :x and :y

你想要的行一般就位於一樣的塊上。在這種狀況下,即便要訪問大量的行(佔很大的百分比),索引區間掃描可能也頗有用。緣由在於:咱們須要讀取和從新讀取的數據庫塊極可能會被緩存,由於數據共同放置在同一個位置(co-located)。另外一方面,若是行並不是共同存儲在一個位置上,使用這個索引對性能來說可能就是災難性的。

只需一個小小的演示就能說明這一點。首先建立一個表,這個表主要按其主鍵排序:

system@ORCL>create table colocated ( x int, y varchar2(80) );

表已建立。

system@ORCL>begin
  2     for i in 1 .. 100000
  3     loop
  4             insert into colocated(x,y)
  5             values (i, rpad(dbms_random.random,75,'*') );
  6     end loop;
  7  end;
  8  /

PL/SQL 過程已成功完成。

system@ORCL>alter table colocated
  2  add constraint colocated_pk
  3  primary key(x);

表已更改。

system@ORCL>begin
  2     dbms_stats.gather_table_stats( user, 'COLOCATED', cascade=>true );
  3  end;
  4  /

PL/SQL 過程已成功完成。

這個表正好知足前面的描述,即在塊大小爲8KB的一個數據庫中,每塊有大約100行。在這個表中,,X = 11.2,3的行極有可能在同一個塊上。仍取這個表,但有意地使它「無組織「。在COLOCATED表中,咱們建立一個Y列,它帶有一個前導隨機數,如今利用這一點使得數據」無組織「,即再也不按主鍵排序:

system@ORCL>create table disorganized
  2  as
  3  select x,y
  4  from colocated
  5  order by y;

表已建立。

system@ORCL>alter table disorganized
  2  add constraint disorganized_pk
  3  primary key (x);

表已更改。

system@ORCL>begin
  2     dbms_stats.gather_table_stats( user, 'DISORGANIZED', cascade=>true );
  3  end;
  4  /

PL/SQL 過程已成功完成。

能夠證實,這兩個表是同樣的——這是一個關係數據庫,因此物理組織對於返回的答案沒有影響(至少關於數據庫理論的課程中就是這麼教的)。實際上,儘管會返回相同的答案,但這兩個表的性能特徵卻有着天壤之別。給定一樣的問題,使用一樣的查詢計劃,查看TKPROF(SQL跟蹤)輸出,能夠看到如下報告:

CPU      Elapsd
  Physical Rds   Executions  Rds per Exec   %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
          1,063            3          354.3   78.3     0.28      0.66 2088343704
select * from colocated where x between 20000 and 40000

            942            1          942.0   69.4     0.31      1.27 3151995455
select /*+ index( disorganized disorganized_pk ) */* from disorg
anized where x between 20000 and 40000

...

                                                     CPU      Elapsd     Old
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
         21,374            1       21,374.0   65.1     0.31      1.27 3151995455
select /*+ index( disorganized disorganized_pk ) */* from disorg
anized where x between 20000 and 40000

          6,673            3        2,224.3   20.3     0.28      0.66 2088343704
select * from colocated where x between 20000 and 40000

在個人數據庫中(塊大小爲8KB),這些表的總塊數以下:

system@ORCL>select table_name, blocks
  2  from user_tables
  3  where table_name in ( 'COLOCATED', 'DISORGANIZED' );
TABLE_NAME                        BLOCKS
------------------------------ ----------
COLOCATED                            1191
DISORGANIZED                         1191

每一個邏輯I/O都涉及緩衝區緩存的一個或多個鎖存器。在一個多用戶/CPU狀況下,在咱們自旋並等待鎖存器時,與第一個查詢相比,第二個查詢所用的CPU時間無疑會高出幾倍。

ARRAYSIZE對邏輯I/O的影響

ARRAYSIZE是客戶請求下一行時Oracle向客戶返回的行數。客戶將緩存這些行,在向數據庫請求下一個行集以前會先使用緩存的這些行,ARRAYSIZE對查詢執行的邏輯I/O可能有很是重要的影響,這是由於,若是必須跨數據庫調用反覆地訪問同一個塊(也就是說,經過多個數據庫調用反覆訪問同一個塊,這裏特別是指跨獲取調用),Oracle就必須一而再、再而三地從緩衝區緩存獲取這個塊。

 

聚簇因子

USER_INDEXES視圖中的CLUSTERING_FACTOR列

根據索引的值指示表中行的有序程度:
若是這個值與塊數接近,則說明表至關有序,獲得了很好的組織,在這種狀況下,同一個葉子塊中的索引條目可能指向同一個數據塊上的行。
若是這個值與行數接近,表的次序可能就是很是隨機的。在這種狀況下,同一個葉子塊上的索引條目不太可能指向同一個數據塊上的行。

能夠把聚簇因子(clustering factor)看做是經過索引讀取整個表時對錶執行的邏輯I/O次數。也就是說,CLUSTERING_FACTOR指示了表相對於索引自己的有序程度,查看這些索引時,會看到如下結果:

system@ORCL>select a.index_name,
  2  b.num_rows,
  3  b.blocks,
  4  a.clustering_factor
  5  from user_indexes a, user_tables b
  6  where index_name in ('COLOCATED_PK', 'DISORGANIZED_PK' )
  7  and a.table_name = b.table_name
  8  /

INDEX_NAME                       NUM_ROWS     BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ---------- -----------------
COLOCATED_PK                       100000       1191              1190
DISORGANIZED_PK                    100000       1191             99915

因此數據庫說:「若是經過索引COLOCATED_PK從頭至尾地讀取COLOCATED表中的每一行,就要執行1190次I/O。不過,若是咱們對DISORGANIZED表作一樣的事情,則會對這個表執行99,915次I/O。「之因此存在這麼大的區別,緣由在於,當Oracle對索引結構執行區間掃描時,若是它發現索引中的下一行與前一行在同一個數據庫塊上,就不會再執行另外一個I/O從緩衝區緩存中得到表塊。它已經有表塊的一個句柄,只需直接使用就能夠了。不過,若是下一行不在同一個塊上,就會釋放當前的這個塊,而執行另外一個I/O從緩衝區緩存獲取要處理的下一個塊。所以,在咱們對索引執行區間掃描時,COLOCATED_PK索引會發現下一行幾乎總於前一行在同一個塊上。DISORGANIZED_PK索引起現的狀況則剛好相反。經過使用提示,讓優化器使用索引全面掃描來讀取整個表,再統計非NULL的Y值個數,就能看到經過索引讀取整個表須要執行多少次I/O:

select count(Y)
from
(select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated)

    CPU                  CPU per             Elapsd                     Old
  Time (s)   Executions  Exec (s)  %Total   Time (s)    Buffer Gets  Hash Value
---------- ------------ ---------- ------ ---------- --------------- ----------
      0.11            1       0.11   24.1       0.39           1,399 2513878408


  Elapsed                Elap per            CPU                        Old
  Time (s)   Executions  Exec (s)  %Total   Time (s)  Physical Reads Hash Value
---------- ------------ ---------- ------ ---------- --------------- ----------
      0.39            1       0.39   17.8       0.11           1,399 2513878408


                                                     CPU      Elapsd     Old
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
          1,399            1        1,399.0   15.3     0.11      0.39 2513878408


                                                     CPU      Elapsd
  Physical Rds   Executions  Rds per Exec   %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
          1,399            1        1,399.0   52.5     0.11      0.39 2513878408


                                                CPU per    Elap per     Old
 Executions   Rows Processed   Rows per Exec    Exec (s)   Exec (s)  Hash Value
------------ --------------- ---------------- ----------- ---------- ----------
           1               1              1.0       0.11        0.39  2513878408


                           % Total    Old
 Parse Calls   Executions   Parses Hash Value
------------ ------------ -------- ----------
           1            1     1.56 2513878408
select count(Y)
from
(select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized)

    CPU                  CPU per             Elapsd                     Old
  Time (s)   Executions  Exec (s)  %Total   Time (s)    Buffer Gets  Hash Value
---------- ------------ ---------- ------ ---------- --------------- ----------
      0.31            1       0.31   52.6       1.29         100,175  204991286
   
      
  Elapsed                Elap per            CPU                        Old
  Time (s)   Executions  Exec (s)  %Total   Time (s)  Physical Reads Hash Value
---------- ------------ ---------- ------ ---------- --------------- ----------
      1.29            1       1.29   70.2       0.31           1,403  204991286
      
      
                                                     CPU      Elapsd     Old
  Buffer Gets    Executions  Gets per Exec  %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
        100,175            1      100,175.0   95.5     0.31      1.29  204991286
        
        
                                                     CPU      Elapsd
  Physical Rds   Executions  Rds per Exec   %Total Time (s)  Time (s) Hash Value
--------------- ------------ -------------- ------ -------- --------- ----------
          1,403            1        1,403.0   98.4     0.31      1.29  204991286
          

                                                CPU per    Elap per     Old
 Executions   Rows Processed   Rows per Exec    Exec (s)   Exec (s)  Hash Value
------------ --------------- ---------------- ----------- ---------- ----------
           1               1              1.0       0.31        1.29   204991286
          
           
                           % Total    Old
 Parse Calls   Executions   Parses Hash Value
 ------------ ------------ -------- ----------
  1            1     1.25  204991286

 

system@ORCL>SET AUTOTRACE ON
system@ORCL>select count(Y) from
  2  (select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated)
  3  ;
  COUNT(Y)
----------
    100000


執行計劃
----------------------------------------------------------
Plan hash value: 3483305348

------------------------------------------------------------------------------
---------------
| Id  | Operation                    | Name         | Rows  | Bytes | Cost (%C
PU)| Time     |
------------------------------------------------------------------------------
---------------
|   0 | SELECT STATEMENT             |              |     1 |    76 |  1402
(1)| 00:00:17 |
|   1 |  SORT AGGREGATE              |              |     1 |    76 |
   |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| COLOCATED    |   100K|  7421K|  1402
(1)| 00:00:17 |
|   3 |    INDEX FULL SCAN           | COLOCATED_PK |   100K|       |   211
(1)| 00:00:03 |
------------------------------------------------------------------------------
---------------


統計信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       1399  consistent gets
       1399  physical reads
          0  redo size
        527  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

system@ORCL>select count(Y) from
  2  (select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized) ;
  COUNT(Y)
----------
    100000


執行計劃
----------------------------------------------------------
Plan hash value: 102678025

------------------------------------------------------------------------------
------------------
| Id  | Operation                    | Name            | Rows  | Bytes | Cost
(%CPU)| Time     |
------------------------------------------------------------------------------
------------------
|   0 | SELECT STATEMENT             |                 |     1 |    76 |   100
K  (1)| 00:20:03 |
|   1 |  SORT AGGREGATE              |                 |     1 |    76 |
      |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| DISORGANIZED    |   100K|  7421K|   100
K  (1)| 00:20:03 |
|   3 |    INDEX FULL SCAN           | DISORGANIZED_PK |   100K|       |   211
   (1)| 00:00:03 |
------------------------------------------------------------------------------
------------------


統計信息
----------------------------------------------------------
          1  recursive calls
          0  db block gets
     100124  consistent gets
          0  physical reads
          0  redo size
        527  bytes sent via SQL*Net to client
        519  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

tkprof 報告以下:

SQL ID: db146dyxc9ctw
Plan Hash: 3483305348
select count(Y) 
from
 (select /*+ INDEX(COLOCATED COLOCATED_PK) */ * from colocated) 


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.03       0.03          0       1399          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.03       0.03          0       1399          0           1

Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=1399 pr=0 pw=0 time=0 us)
 100000   TABLE ACCESS BY INDEX ROWID COLOCATED (cr=1399 pr=0 pw=0 time=40664 us cost=1402 size=7600000 card=100000)
 100000    INDEX FULL SCAN COLOCATED_PK (cr=209 pr=0 pw=0 time=13426 us cost=211 size=0 card=100000)(object id 81368)
********************************************************************************

select count(Y) from
(select /*+ INDEX(DISORGANIZED DISORGANIZED_PK) */ * from disorganized) 

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.12       0.12          0     100124          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.12       0.12          0     100124          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  SORT AGGREGATE (cr=100124 pr=0 pw=0 time=0 us)
 100000   TABLE ACCESS BY INDEX ROWID DISORGANIZED (cr=100124 pr=0 pw=0 time=140919 us cost=100177 size=7600000 card=100000)
 100000    INDEX FULL SCAN DISORGANIZED_PK (cr=209 pr=0 pw=0 time=17519 us cost=211 size=0 card=100000)(object id 81370)

在這兩種狀況下,索引都須要執行209次邏輯I/O(Row Source Operation行中的cr-209)。若是
將一致讀(consistent read)總次數減去209,只測量對錶執行的I/O次數,就會發現所獲得的數字與各個索引的聚簇因子相等。COLOCATED_PK是「有序表「的一個經典例子,DISORGANIZE_PK則是一個典型的」表次序至關隨機「的例子。如今來看看這對優化器有什麼影響。若是咱們想獲取25,000行,Oracle對兩個索引都會選擇全表掃描(經過索引獲取25%的行不是最優計劃,即便是對頗有序的表也是如此)。不過,若是隻選擇表數據的11%,就會觀察到如下結果:

select * 
from
 colocated where x between 20000 and 30000


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.01          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      668      0.00       0.02          0       1452          0       10001
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      670      0.00       0.03          0       1452          0       10001

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
  10001  TABLE ACCESS BY INDEX ROWID COLOCATED (cr=1452 pr=0 pw=0 time=27435 us cost=142 size=810162 card=10002)
  10001   INDEX RANGE SCAN COLOCATED_PK (cr=689 pr=0 pw=0 time=13544 us cost=22 size=0 card=10002)(object id 81368)

********************************************************************************
select * 
from
 disorganized where x between 20000 and 30000


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch      668      0.03       0.06          1       1855          0       10001
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total      670      0.03       0.06          1       1855          0       10001

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 5  

Rows     Row Source Operation
-------  ---------------------------------------------------
  10001  TABLE ACCESS FULL DISORGANIZED (cr=1855 pr=1 pw=0 time=25769 us cost=326 size=810162 card=10002)

這裏的表結構和索引與前面徹底同樣,但聚簇因子有所不一樣。在這種狀況下,優化器爲COLOCATED表選擇了一個索引訪問計劃,而對DISORGANIZED表選擇了一個全面掃描訪問計劃。要記住,11%並非一個閥值,它只是一個小於25%的數,並且在這裏會致使對COLOCATED表的一個索引區間掃描。

以上討論的關鍵點是,索引並不必定老是合適的訪問方法。優化器也許選擇不使用索引,並且如前面的例子所示,這種選擇可能很正確。影響優化器是否使用索引的因素有不少,包括物理數據佈局。所以,你可能會矯枉過正,力圖重建全部的表來使全部索引有一個好的聚簇因子,可是在大多數狀況下這可能只會浪費時間。只有當 你在對錶中的大量數據(所佔百分比很大)執行索引區間掃描時,這纔會產生影響。另外必須記住,對於一個表來講,通常只有一個索引能有合適的聚簇因子!表中 的行可能只以一種方式排序。在前面所示的例子中,若是Y列上還有一個索引,這個索引在COLOCATED表中可能就不能很好地聚簇,而在DISORGANIZED表中則剛好相反。若是你認爲數據物理聚簇很重要,能夠考慮使用一個IOT、B*樹聚簇,或者在連續地重建表時考慮散列聚簇。

B*樹小結 B*樹索引是到目前爲止Oracle數據庫中最經常使用的索引結構。它們是絕好的通用索引機制。在訪問時間方面提供了很大的可擴縮性,從一個11行的索引返回數據所用的時間與一個100,000行的索引結構中返回數據的時間是同樣的。 何時創建索引,在哪些列上創建索引,你的設計中必須注意這些問題。索引並不必定就意味着更快的訪問;實際上你會發現,在許多狀況下,若是Oracle使 用索引,反而會使性能降低。這實際上兩個因素的一個函數,其中一個因素是經過索引須要訪問表中多少數據(佔多大的百分比),另外一個因素是數據如何佈局。若是能徹底使用索引「回答問題「(而不用表),那麼訪問大量的行(佔很大的百分比)就是有意義的,由於這樣能夠避免讀表所帶來的額外的分散I/O。若是使用索引來訪問表,可能就要確保只處理整個表中的不多一部分(只佔很小的百分比)。 應該在應用的設計期間考慮索引的設計和實現,而不要過後纔想起來。若是對如何訪問數據作了精心的計劃和考慮,大多數狀況下就能清楚地知道須要什麼索引。

相關文章
相關標籤/搜索