1. 什麼是索引和建立索引的好處
a) 什麼是索引
在數據庫中,索引的含義與日常意義上的“索引”一詞並無多大區別,與書中的索引一樣,數據庫中的索引使您可以快速找到表中的特定信息。索引包含從表中一個或多個列生成的鍵,以及映射到指定數據的存儲位置的指針,也就是說索引由鍵 和 指針組成。它是用於提高數據庫表數據訪問速度的數據庫對象。
b)建立索引的好處:
1. 索引可以避免全表掃描。多數查詢可以僅掃描少量索引頁及數據頁,而不是遍歷所有數據頁。
2. 對於非聚集索引,有些查詢甚至可以不訪問數據頁。如字典的目錄就可以查到所有拼音第一字母爲z 的所有字。
3. 聚集索引可以避免數據插入操作集中於表的最後一個數據頁。
4. 一些情況下,索引還可用於避免排序操作。
c) 索引的存儲
索引包含由表中的一列或多列生成的鍵。這些鍵存儲在一個結構(B 樹)中,不同於二叉樹。同一個分支下有一個或多個子節點。
B樹的簡單結構:
(從圖可見,當我們插入關鍵字4時,由於原結點已經滿了,故進行分裂,基本按一半的原則進行分裂,然後取出中間的關鍵字2,升級(這裏是成爲根結點)。其它的依類推,就是這樣一個大概的過程。)
一條索引記錄中包含的基本信息包括:鍵值 + 邏輯指針。
2. 什麼是聚集索引
2.1 聚集索引定義
聚集索引是根據數據行的鍵值在表中排序存儲數據行。索引定義中包含聚集索引列。每個表只能有一個聚集索引。只有當表包含聚集索引時,表中的數據行才按排序順序存儲。如果表具有聚集索引,則該表稱爲聚集表。如果表沒有聚集索引,則其數據行存儲在一個稱爲堆的無序結構中。
2.2 聚集索引的結構
對於某個聚集索引,索引指向該聚集索引某個特定分區(數據頁)的頂部。SQL Server 將在索引中向下移動以查找與某個聚集索引鍵對應的行。原因是聚集索引的索引順序就是數據排列順序。
1.1 聚集索引與查詢操作
如上圖,在建立聚集索引後,當需要在根據此字段查找特定的記錄時,數據庫系統會根據特定的系統表查找的此索引的根,然後根據指針查找下一個,直到找到。數據查詢時首先是對索引表查詢,如果此時索引表在緩存中可以找到,則可以避免一次IO操作。在索引表中找到所需數據索引值後,就可以確定目標數據行所在的數據位置,從而讀取數據。
1.2 聚集索引與插入和刪除操作
插入數據時,首先根據索引找到對應的數據頁,然後通過挪動已有的記錄爲新數據騰出空間,最後插入數據。
刪除數據時將導致其下方的數據行向上移動以填充刪除記錄造成的空白。
對於數據的刪除操作,可能導致索引頁中僅有一條記錄,這時,該記錄可能會被移至鄰近的索引頁中,原索引頁將被回收,即所謂的“索引合併”。同樣插入數據頁會更改索引。每一次索引更改都是一次IO操作。
聚集索引的建立會降低數據插入和刪除的效率。
2. 什麼是非聚集索引
2.1 非聚集索引定義
非聚集索引並不是在物理上排列數據,即索引中的邏輯順序並不等同於表中行的物理順序,索引是指向表中行的位置的指針,這些指針本身是有序的,通過這些指針可以在表中快速定位數據。
2.2 非聚集索引的結構
由於非聚集索引數據存儲時無序的,所以在非聚集索引中指針包含數據行在數據頁中的偏移量。即指針由 數據頁 + 數據行偏移量 組成。
1.1 非聚集索引的查詢
如上圖,在建立非聚集索引後,當需要在根據此字段查找特定的記錄時,數據庫系統會根據特定的系統表查找的此索引的根,然後根據指針查找,直到找到。數據查詢時首先是對索引表查詢,如果此時索引表在緩存中可以找到,則可以避免一次IO操作。在索引表中找到所需數據索引值後,就可以確定目標數據行所在的數據位置,從而讀取數據。
1.2 非聚集索引的插入刪除
如果一張表包含一個非聚集索引但沒有聚集索引,則新的數據將被插入到最末一個數據頁中,然後非聚集索引將被更新。如果也包含聚集索引,該聚集索引將被用於查找新行將要處於什麼位置,隨後,聚集索引、以及非聚集索引將被更新。
如果在刪除命令的Where子句中包含的列上,建有非聚集索引,那麼該非聚集索引將被用於查找數據行的位置,數據刪除之後,位於索引葉子上的對應記錄也將被刪除。如果該表上有其它非聚集索引,則它們葉子結點上的相應數據也要刪除。
4. 聚集索引和非聚集的區別
聚集索引和非聚集索引的根本區別是數據記錄的排列順序和索引的排列順序是否一致,聚集索引表記錄的排列順序與索引的排列順序一致,優點是查詢速度快,因爲一旦具有第一個索引值的紀錄被找到,具有連續索引值的記錄也一定物理的緊跟其後,從而縮小了搜索範圍,對於返回某一範圍的數據效果最好。
聚集索引的缺點是對錶進行修改速度較慢,這是爲了保持表中的記錄的物理順序與索引的順序一致,而把記錄插入到數據頁的相應位置,必須在數據頁中進行數據重排,降低了執行速度。
非聚集索引指定了表中記錄的邏輯順序,數據記錄的物理順序和索引的順序不一致,聚集索引和非聚集索引都採用了B樹的結構,但非聚集索引的葉子層順序並不與實際的數據頁相同,而採用指向表中的記錄在數據頁中位置的方式。非聚集索引比聚集索引層次多,添加記錄不會引起數據順序的重組。在有大量不同數據的列上建立非聚集索引,可以提高數據的查詢和修改速度。
在對聚集索引列查詢時,聚集索引的速度要比非聚集索引速度快。
在對聚集索引列排序時,聚集索引的速度要比非聚集索引速度快。但是如果數據量比較大時,如10萬以上,則二者的速度差別不明顯。
5. 聚集索引和非聚集的建立原則
在創建索引時要做到三個適當,即在適當的表上、適當的列上創建適當數量的索引。雖然這可以通過一句話來概括優化的索引的基本準則,但是要做到這一點的話,需要做出很大的努力。具體的來說,要做到這個三個適當有如下幾個要求。
5.1 根據表的大小來創建索引。
雖然給表創建索引,可以提高查詢的效率。但是需要注意的是,索引也需要一定的開銷的。爲此並不是說給所有的表都創建索引,那麼就可以提高數據庫的性能。這個認識是錯誤的。給所有的表都創建了索引,那麼其反而會給數據庫的性能造成負面的影響。因爲此時濫用索引的開銷可能已經遠遠大於由此帶來的性能方面的收益。所以,數據庫管理員首先需要做到,爲合適的表來建立索引,而不是爲所有的表建立索引。
一般來說,不需要爲比較小的表創建索引。因爲即使建立了索引,其性能也不會得到很大的改善。相反索引建立的開銷,如維護成本等等,要比這個要大。也就是說,付出的要比得到的多,顯然違反常理。
另外,就是對於超大的表,也不一定要建立索引。有些表雖然比較大,記錄數量非常的多。但是此時爲這個表建立索引並一定的合適。對於一些超大的表,建立索引有時候往往不能夠達到預計的效果。而且在大表上建立索引,其索引的開銷要比普通的表大的多。那麼到底是否給大表建立索引呢?主要是看兩個方面的內容。首先是需要關注一下,在這張大表中經常需要查詢的記錄數量。一般來說,如果經常需要查詢的數據不超過10%到15%的話,那就沒有必要爲其建立索引的必要。因爲此時建立索引的開銷可能要比性能的改善大的多。如果數據庫管理員需要得出一個比較精確的結論,那麼就需要進行測試分析。
5.2 根據列的特徵來創建索引
列的特點不同,索引創建的效果也不同。需要了解爲哪些列創建索引可以起到事半功倍的效果。同時也需要了解爲哪些列創建索引反而起到的是事倍功半的效果。
索引設置的是否恰當,不僅跟數據庫設計架構有關,而且還跟企業的經濟業務相關。雖然一開始已經做了索引的優化工作。但是隨着後來經濟數據的增加,這個索引的效果會越來越打折扣。所以需要隔一段時間,對數據庫的索引進行優化。該去掉的去掉,該調整的調整,以提高數據庫的性能。
5.3 在一個表上創建多少索引合適
通常來說,表的索引越多,其查詢的速度也就越快。但是,表的更新速度則會降低。這主要是因爲表的更新同時也是索引的更新。到底在表中創建多少索引合適,就需要在這個更新速度與查詢速度之間取得一個均衡點。如對於一些數據倉庫或者決策型數據庫系統,其主要用來進行查詢。相關的記錄往往是在數據庫初始化的時候導入。此時,設置的索引多一點,可以提高數據庫的查詢性能。同時因爲記錄不怎麼更新,所以索引比較多的情況下,也不會影響到更新的速度。相反,如果那些表中經常需要更新記錄,如一些事務型的應用系統,數據更新操作是家常便飯的事情。此時如果在一張表中建立過多的索引,則會影響到更新的速度。由於更新操作比較頻繁,所以對其的負面影響,要比查詢效率提升要大的多。此時就需要限制索引的數量,只在一些必要的字段上建立索引。
總之,在適當的表、適當的列上建立適當的索引。具體的索引優化內容還是需要在日常工作中繼續體會與總結。
下面的表總結了何時使用聚集索引或非聚集索引:
動作描述 |
使用聚集索引 |
使用非聚集索引 |
列經常被分組排序 |
應 |
應 |
返回某範圍內的數據 |
應 |
不應 |
一個或極少不同值 |
不應 |
不應 |
小數目的不同值 |
應 |
不應 |
大數目的不同值 |
不應 |
應 |
頻繁更新的列 |
不應 |
應 |
外鍵列 |
應 |
應 |
主鍵列 |
應 |
應 |
頻繁修改索引列 |
不應 |
應 |
6. 什麼是複合索引
6.1 複合索引定義
索引可以包含一個、兩個或更多個列。兩個或更多個列上的索引被稱作複合索引。
利用索引中的附加列,您可以縮小搜索的範圍,但使用一個具有兩列的索引不同於使用兩個單獨的索引。複合索引的結構與電話簿類似,人名由姓和名構成,電話簿首先按姓氏對進行排序,然後按名字對有相同姓氏的人進行排序。如果您知道姓,電話簿將非常有用;如果您知道姓和名,電話簿則更爲有用,但如果您只知道名不姓,電話簿將沒有用處。
所以說創建複合索引時,應該仔細考慮列的順序。對索引中的所有列執行搜索或僅對前幾列執行搜索時,複合索引非常有用;僅對後面的任意列執行搜索時,複合索引則沒有用處。
如:建立 姓名、年齡、性別的複合索引。
起作用的複合索引查詢:
6.2 複合索引的建立原則:
如果您很可能僅對一個列多次執行搜索,則該列應該是複合索引中的第一列。如果您很可能對一個兩列索引中的兩個列執行單獨的搜索,則應該創建另一個僅包含第二列的索引。
如上圖所示,如果查詢中需要對年齡和性別做查詢,則應當再新建一個包含年齡和性別的複合索引。
包含多個列的主鍵始終會自動以複合索引的形式創建索引,其列的順序是它們在表定義中出現的順序,而不是在主鍵定義中指定的順序。在考慮將來通過主鍵執行的搜索,確定哪一列應該排在最前面。
請注意,創建複合索引應當包含少數幾個列,並且這些列經常在select查詢裏使用。在複合索引裏包含太多的列不僅不會給帶來太多好處。而且由於使用相當多的內存來存儲複合索引的列的值,其後果是內存溢出和性能降低。
6.3 複合索引對排序的優化:
複合索引只對和索引中排序相同或相反的order by 語句優化。
在創建複合索引時,每一列都定義了升序或者是降序。如定義一個複合索引:
CREATE INDEX idx_example
ON table1 (col1 ASC, col2 DESC, col3 ASC)
其中 有三列分別是:col1 升序,col2 降序, col3 升序。現在如果我們執行兩個查詢
1:Select col1, col2, col3 from table1 order by col1 ASC, col2 DESC, col3 ASC 和索引順序相同
2:Select col1, col2, col3 from table1 order by col1 DESC, col2 ASC, col3 DESC 和索引順序相反
查詢1,2 都可以別複合索引優化。
如果查詢爲:
Select col1, col2, col3 from table1 order by col1 ASC, col2 ASC, col3 ASC 排序結果和索引完全不同時,此時的查詢不會被複合索引優化。
查詢優化器在在where查詢中的作用:
1. 如果一個多列索引存在於 列 Col1 和 Col2 上,
則以下語句:Select * from table where col1=val1 AND col2=val2
查詢優化器會試圖通過決定哪個索引將找到更少的行。之後用得到的索引去取值。
1. 如果存在一個多列索引,任何最左面的索引前綴能被優化器使用。所以聯合索引的順序不同,影響索引的選擇,儘量將值少的放在前面。
如:一個多列索引爲 (col1 ,col2, col3)
那麼在索引在列 (col1) 、(col1 col2) 、(col1 col2 col3) 的搜索會有作用。
SELECT * FROM tb WHERE col1 = val1
SELECT * FROM tb WHERE col1 = val1 and col2 = val2
SELECT * FROM tb WHERE col1 = val1 and col2 = val2 AND col3 = val3
2. 如果列不構成索引的最左面前綴,則建立的索引將不起作用。
如:
SELECT * FROM tb WHERE col3 = val3
SELECT * FROM tb WHERE col2 = val2
SELECT * FROM tb WHERE col2 = val2 and col3=val3
3. 如果一個 Like 語句的查詢條件不以通配符起始則使用索引。
如:%車 或 %車% 不使用索引。
車% 使用索引。
索引的缺點:
1. 佔用磁盤空間。
2. 增加了插入和刪除的操作時間。一個表擁有的索引越多,插入和刪除的速度越慢。如 要求快速錄入的系統不宜建過多索引。
7. 下面是一些常見的索引限制問題
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';