做者:陳浩 前端開發部 iOS 開發工程師html
在平常開發中,數據庫下降了咱們操做數據的門檻,咱們只要編寫特定的 SQL,就能對數據作增刪改查操做。在簡化的背後,每每都隱藏着性能優化的福利,數據庫也是如此,咱們知道假如沒有索引,查詢數據就會全表掃描,而索引就如書的目錄通常,大大提升了查詢效率。本文將對數據庫索引進行介紹,認識索引的數據結構,同時也介紹索引的其餘概念。前端
索引在本質上是爲了優化查找的速度,對於給定的數據,咱們可使用順序查找,若是數據已經排好序,咱們可使用二分查找,若是查找的數據量不大,咱們能夠構造二叉查找樹將查找放在內存中,而索引的數據結構是由平衡二叉樹演化而來,在正式介紹索引的數據結構以前,讓咱們先來看看二叉查找樹。mysql
二叉查找樹要求左子樹的鍵值老是小於根的鍵值,右子樹的鍵值老是大於根的鍵值。算法
二叉查找樹的問題是假如單支過長就會大大影響其查找效率,甚至退化成順序查找sql
爲了提升二叉樹的查找效率,須要構造的這棵二叉樹是平衡的——平衡二叉樹要求任何結點的兩個子樹的高度最大差爲1。平衡二叉樹一般須要左旋、右旋來達到平衡。數據庫
咱們先來看看對 B 樹的描述:性能優化
對於查找而言,B 樹的查找相似二叉樹,由於每一個結點內的關鍵字都是排序好的 key[1...n],咱們可運用二分查找將查找關鍵字 k 與 key[i] 比較,從而找出相應區間的子樹。數據結構
B 樹查找的簡化代碼:性能
Result BTreeSearch(BTNode *t, KeyType k) {
BTNode *p = t; *q = NULL; // q 指向 p 的雙親
int found = 0, i = 0;
while (p != NULL && found == 0) {
i = BinarySearch(p, k);
if (i > 0 && p->key[i] == k) {
found = 1;
} else {
q = p;
p = p->ptr[i];
}
}
...
}
複製代碼
上述代碼也可使用遞歸。有了這些基本的認識後,不難發現 B 樹的查找效率與樹的高度有關,高度越小,查找的次數就越少。學習
接下來看看 B 樹的插入和刪除。 對插入而言,若是該結點還有空位置,直接插入,不然,會將結點分紅兩部分,中間位置的關鍵字插入到父結點中,若是父結點也不知足,再往上插,直到這個過程傳到根結點。
插入15
刪除比插入稍微複雜一點,若是刪除一個關鍵字後,結點的關鍵字個數沒有少於它的裝填因子,則直接刪除
刪除8,16
不然分兩種狀況:
刪除15
刪除4後的結果
由上可知,B 樹的插入和刪除都是須要代價的,因此咱們對數據庫索引的創建也須要特別謹慎,不然不合理的索引反而下降了效率。
B+ 樹是 B- 樹的變形,經常使用於索引結構中,它與 B- 樹的主要差別有:
B 樹和平衡二叉樹的一個重要區別是結點的大小及其形成的樹的高度不一樣,B+ 樹的結點大小通常是一個磁盤塊的大小,也就是數據頁的大小,所以 B 樹矮而胖,二叉樹高而瘦。前面已經提到 B 樹的查找效率和其高度有關,假設當前數據表的數據爲 N,每一個磁盤塊的數據項的數量是 m,則有 h = ㏒(m+1)N,而 m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小又是固定的,故數據項的大小越小,樹的高度也就越小。這就是爲何要求索引字段儘量小的緣由。同理,將數據不存儲在分支結點,也是爲了儘量多的存放數據項。
B+ 樹索引就是 B+ 樹在數據庫中的實現。B+ 索引在數據庫中有一個特色是高扇出性,所以在數據庫中,B+ 樹的高度通常都在2~4層,這也就是說查找某一鍵值的行記錄時最多隻須要2到4次 IO。
B+ 樹索引並不能找到一個給定鍵值的具體行。B+樹索引能找到的只是被查找數據行所在的頁。而後數據庫經過把頁讀入到內存,再在內存中進行查找,最後獲得要查找的數據。
B+ 樹索引可分爲彙集索引和輔助索引(secondary index),它們的主要區別是彙集索引要求以惟一的 key(通常是主鍵)來構造索引,文件中記錄的物理存儲順序和索引順序一致,因爲實際的數據頁只能按照一棵 B+ 樹進行排序,所以每張表只能擁有一個彙集索引,輔助索引的 key 能夠不是惟一的,輔助索引能提升彙集索引之外 key 的查找性能,這也會增長必定的開銷。
下面表有三個列,分別是 id(主鍵)、name 和 salary,咱們來看看彙集索引和輔助索引的原理:
以上是彙集索引
以上是輔助索引
咱們已經介紹過了在單個列上使用索引,聯合索引是指對錶上的多個列進行索引,聯合索引的本質也是一棵 B+ 樹,下面看看聯合索引的內部結構:
對上圖而言,(1,1)、(1,2)、(2,1)、(2,4)、(3,1)、(3,2),數據按(a,b)的順序進行了存放,第一列是升序排序的,第二列是根據第一列排序而排序的。
所以,對於查詢 SELECT*FROM TABLE WHERE a=xxx and b=xxx
,顯然是可使用(a,b)這個聯合索引的。對於單個的a列查詢 SELECT*FROM TABLE WHERE a=xxx
,也可使用這個(a,b)索引。但對於b列的查詢 SELECT*FROM TABLE WHERE b=xxx
,則不可使用這棵B+樹索引。能夠發現葉子節點上的b值爲一、二、一、四、一、2,顯然不是排序的,所以對於b列的查詢使用不到(a,b)的索引。
聯合索引能在索引到第一個鍵值後對第二個鍵值進行排序。例如,查詢某個用戶的購物狀況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引能夠避免多一次的排序操做,由於索引到某個用戶 id 後,購買記錄已是有序的了。
正如前面所介紹的那樣,聯合索引(a,b)實際上是根據列a、b進行排序,所以語句 SELECT...FROM TABLE WHERE a=xxx ORDER BY b
能夠直接使用聯合索引獲得結果。
然而對於聯合索引(a,b,c)來講,語句 SELECT...FROM TABLE WHERE a=xxx AND b=xxx ORDER BY c
或 SELECT...FROM TABLE WHERE a=xxx ORDER BY b
一樣能夠直接經過聯合索引獲得結果。
但對於語句 SELECT...FROM TABLE WHERE a=xxx ORDER BY c
,聯合索引不能直接獲得結果,由於 c 是用不到索引的。
這就是索引最左前綴匹配的特性。根據該原則,咱們創建聯合索引時要考慮好查詢儘量地用得上索引,這也要求咱們儘量選擇區分度高的列做爲索引。
最後再簡單介紹下另外兩種索引結構
哈希索引經過哈希算法來實現查找,其衝突解決採用鏈地址法,咱們知道哈希算法的時間複雜度爲 O(1),因此哈希索引是很是高效的。
由於哈希索引的記錄不以任何特定方式排序,這也致使哈希索引沒法應用在範圍查找中。
位圖索引(bitmap index)是爲多個列查詢設計的特殊索引,位圖索引適合用於列上的值大量重複出現。
表結構:
ID | gender | income_level |
---|---|---|
43123 | m | L1 |
65654 | f | L2 |
76534 | f | L1 |
12343 | m | L4 |
65765 | f | L3 |
gender 的位圖:
m | 10010 |
f | 01101 |
income_level 的位圖:
L1 | 10100 |
L2 | 01000 |
L3 | 00001 |
L4 | 00010 |
L5 | 00000 |
上述表對於只以性別爲條件的查詢,位圖索引並不能帶來什麼性能的提高。然而對查詢 Select * from t where gender = 'f' and income_level = 'L3'
,位圖索引會執行兩個位圖的交操做(邏輯與)。即 gender 的位圖 = f(01101) 和 income_level 的位圖 = L2(01000) 的交獲得位圖 01000。顯然對於多個列上大量重複數據項的查詢,位圖索引能夠提升查找效率。此外,位圖索引還有體積小的優勢。
本文是我學習數據庫索引的筆記,僅僅介紹了數據庫的幾種索引的原理,並無深刻到更加底層的研究,只能對平常開發中如何創建索引、選擇索引發到必定的指示做用,而對於查詢性能的優化仍是須要從大量的實踐中總結出經驗。