InnoDB索引原理
前面總結過B樹和B+樹原理
MySQL支持多種索引,包括B+樹索引(最為常用)、全文索引、哈希索引等等。
數據庫中的B+樹索引可以分為聚集索引和輔助索引,但是不管是聚集還是輔助的索引,其內部都是B+樹,是高度平衡的,葉子結點存放着所有的數據。聚集索引和輔助索引最大的不同在於,葉子結點存放的是否是一整行的信息。
為什么不使用B樹
因為B+樹的分支結點並不會存儲關鍵字的具體信息,只存儲關鍵字索引,所以相較於B樹也較小,因此一次I/O操作所能夠容納的關鍵字索引就多一些,那么讀取一個結點的I/O操作次數也就少一些;
B+樹的所有關鍵字的具體信息都存儲在葉子結點,通常都會使用鏈表將葉子結點連接起來,遍歷葉子結點就能夠獲取到所有的數據,也就可以進行區間查詢,而B樹只有中序遍歷才能夠獲取到所有的數據。
B+樹
B+樹是為磁盤或者其它直接存取輔助設備設計的一種平衡樹,在B+樹中,所有記錄結點都是按照鍵值的大小順序存放在同一層的葉子結點上,由各葉子結點指針進行連接。
舉個例子,上面為index page、下面為leaf page,如下圖:
插入
插入分為三種情況,但都必須保證插入后葉子結點中的紀錄依然排序。
還是根據上面的那副圖作為例子,往里面加入28,我們索引到具體的位置,在25-30中間,我們發現當前的情況為葉子結點不為空,父親結點也不為空,查詢表格發現我們可以直接將紀錄插入到葉子結點中,所以如下:
接着往里面插入70,我們發現在葉子結點已經滿了,但是父親結點並沒有滿,所以需要將葉子結點進行分裂,因為70在最右邊,所以將中間的數字60向上移動,放到原來父親結點中75的位置,而75則向后移動一位,得到如下圖:
注意:因為圖片大小關系,圖中的葉子結點中的雙鏈表沒有顯示,但是實際是應該顯示的。
最后插入95,根據圖片索引到其插入位置為葉子結點中90的后面,此時我們發現葉子結點和父親結點都已經滿了,於是它們兩個都需要進行分裂,首先將葉子結點中的中間位置85向上移動,並且同時分裂成兩個葉子結點(左邊比85小,右邊比85大),此時父親結點也放不下85,於是將中間位置的60向上移動單獨成為一個結點,左子結點中的值比60小,右子結點的值比60大,具體如下圖:
旋轉
上面的操作為了保持平衡而進行大量的拆分頁操作,因為B+樹主要用於磁盤,而拆分則意味着磁盤的操作,所以要在盡可能的情況下減少拆分的動作。B+樹也提供了旋轉的功能:在當前葉子結點滿了,但是兄弟結點沒有滿的情況下,可以將部分結點移動到兄弟結點中,並且調整父親結點。
比如在前面提到的加入70(將50移動到了左邊的結點,然后調整父親結點擴大到第一個結點的紀錄值,即55),其圖可以為:
可以看到,采用旋轉操作使得B+樹減少了一次頁的拆分動作,而且還保持了高度為2。
刪除
B+樹使用填充因子來控制樹的刪除變化,填充因子最小可設為50%,同樣的,B+樹的刪除操作需要保證刪除后葉子結點中的紀錄依然排序,刪除分為好幾種情況,如下:
我們就繼續在插入的樹的基礎上進行刪除操作。刪除70后,發現葉子結點和其父親結點的值的個數並沒有小於一半,所以不需要進行其它的操作。
接下來刪除25,因為同樣是刪除表格中的第一種操作,所以直接刪除即可,但是需要注意的是,其父親結點仍然存在25,所以需要將最左邊的28更新到父親結點中即可,如圖下:
接着刪除60,我們發現刪除60后,該葉子結點已經小於填充因子,因此這種情況屬於表格中的第三種情況,則需要合並兄弟結點,因為剩余的數字小於75,所以要合並到左邊的兄弟結點,同時把根結點的60刪除掉,合並它的兩個子結點,如圖下:
B+樹索引
數據庫中的B+樹索引可以分為聚集索引和輔助索引**,但是不管是聚集索引還是輔助索引,其內部都是B+樹的,即高度平衡的,葉子結點存放着所有的數據,它們的不同點在於,葉子結點存放的是否是真實的數據行信息。
聚集索引
InnoDB存儲引擎表是索引組織表,即表中數據按照主鍵順序存放,聚集索引就是按照每張表的主鍵構造一顆B+樹,同時葉子結點存放的即為整張表的行紀錄數據(聚集索引的葉子結點也稱為數據頁)。聚集索引的這個特性也決定了索引組織表中數據也是索引的一部分,和B+樹數據結構一樣,每個數據頁之間都通過一個雙向鏈表來進行鏈接。
一次查詢操作將包含3個索引頁的讀取
聚集索引的另外一個好處是,它對於主鍵的排序查找和范圍查找速度非常快。葉子結點的數據就是用戶所要查詢的數據。另外一個是范圍查詢,即如果要查找住主鍵某一個范圍內的數據,通過葉子結點的上層中間結點就可以得到頁的范圍,之后直接讀取數據頁即可。
輔助索引(非聚集索引)
對於輔助索引,葉子結點並不包含行記錄的全部數據,葉子結點除了包含鍵值以外,每個葉子結點中的索引行還包含一個書簽,該書簽用來告訴存儲引擎哪里可以找到與索引相對應的行數據。由於Innodb存儲引擎表是索引組織表,因此Innodb存儲引擎的輔助索引的書簽就是相應行數據的聚集索引鍵;
當通過索引數據來查找數據的時候,存儲引擎會遍歷輔助索引並且通過葉級別的指針獲取到指向主鍵索引的的主鍵,然后再通過主鍵索引來找到一個完整的記錄;
非聚集索引與聚集索引相比:
葉子結點並非數據結點;
葉子結點為每一真正的數據行存儲一個“鍵-指針”對;
葉子結點中還存儲了一個指針偏移量,根據頁指針及指針偏移量可以定位到具體的數據行;
類似的,在除葉結點外的其它索引結點,存儲的也是類似的內容,只不過它是指向下一級的索引頁的;
表數據存儲順序與索引順序無關。而聚集索引表中各行的物理順序與鍵值的邏輯(索引)順序相同;
每張表可以有多個非聚集索引,而聚集索引只能有一個;
一次查詢操作將包含3個索引頁的讀取+1個數據頁的讀取
建索引的幾大規則
最左前綴匹配原則,數據庫會一直向右匹配直到遇到范圍查詢就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的;
=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序;
盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,所以最好別對這些字段創建索引;
索引列不能參與計算,保持列“干凈”,因為B+樹中存的都是數據表中的字段值,但進行檢索的時候,需要把所有元素都應用函數才能比較,顯然成本太大;
盡量的擴展索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那么只需要修改原來的索引即可;
索引要建立在查詢頻繁的字段上。這是因為,如果這些列很少用到,那么有無索引並不能明顯改變查詢速度。相反,由於增加了索引,反而降低了系統的維護速度和增大了空間需求。
索引優化
應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描; 例子:select id from t where num is null
應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描;
應盡量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描;(可以使用union來代替or)
in 和 not in 也要慎用,因為in會使系統無法使用索引,而只能直接搜索表中的數據,對於連續的數值,能用 between 就不要用 in;
盡量避免在索引過的字符數據中,使用非打頭字母搜索,這也使得引擎無法利用索引; 比如:SELECT * FROM T1 WHERE NAME LIKE ‘%L%’
應盡量避免在 where 子句中對字段進行表達式操作或者函數操作,都會導致引擎放棄使用索引而進行全表掃描; 比如:SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=’L’
應用
聯合索引
聯合索引是指對表上的多個列進行索引,加快搜索的效率。
覆蓋索引
從輔助索引中就可以得到查詢的紀錄,避免了去查詢聚集索引中的紀錄所造成的消耗。