1.什么是引
索引是建立在表的一列或多個列上的輔助對象,目的是加快訪問表中的數據;Oracle存儲索引的數據結構是B*樹,位圖索引也是如此,只不過是葉子節點不同B*數索引;索引由根節點、分支節點和葉子節點組成,上級索引塊包含下級索引塊的索引數據,葉節點包含索引數據和確定行實際位置的rowid。
2.使用索引的目的
當查詢返回的記錄數排序表<40%非排序表 <7%且表的碎片較多(頻繁增加、刪除)時可以加快查詢速度減少I/O操作消除磁盤排序
3.索引的分類及結構
從物理上說,索引通常可以分為:分區和非分區索引、常規B樹索引、位圖(bitmap)索引、翻轉(reverse)索引等。其中,B樹索引屬於最常見的索引,由於我們的這篇文章主要就是對B樹索引所做的探討,因此下面只要說到索引,都是指B樹索引。
B樹索引是一個典型的樹結構,其包含的組件主要是:
1) 葉子節點(Leaf node):包含條目直接指向表里的數據行。
2) 分支節點(Branch node):包含的條目指向索引里其他的分支節點或者是葉子節點。
3) 根節點(Root node):一個B樹索引只有一個根節點,它實際就是位於樹的最頂端的分支節點。
可以用下圖一來描述B樹索引的結構。其中,B表示分支節點,而L表示葉子節點。
對於分支節點塊(包括根節點塊)來說,其所包含的索引條目都是按照順序排列的(缺省是升序排列,也可以在創建索引時指定為降序排列)。每個索引條目(也可以叫做每條記錄)都具有兩個字段。第一個字段表示當前該分支節點塊下面所鏈接的索引塊中所包含的最小鍵值;第二個字段為四個字節,表示所鏈接的索引塊的地址,該地址指向下面一個索引塊。在一個分支節點塊中所能容納的記錄行數由數據塊大小以及索引鍵值的長度決定。比如從上圖一可以看到,對於根節點塊來說,包含三條記錄,分別為(0 B1)、(500 B2)、(1000 B3),它們指向三個分支節點塊。其中的0、500和1000分別表示這三個分支節點塊所鏈接的鍵值的最小值。而B1、B2和B3則表示所指向的三個分支節點塊的地址。
對於葉子節點塊來說,其所包含的索引條目與分支節點一樣,都是按照順序排列的(缺省是升序排列,也可以在創建索引時指定為降序排列)。每個索引條目(也可以叫做每條記錄)也具有兩個字段。第一個字段表示索引的鍵值,對於單列索引來說是一個值;而對於多列索引來說則是多個值組合在一起的。第二個字段表示鍵值所對應的記錄行的ROWID,該ROWID是記錄行在表里的物理地址。如果索引是創建在非分區表上或者索引是分區表上的本地索引的話,則該ROWID占用6個字節;如果索引是創建在分區表上的全局索引的話,則該ROWID占用10個字節。
知道這些信息以后,我們可以舉個例子來說明如何估算每個索引能夠包含多少條目,以及對於表來說,所產生的索引大約多大。對於每個索引塊來說,缺省的PCTFREE為10%,也就是說最多只能使用其中的90%。同時9i以后,這90%中也不可能用盡,只能使用其中的87%左右。也就是說,8KB的數據塊中能夠實際用來存放索引數據的空間大約為6488(8192×90%×88%)個字節。
假設我們有一個非分區表,表名為warecountd,其數據行數為130萬行。該表中有一個列,列名為goodid,其類型為char(8),那么也就是說該goodid的長度為固定值:8。同時在該列上創建了一個B樹索引。
在葉子節點中,每個索引條目都會在數據塊中占一行空間。每一行用2到3個字節作為行頭,行頭用來存放標記以及鎖定類型等信息。同時,在第一個表示索引的鍵值的字段中,每一個索引列都有1個字節表示數據長度,后面則是該列具體的值。那么對於本例來說,在葉子節點中的一行所包含的數據大致如下圖二所示:
從上圖可以看到,在本例的葉子節點中,一個索引條目占18個字節。同時我們知道8KB的數據塊中真正可以用來存放索引條目的空間為6488字節,那么在本例中,一個數據塊中大約可以放360(6488/18)個索引條目。而對於我們表中的130萬條記錄來說,則需要大約3611(1300000/360)個葉子節點塊。
而對於分支節點里的一個條目(一行)來說,由於它只需保存所鏈接的其他索引塊的地址即可,而不需要保存具體的數據行在哪里,因此它所占用的空間要比葉子節點要少。分支節點的一行中所存放的所鏈接的最小鍵值所需空間與上面所描述的葉子節點相同;而存放的索引塊的地址只需要4個字節,比葉子節點中所存放的ROWID少了2個字節,少的這2個字節也就是ROWID中用來描述在數據塊中的行號所需的空間。因此,本例中在分支節點中的一行所包含的數據大致如下圖三所示:
4.怎樣建立索引
create index <index_name> on <table_name>(<column_name>) [tablespace<tablespace_name>];
1、普通索引
create index index_text_txt on test(txt);
2、唯一索引 Oracle 自動在表的主鍵上創建唯一索引
create unique index <index_name> on <index_name>(<coiumn_name>);
3、位圖索引
作用范圍及優點:
1、位圖索引適合創建在低級數列(重復的數值多,如性別)上
2、減少響應時間
3、節省空間占用
create bitmap index <index_name> on <table_name>(<column_name>)
4、組合索引
作用范圍及優點:
1、組合索引是在表的多個列上創建的索引
2、索引中的順序是任意的
3、如果SQL語言的WHERE子句中引用了組合索引的所有或大多數列,則可以提高檢索速度
create index <index_name> on <table_name>(<column_name1><column_name2>)
5、基於函數索引
create index <index_name> on <table_name>(<function_name>(<column_name>));
6、反向鍵索引
create index <index_name> on <table_name>(column_name) reverse;
7.重置索引
alter index <index_name> rebuild;
8.刪除索引
drop index <index_name>
5.索引失效細節
1.使用不等於操作符(<>, !=)
下面這種情況,即使在列dept_id有一個索引,查詢語句仍然執行一次全表掃描
select * from dept where staff_num <> 1000;
但是開發中的確需要這樣的查詢,難道沒有解決問題的辦法了嗎?
有!
通過把用 or 語法替代不等號進行查詢,就可以使用索引,以避免全表掃描:上面的語句改成下面這樣的,就可以使用索引了。
select * from dept shere staff_num < 1000 or dept_id > 1000;
2.使用 is null 或 is not null
使用 is null 或is nuo null也會限制索引的使用,因為數據庫並沒有定義null值。如果被索引的列中有很多null,就不會使用這個索引(除非索引是一個位圖索引,關於位圖索引,會在以后的blog文章里做詳細解釋)。在sql語句中使用null會造成很多麻煩。
解決這個問題的辦法就是:建表時把需要索引的列定義為非空(not null)
3..使用函數
如果沒有使用基於函數的索引,那么where子句中對存在索引的列使用函數時,會使優化器忽略掉這些索引。下面的查詢就不會使用索引:
select * from staff where trunc(birthdate) = '01-MAY-82';
但是把函數應用在條件上,索引是可以生效的,把上面的語句改成下面的語句,就可以通過索引進行查找。
select * from staff where birthdate < (to_date('01-MAY-82') + 0.9999);
4.比較不匹配的數據類型
比較不匹配的數據類型也是難於發現的性能問題之一。下面的例子中,dept_id是一個varchar2型的字段,在這個字段上有索引,但是下面的語句會執行全表掃描。
select * from dept where dept_id = 900198;
這是因為oracle會自動把where子句轉換成to_number(dept_id)=900198,就是3所說的情況,這樣就限制了索引的使用。把SQL語句改為如下形式就可以使用索引
select * from dept where dept_id = '900198';
5.使用like子句
使用like子句查詢時,數據需要把所有的記錄都遍歷來進行判斷,索引不能發揮作用,這種情況也要盡量避免。
Like 的字符串中第一個字符如果是‘%’則用不到索引
Column1 like ‘aaa%’ 是可以的
Column1 like ‘%aaa%’用不到
6.使用in
盡管In寫法要比exists簡單一些,exists一般來說性能要比In要高的多
用In還是用Exists的時機
當in的集合比較小的時候,或者用Exists無法用到選擇性高的索引的時候,用In要好,否則就要用Exists
例:select count(*) from person_info where xb in (select xb_id from dic_sex);
Select count(*) from n_acntbasic a where shbxdjm =:a and exists(select 1 from person_info where pid=a.pid and …);
Select * from person_info where zjhm=3101….;將會對person_info全表掃描
Select * from person_info where zjhm =‘3101…’才能用到索引
假定TEST表的dt字段是date類型的並且對dt建了索引。
如果要查‘20041010’一天的數據.下面的方法用不到索引
Select * from test where to_char(dt,’yyyymmdd’) =‘20041010’;
而select * from test where dt >=to_date(‘20041010’,’yyyymmdd’) and dt < to_date(‘20041010’,’yyyymmdd’) + 1 將會用到索引。
7.如果能不用到排序,則盡量避免排序
用到排序的情況有
集合操作。Union ,minus ,intersect等,注:union all 是不排序的。
Order by
Group by
Distinct
In 有時候也會用到排序
確實要排序的時候也盡量要排序小數據量
,盡量讓排序在內存中執行,有文章說,內存排序的速度是硬盤排序的1萬倍。