mysql innodb索引原理


聚集索引(clustered index)

innodb存儲引擎表是索引組織表,表中數據按照主鍵順序存放。其聚集索引就是按照每張表的主鍵順序構造一顆B+樹,其葉子結點中存放的就是整張表的行記錄數據,這些葉子節點成為數據頁。

聚集索引的存儲並不是物理上連續的,而是邏輯上連續的,葉子結點間按照主鍵順序排序,通過雙向鏈表連接。多數情況下,查詢優化器傾向於采用聚集索引,因為聚集索引能在葉子結點直接找到數據,並且因為定義了數據的邏輯順序,能特別快的訪問針對范圍值的查詢。

聚集索引的這個特性決定了索引組織表中的數據也是索引的一部分。由於表里的數據只能按照一顆B+樹排序,因此一張表只能有一個聚簇索引。

在Innodb中,聚簇索引默認就是主鍵索引。如果沒有主鍵,則按照下列規則來建聚簇索引:

  • 沒有主鍵時,會用一個非空並且唯一的索引列做為主鍵,成為此表的聚簇索引;
  • 如果沒有這樣的索引,InnoDB會隱式定義一個主鍵來作為聚簇索引。

由於主鍵使用了聚簇索引,如果主鍵是自增id,那么對應的數據也會相鄰地存放在磁盤上,寫入性能較高。如果是uuid等字符串形式,頻繁的插入會使innodb頻繁地移動磁盤塊,寫入性能就比較低了。

B+樹(多路平衡查找樹)

我們知道了innodb引擎索引使用了B+樹結構,那么為什么不是其他類型樹結構,例如二叉樹呢?

計算機在存儲數據的時候,有最小存儲單元,這就好比人民幣流通最小單位是分一樣。文件系統的最小單元是塊,一個塊的大小是4k(這個值根據系統不同並且可設置),InnoDB存儲引擎也有自己的最小儲存單元—頁(Page),一個頁的大小是16K(這個值也是可設置的)。

文件系統中一個文件大小只有1個字節,但不得不占磁盤上4KB的空間。同理,innodb的所有數據文件的大小始終都是16384(16k)的整數倍。

12

所以在MySQL中,存放索引的一個塊節點占16k,mysql每次IO操作會利用系統的預讀能力一次加載16K。這樣,如果這一個節點只放1個索引值是非常浪費的,因為一次IO只能獲取一個索引值,所以不能使用二叉樹。

B+樹是多路查找樹,一個節點能放n個值,n = 16K / 每個索引值的大小。
例如索引字段大小1Kb,這時候每個節點能放的索引值理論上是16個,這種情況下,二叉樹一次IO只能加載一個索引值,而B+樹則能加載16個。

B+樹的路數為n+1,n是每個節點存在的值數量,例如每個節點存放16個值,那么這棵樹就是17路。

從這里也能看出,B+樹節點可存儲多個值,所以B+樹索引並不能找到一個給定鍵值的具體行。B+樹只能找到存放數據行的具體頁,然后把頁讀入到內存中,再在內存中查找指定的數據。

附:B樹和B+樹的區別在於,B+樹的非葉子結點只包含導航信息,不包含實際的值,所有的葉子結點和相連的節點使用鏈表相連,便於區間查找和遍歷。

輔助索引

也稱為非聚集索引,其葉子節點不包含行記錄的全部數據,葉子結點除了包含鍵值以外,每個葉子結點中的索引行還包含一個書簽,該書簽就是相應行的聚集索引鍵。

如下圖可以表示輔助索引和聚集索引的關系(圖片源自網絡,看大概意思即可):
21

當通過輔助索引來尋找數據時,innodb存儲引擎會通過輔助索引葉子節點獲得只想主鍵索引的主鍵,既然后再通過主鍵索引找到完整的行記錄。

例如在一棵高度為3的輔助索引樹中查找數據,那需要對這顆輔助索引樹進行3次IO找到指定主鍵,如果聚集索引樹的高度同樣為3,那么還需要對聚集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,因此一共需要6次IO訪問來得到最終的數據頁。

創建的索引,如聯合索引、唯一索引等,都屬於非聚簇索引。

聯合索引

聯合索引是指對表上的多個列進行索引。聯合索引也是一顆B+樹,不同的是聯合索引的鍵值數量不是1,而是大於等於2。

例如有user表,字段為id,age,name,現發現如下兩條sql使用頻率最多:

Select * from user where age = ? ; Select * from user where age = ? and name = ?;

這時候不需要為age和name單獨建兩個索引,只需要建如下一個聯合索引即可:

create index idx_age_name on user(age, name)

聯合索引的另一個好處已經對第二個鍵值進行了排序處理,有時候可以避免多一次的排序操作。

覆蓋索引

覆蓋索引,即從輔助索引中就可以得到查詢所需要的所有字段值,而不需要查詢聚集索引中的記錄。覆蓋索引的好處是輔助索引不包含整行記錄的所有信息,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。

例如上面有聯合索引(age,name),如果如下:

select age,name from user where age=?

就能使用覆蓋索引了。

覆蓋索引的另一個好處是對於統計問題,例如:

select count(*) from user

innodb存儲引擎並不會選擇通過查詢聚集索引來進行統計。由於user表上還有輔助索引,而輔助索引遠小於聚集索引,選擇輔助索引可以減少IO操作。

如果對於name列做了索引,那么:

select id,name from user

也會使用覆蓋索引,因為name在索引上,而id(主鍵)在其索引樹的子節點上保存。

注意事項

  • 索引只建合適的,不建多余的
因為每當增刪數據時,B+樹都要進行調整,如果建立多個索引,多個B+樹都要進行調整,而樹越多、結構越龐大,這個調整越是耗時耗資源。如果減少了這些不必要的索引,磁盤的使用率可能會大大降低。
  • 索引列的數據長度能少則少。
索引數據長度越小,每個塊中存儲的索引數量越多,一次IO獲取的值更多。
  • 匹配列前綴可用到索引 like 9999%,like %9999%、like %9999用不到索引;
  • Where 條件中in和or可以使用索引, not in 和 <>操作無法使用索引;
如果是not in或<>,面對B+樹,引擎根本不知道應該從哪個節點入手。
  • 匹配范圍值,order by 也可用到索引;
  • 多用指定列查詢,只返回自己想到的數據列,少用select *;
不需要查詢無用字段,並且不使用*可能還會命中覆蓋索引哦;
  • 聯合索引中如果不是按照索引最左列開始查找,無法使用索引;
最左匹配原則;
  • 聯合索引中精確匹配最左前列並范圍匹配另外一列可以用到索引;
  • 聯合索引中如果查詢中有某個列的范圍查詢,則其右邊的所有列都無法使用索
  • 查詢優化器不一定會使用索引
在執行SQL時,查詢優化器會判斷使用索引的效率問題,如果優化器認為使用索引還不如全表掃描高效,那么就會棄用索引而使用全表掃描。
  • 必須使用UTF8MB4字符集
萬國碼,無需轉碼,無亂碼風險,曾經線上Oracle曾經因為生僻字改字符集UTF-8,煩的一筆。
  • 禁止使用存儲過程、視圖、觸發器、Event
解放數據庫CPU,將計算轉移到服務層,並發量大時這些功能很可能將數據庫拖死,業務邏輯放到服務層具備更好的擴展性,能夠輕易實現“增機器就加性能”。數據庫擅長存儲與索引,CPU計算還是上移吧
  • 禁止存儲大文件或者大照片
  • 禁止使用外鍵,如果有外鍵完整性約束,需要應用程序控制
外鍵會導致表與表之間耦合,update與delete操作都會涉及相關聯的表,十分影響sql 的性能,甚至會造成死鎖。高並發情況下容易造成數據庫性能
  • 必須把字段定義為NOT NULL並且提供默認值
a)null的列使索引/索引統計/值比較都更加復雜,對MySQL來說更難優化 b)null 這種類型MySQL內部需要進行特殊處理,增加數據庫處理記錄的復雜性;同等條件下,表中有較多空字段的時候,數據庫的處理性能會降低很多 c)null值需要更多的存儲空,無論是表還是索引中每行中的null的列都需要額外的空間來標識 d)對null 的處理時候,只能采用is null或is not null,而不能采用=、in、<、<>、!=、not in這些操作符號。如:where name!=’shenjian’,如果存在name為null值的記錄,查詢結果就不會包含name為null值的記錄
  • 禁止使用TEXT、BLOB類型
會浪費更多的磁盤和內存空間,非必要的大量的大字段查詢會淘汰掉熱數據,導致內存命中率急劇降低,影響數據庫性能
  • 禁止使用ENUM,可使用TINYINT代替
a)增加新的ENUM值要做DDL操作
b)ENUM的內部實際存儲就是整數,你以為自己定義的是字符串?
  • 禁止在更新十分頻繁、區分度不高的屬性上建立索引
a)更新會變更B+樹,更新頻繁的字段建立索引會大大降低數據庫性能
b)“性別”這種區分度不大的屬性,建立索引是沒有什么意義的,不能有效過濾數據,性能與全表掃描類似
  • 建立組合索引,必須把區分度高的字段放在前面
能夠更加有效的過濾數據
  • 禁止使用SELECT *,只獲取必要的字段,需要顯示說明列屬性
讀取不需要的列會增加CPU、IO消耗;不能有效的利用覆蓋索引
  • 禁止使用屬性隱式轉換
SELECT uid FROM t_user WHERE phone=13812345678 會導致全表掃描,而不能命中phone索引
  • 禁止在WHERE條件的屬性上使用函數或者表達式
SELECT uid FROM t_user WHERE from_unixtime(day)>='2017-02-15' 會導致全表掃描 正確的寫法是:SELECT uid FROM t_user WHERE day>= unix_timestamp('2017-02-15 00:00:00')
  • 禁止負向查詢,以及%開頭的模糊查詢
a)負向查詢條件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,會導致全表掃描 b)%開頭的模糊查詢,會導致全表掃描
  • 禁止大表使用JOIN查詢,禁止大表使用子查詢
會產生臨時表,消耗較多內存與CPU,極大影響數據庫性能。這個視業務而定
  • 禁止使用OR條件,必須改為IN查詢
舊版本Mysql的OR查詢是不能命中索引的,即使能命中索引,為何要讓數據庫耗費更多的CPU幫助實施查詢優化呢?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM