Mysql Innodb 索引分析

什麼是索引

數據庫索引是我們每個開發人員既熟悉又陌生的東西,幾乎所有的業務系統都要與索引打交道,如果數據庫查詢慢了,第一時間想到的也是添加一個索引試試。但是大多數人並沒有去深究這個神奇的東西究竟是如何起作用的。

其實索引就像一本書的目錄,沒有目錄的書,我們只能一頁一頁的往下翻,邊看邊找自己感興趣的內容。而有了目錄,我們想看書的哪一部分只需要翻翻目錄的介紹,找到對應的頁碼,然後就可以快速的翻到感興趣的部分了。數據庫也是同樣的道理,數據通常是保存到磁盤中的(ssd或者hdd),而磁盤的讀取速度遠遠不如內存那麼快。數據量較大的情況下,如果不能定位數據的大致位置,而採用順序掃描的方式去查找匹配的數據,那用戶每次操作後等待結果的時間大概都能打一把王者了,這個時候就需要索引出場了。

索引的原理其實很簡單,就是通過某種數據結構把要查詢的關鍵字與實際的數據存儲位置進行映射。這樣我們在搜索數據的時候,就不是直接把數據逐條從磁盤上查出來和關鍵字進行比較,而是先從索引中查找到這個關鍵字,然後得到關鍵字所映射的數據記錄的實際位置,再根據存儲位置到磁盤上去讀取相應的記錄。索引都是排好序的,而且索引的部分結構可以加載到內存中,這些都能極大的提升檢索的速度。

基於BTree的索引實現

原理上很簡單,但是要實現一個高效的數據索引並不容易。目前主流的關係型數據庫基本還是使用Btree(也叫B-Tree)的各種變形結構來實現數據索引的。顧名思義,btree也是一種樹形結構,中文名稱叫做平衡多路查找樹,結構類似於下圖:


Btree的每個節點主要包含三類信息:數據的關鍵字(key),數據本身,指向子節點的指針。key與指針交替間隔的,而且從圖上可以很直觀的看出,所有的key都是從左到右升序排列的,每個key在整個Btree中只會出現一次。

在Mysql的索引實現中,每個Btree的節點存儲到一個頁(Page)中,這是Mysql磁盤管理的最小單位,InnoDB 讀取數據時必須以Page爲單位整體讀出來放到內存中。我們以查詢關鍵字10爲例,大致的過程如下:

  1. 根據根節點找到Page1,讀入內存。【磁盤I/O操作第1次】
  2. 比較關鍵字10在區間<17,找到Page2的指針P1。
  3. 根據P1指針找到Page2,讀入內存。【磁盤I/O操作第2次】
  4. 比較關鍵字29在區間(8,12),找到Page6的指針P2。
  5. 根據P2指針找到Page6,讀入內存。【磁盤I/O操作第3次】
  6. 在Page6中的關鍵字列表中找到關鍵字10以及對應的數據。

整個過程只需要3次磁盤IO操作,3次內存操作,效率比逐條讀取比對的方式提升了很多。

B+Tree的優化

Btree能夠極大的提升數據查詢的效率,但仍然存在一些缺點,比如:數據和關鍵字都存儲到節點中,佔用的空間較大,導致單個節點能容納的Key的數量較少,每次能讀入內存的key也就越少,這樣可能會影響搜索效率;另外Btree的查詢效率頁不穩定,如果關鍵字位於上層節點,則可以很快的返回結果,但關鍵字如果在更深的節點上,則查詢的效率會大大降低。針對這些問題,人們又對Btree進行一些細節上的優化,使其能夠更加滿足數據庫索引的需求,這就是B+Tree。Mysql InnoDB的索引就是採用B+Tree實現的:


B+Tree的改進主要有3點:

  1. B+Tree的非葉子節點並不存儲數據本身,只存儲Key和子節點指針。因此單個節點可以存儲更多的Key,一次性讀入內存的關鍵字也就越多,相對IO讀寫次數就降低了。

  2. B+Tree的葉子節點包含了所有的Key和數據,而且每個葉子節點都存儲了前後兩個葉子節點的位置指針,這樣在區間查詢時可以直接從葉子節點進行遍歷,跳過上層的根節點,可以提高區間查詢的效率。

  3. B+Tree的Key可能出現在多個節點中,而Btree的Key只會出現在唯一的一個節點,由於B+Tree的數據都只存儲在葉子結點中,所以不管查詢什麼Key,最終都需要沿着根節點逐次搜索到葉子節點才能找到數據,這樣可以避免不同的Key查詢效率差異過大的問題。

聚簇索引(Clustered Index)和非聚簇索引 (Non- Clustered Index)

聚簇索引就是數據和索引本身是存儲在一起(物理上的),比如上面B+Tree的示意圖,黃色小格里面的Data就是數據本身;聚簇索引的查詢效率肯定是最高的,只要找到了數據就可以直接讀取了,不需要再進行任何跳轉。但是因爲數據只能存儲在一個位置,所以每張表的聚簇索引肯定也就只有一個。在Mysql InnoDB中,主鍵索引就是聚簇索引,不能修改(沒有主鍵的表就看第一個唯一索引,都沒有Mysql就自己生成一個),所以主鍵查詢的效率是最高的。和數據的存儲位置無關的索引都是非聚簇索引(也叫二級索引),這個時候葉子節點裏面存儲的Data實際上是數據的主鍵(而不是數據本身),所以非聚簇索引的查詢會多一個步驟,找到數據主鍵之後還要到聚簇索引中去查找實際的數據。

主鍵的選擇

根據以上對索引結構的分析,我們就能瞭解一些Mysql InnoDB數據表主鍵的一些限制:

  1. 不要用過長的字段作爲主鍵:因爲所有的二級索引的data區域都是存放到數據記錄的主鍵,如果主鍵過長會導致二級索引的空間佔用變大,影響查詢效率;
  2. 儘量使用單調自增的字段作爲主鍵:主鍵索引本質上是一棵B+Tree,key是有序排列的;如果主鍵是單調自增的,那麼產生新數據的時候,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁,如圖:

    這樣可以形成一個緊湊的存儲結構(沒有磁盤碎片),也無需移到其它已有的節點,索引的插入效率較高;反之,如果主鍵是類似於身份證號碼那樣的非自增結構,索引在插入時會產生大量額外的節點移動開銷,導致大量的磁盤碎片,而且已被緩存的索引頁可能會被強制刷新,影響插入效率。

使用索引的一些注意事項:

  1. 索引不能解決一切問題,好的索引會提高查詢效率,但同時也會增加插入數據的開銷,需要綜合考慮系統的性能瓶頸來進行設置;

  2. 如果需要索引的列是一個很長的字符列時,可以考慮使用前綴索引,前綴的長度計算需要綜合實際的數據來考慮。例如:

ALTER TABLE `city_demo` ADD KEY `idx_city` (`city`(7))
  1. SQL查詢時,如果索引列必須要是獨立的,否則無法使用索引,索引。“ 獨立的列” 是指索引列不能是表達式的一部分, 也不能是函數的參數。例如下面的情況都無法使用索引:
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;

SELECT ... WHERE TO_DAYS( CURRENT_DATE) - TO_DAYS( date_col) <= 10;
  1. 複合索引的索引列序非常重要:建立複合索引時,多個字段的排列順序是能夠影響索引是否生效的重要問題。簡單的說,複合索引是按照從左到右的順序生效的,如果查詢條件中缺少中間的部分字段,則僅左邊能夠匹配的索引部分可以生效。假設有一個複合索引(a,b,c),則有以下的規則:
select * from exp_tbl  where a=3  and b=45 and c=5 --這種三個索引順序使用中間沒有中斷,全部發揮作用;
select * from exp_tbl  where a=3  and c=5 -- 這種情況下b就是斷點,a發揮了效果,c沒有效果
select * from exp_tbl  where b=3  and c=4 -- 這種情況下a就是斷點,在a後面的索引都沒有發揮作用,這種寫法聯合索引沒有發揮任何效果;
select * from exp_tbl  where b=45 and a=3 and c=5 -- 和第一種情況是一樣的,條件書寫的順序無關
  1. 如果一個查詢中條件經常都是不固定的(比如用戶輸入條件進行查詢,這種情況很常見),就不太好設置複合索引了,還有一種方式就是把潛在的可能成爲條件的數據列都設置成單列索引。Mysql從5.1開始支持index merge技術(索引合併),簡單的說就是如果一個查詢有個多個條件,在沒有合適的複合索引可以使用的情況下,Mysql會同時使用多個單列索引進行掃描,再把掃描的結果進行合併(並集,交集)。但索引合併在性能是可能是不可控的(並集,交集操作可能消耗大量的資源),所以使用時需要慎重,如果可能的話使用複合索引仍然是最佳的選擇。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章