大白話mysql之深刻淺出索引原理 - 上

當咱們使用漢語字典查找某個字時,咱們會先經過拼音目錄查到那個字所在的頁碼,而後直接翻到字典的那一頁,找到咱們要查的字,經過拼音目錄查找比咱們拿起字典從頭一頁一頁翻找要快的多,數據庫索引也同樣,索引就像書的目錄,經過索引能極大提升數據查詢的效率。mysql

索引的實現方式

在數據庫中,常見的索引實現方式有哈希表、有序數組、搜索樹。sql

哈希表

哈希表是經過鍵值對(key-value)存儲數據的索引實現方式,能夠將哈希表想象成是一個數組,將索引經過哈希函數計算獲得該行數據在數組中的位置,而後將數據存到數組中,容易發現一個問題,若是兩個索引經過哈希函數計算後獲得的數組位置相同要怎麼辦?咱們能夠採用哈希鏈表,數組的每一個 value 都是一個鏈表,新數據直接添加到鏈表尾部。
image.png數據庫

因此數據庫查詢過程爲:索引經過哈希函數計算數據所在位置 --> 遍歷指定位置的鏈表,找到知足條件的數據。數組

每次有新數據加入時,新數據時直接添加到鏈表尾部,因此添加數據時很方便。數據結構

哈希表不擅長進行區間查詢,通常都用於等值查詢,由於兩個相鄰索引經過 hash 函數後計算獲得的數組位置不必定還保持相鄰,須要哈希屢次才能把區間的數據全查出來。函數

有序數組

顧名思義,有序數組是按索引大小將數據保存在一個數組上,由於該數組是有序的,能夠經過二分法很容易查到位置,找到第一個位置後,經過向左或者向右遍歷很容易獲得所求區間的數據。所以,不管是等值查詢仍是區間查詢,效率都極高。性能

但缺陷也是顯而易見的,當向數組中間 n 位置插入一條數據時,需將 n 後面的數據所有日後移動,因此,這種索引通常用於靜態存儲引擎。spa

搜索樹

二叉搜索樹:一棵空樹,或者是具備下列性質的二叉樹:若它的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值;若它的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值;二叉搜索樹的左、右子樹也分別爲二叉搜索樹。指針

平衡二叉樹:平衡二叉樹是在二叉搜索樹的基礎上引入的,指的是結點的左子樹和右子樹的深度差不超過 1。code

多叉樹:每一個結點能夠有多個子結點,子節點的大小從左到右依次遞增。

數據庫通常使用平衡樹來當索引的存儲數據結構,當使用平衡二叉實現索引時,結構以下圖。
image.png

從圖中可發現,每次查詢最多須要訪問 4 個節點必能獲得所要數據。例如查詢 user2 時,查詢過程爲:userA-->userC-->userF-->user2。因此查詢速度很高,複雜度爲 O (log (n));平衡二叉樹的更新複雜度也爲 O (log (n))。

區間查詢時,因爲搜索樹的特性(左子樹小於右子樹),能夠很快的排除掉不知足條件的節點,查起來速度也是很快的。

思考下爲何用平衡搜索樹呢?

由於普通的二叉樹可能由於插入的數據最後變成一個很長的鏈表,查詢複雜度退化成 O (n)。

若是搜索樹存於內存中,與多叉樹相比,二叉樹的搜索速率是最高的,但實際上數據庫使用的是 n 叉樹而不是二叉樹。

  • 索引不只存於內存,仍是寫到磁盤上,搜索樹上的每一個結點在磁盤上表現爲一個數據塊。
  • 多叉樹每一個結點下能夠有多個子節點,因此存儲相同數據量時多叉樹的樹高比二叉樹小,查詢一個數據須要訪問的結點數更少,即查詢過程訪問更少的數據塊。查詢速度較高。

在 mysql 的 innodb 引擎中,使用 B + 樹來存儲數據,B + 樹是一種多叉平衡查找樹。

innodb 的索引模型

在 B + 樹中,咱們將節點分爲葉子結點和非葉子結點,非葉子結點上保存的是索引,並且一個節點能夠保存多個索引;數據所有存於葉子結點上,而且葉子結點之間經過指針鏈接起來。

根據葉子結點的內容不一樣,innodb 索引分爲主鍵索引和非主鍵索引。非主鍵索引也稱爲二級索引。主鍵索引的葉子結點中保存的數據爲整行數據,而非主鍵索引葉子節點保存的是主鍵的值。
image.png
image.png

經過主鍵索引查詢數據時,咱們只需查找主鍵索引樹即可以獲取數據;經過非主鍵索引查詢數據時,咱們先經過非主鍵索引樹查找到主鍵值,而後再在主鍵索引樹搜索一次,這個過程稱爲回表,也就是說非主鍵索引查詢會比主鍵查詢多搜索一棵樹。因此咱們應儘量使用主鍵查詢。

B + 樹是一顆 N 叉樹,N 是由什麼決定的?可否調整?

  • 經過修改 page 的大小來間接調整 N 的大小。一個節點上的全部數據都在一個 page 中,頁越大,每頁存放的索引就越多,N 就越大。數據頁調整後,若是數據頁過小層數會太深,數據頁太大,加載到內存的時間和單個數據頁查詢時間會提升,須要達到平衡才行。
  • 修改索引的大小。每一個索引包括固定字節數的 Point 指針和索引字段內容,索引字段越小,每頁能存的索引就越多,N 就越大。

索引維護

添加新行時,將會在索引表上添加一條記錄,若是是索引遞增插入時,數據都是追加在當前最大索引以後,不會對樹中其餘數據形成影響;若是新加入的數據的索引值位於節點的中間,須要挪動部分節點的位置,從而保持索引樹的有序性。

並且,相鄰多個節點是存儲在同一個數據頁上的,此時,若是是在已經存儲滿狀態的數據頁中插入節點,會申請新的數據頁,將部分數據挪動到新的數據頁,這個過程稱爲頁分裂,頁分裂除了會影響性能,還會下降磁盤空間利用率。不規則數據插入時,會形成頻繁的頁分裂。因此,通常狀況下會採用遞增主鍵,使新數據遞增插入。

當相鄰兩個頁因爲刪除了數據,利用率很低以後,會將數據頁作合併。

什麼狀況下應該使用業務邏輯字段作主鍵?有什麼優缺點?

  • 業務邏輯字段不容易保證索引樹結點有序插入,這樣寫入成本較高。
  • innodb 默認使用整數類型做爲主鍵,主鍵長度較小,二級索引的葉子結點中保存的是主鍵值,主鍵長度越小,二級索引的葉子結點佔用空間也就越小。
  • 固然,使用業務邏輯字段作主鍵也有好處,能夠避免回表,每次只需掃描一次主鍵索引樹便可。

綜上,從性能和存儲空間方面考量,自增主鍵每每是更合理的選擇,可是當業務場景有且只有一個索引,並且該索引爲惟一索引時,此時更適合使用業務邏輯字段做爲主鍵,一個是避免回表,還有一個是隻有一個索引也不須要考慮二級索引的空間佔用狀況了。

索引重建

由於數據修改、刪除、頁分裂等緣由,會致使數據頁空間利用率下降,此時,能夠考慮重建索引,將數據按順序插入,提升磁盤空間利用率。

重建普通索引時,直接先刪除索引,再從新建立便可。

alter table T drop index k; 
alter table T add index(k);

主鍵索引不能經過上面的語句去重建,由於刪除主鍵索引後,innodb 會以下處理:

  • 若是存在非空且字段類型爲數值的惟一索引(INT and NOT NULL and UNIQUE INDEX), 會將第一個知足條件的索引做爲主鍵索引 , _rowid 爲對應主鍵,值與惟一索引相同。(可經過 select _rowid from table 查詢)。
  • 若是找不到合適的索引,那麼 InnoDB 會自動生成一個不可見的名爲 ROW_ID 的列名爲 GEN_CLUST_INDEX 的主鍵索引,該列是一個 6 字節的自增數值,隨着插入而自增。

因此刪除主鍵索引的結果實際上是修改了主鍵字段,而普通索引的葉子節點存的是主鍵的值,因此,一旦修改了主鍵字段,普通索引也會有影響,葉子節點的值將被修改爲新的主鍵字段。

當主鍵索引須要重建時,更好的作法是直接使用 alter table t engine=innodb 重建表。

寫在最後

喜歡本文的朋友,歡迎關注公衆號「會玩code」,專一大白話分享實用技術
image.png

相關文章
相關標籤/搜索