InnoDB索引實現原理以及注意點和建議

1、InnoDB實現原理

雖然InnoDB也使用B+Tree做爲索引結構,但具體實現方式卻與MyISAM大相徑庭。由於InnoDB支持聚簇索引(主鍵索引),聚簇索引就是表,因此InnoDB不用像MyISAM那樣須要獨立的行存儲。也就是說,InnoDB的數據文件自己就是索引文件。數據庫

聚簇索引的每個葉子節點都包含了主鍵值、事務ID、用於事務和MVCC的回滾指針以及全部的剩餘列。假設咱們以col1爲主鍵,則下圖是一個InnoDB表的聚簇索引(主鍵索引)(Primary key)示意。服務器

與MyISAM不一樣的是,InnoDB的二級索引和聚簇索引很不相同。InnoDB的二級索引的葉子節點存儲的不是行號(行指針),而是主鍵列。這種策略的缺點是二級索引須要兩次索引查找,第一次在二級索引中查找主鍵,第二次在聚簇索引中經過主鍵查找須要的數據行。併發

畫外音:能夠經過咱們前面提到過的索引覆蓋來避免回表查詢,這樣就只須要一次回表查詢,對於InnoDB而言,就是隻須要一次索引查找就能夠查詢到須要的數據記錄,由於須要的數據記錄已經被索引到二級索引中,直接就能夠找到。高併發

由於InnoDB的索引的方式經過主鍵彙集數據,嚴重依賴主鍵。索引若是沒有定義主鍵,那麼InnoDB會選擇一個惟一的非空索引代替。若是沒有這樣的索引,InnoDB會隱式定義一個主鍵來做爲聚簇索引。性能

2、優缺點

  1. 優勢
    1. 能夠把相關數據存儲在一塊兒,減小數據查詢時的磁盤I/O
    2. 數據訪問更快,由於聚簇索引就是表,索引和數據保存在一個B+Tree中
    3. 使用索引覆蓋的查詢時能夠直接使用頁節點中的主鍵值
  2. 缺點
    1. 插入速度嚴重依賴插入順序
    2. 更新聚簇索引列的代價很高,由於會強制InnoDB把更新的列移動到新的位置
    3. 基於聚簇索引的表在插入新行,或者主鍵被更新致使須要移動行的時候,可能會致使「頁分裂」。當行的主鍵值要求必須將這一行插入到已滿的頁中時,存儲引擎會將該頁分裂爲兩個頁面來容納該行,這就是一次頁分裂操做,頁分裂會致使表佔用更多的存儲空間。
    畫外音:關於頁,咱們在上一篇文章中也提到過。頁是計算機管理存儲器的邏輯塊,硬件及操做系統每每將主存和磁盤存儲區分割爲連續的 大小相等的塊,每一個存儲塊稱爲一頁。存和磁盤以頁爲單位交換數據。數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設 爲等於一個頁,這樣每一個節點只須要一次磁盤I/O就能夠徹底載入 
    基於聚簇索引以上的這些特色,在InnoDB中,咱們應該儘可能使用和應用無關的主鍵,例如自增主鍵,這樣能夠保證數據行是按照順序寫入的。而不是使用GUID、UUID生成隨機的主鍵。

3、 注意&建議

  1. 主鍵推薦使用整型,避免索引分裂;
  2. 查詢使用索引覆蓋可以提高很大的性能,由於避免了回表查詢
  3. 選擇合適的順序創建索引,有的場景並不是區分度越高的字段放在前邊越好,聯合索引使用居多
  4. 合理使用in操做將範圍查詢轉換成多個等值查詢,可是若是有order by 不一樣的列來講是不會走索引的
  5. 大批量數據查詢任務分解爲分批查詢
  6. 將複雜查詢轉換爲簡單查詢
  7. 合理使用inner join,好比分頁的時候

4、一些問題的分析

  1. 索引分裂我的理解:在 MySQL插入記錄的同時會更新配置的相應索引文件,根據以上的瞭解,在插入索引時,可能會存在索引的頁的分裂,所以會致使磁盤數據的移動。當插入的主鍵是隨機字符串時,每次插入不會是在B+樹的最後插入,每次插入位置都是隨機的,每次均可能致使數據頁的移動,並且字符串的存儲空間佔用也很大,這樣重建索引不只僅效率低並且 MySQL的負載也會很高,同時還會致使大量的磁盤碎片,磁盤碎片多了也會對查詢形成必定的性能開銷,由於存儲位置不連續致使更多的磁盤I/O,這就是爲何推薦定義主鍵爲遞增整型的一個緣由優化

  2. 自增主鍵的弊端 對於高併發的場景,在InnoDB中按照主鍵的順序插入可能會形成明顯的爭用,主鍵的上界會成爲「熱點」,由於全部的插入都發生在此處,索引併發的插入可能會形成間隙鎖競爭,何爲間隙鎖競爭,下個會詳細介紹;另一個緣由多是Auto_increment的鎖機制,在 MySQL處理自增主鍵時,當innodb_autoinc_lock_mode爲0或1時,在不知道插入有多少行時,好比insert t1 xx select xx from t2,對於這個statement的執行會進行鎖表,只有這個statement執行完之後纔會釋放鎖,而後別的插入纔可以繼續執行,可是在innodb_autoinc_lock_mode=2時,這種狀況不會存在表鎖,可是隻能保證全部併發執行的statement插入的記錄是惟一而且自增的,可是每一個statement作的多行插入之間是不鏈接的spa

  3. 優化器不使用索引選擇全表掃描 好比一張order表中有聯合索引(order_id, goods_id),在此例子上來講明這個問題是從兩個方面來講:操作系統

    1. 查詢字段在索引中
    select order_id from order where order_id > 1000; --若是查看其執行計劃的話,發現是用use index condition,走的是索引覆蓋。 
    1. 查詢字段不在索引中
    select * from order where order_id > 1000; 

    此條語句查詢的是該表全部字段,有一部分字段並未在此聯合索引中,所以走聯合索引查詢會走兩步,首先經過聯合索引肯定符合條件的主鍵id,而後利用這些主鍵id再去聚簇索引中去查詢,而後獲得全部記錄,利用主鍵id在聚簇索引中查詢記錄的過程是無序的,在磁盤上就變成了離散讀取的操做,假如當讀取的記錄不少時(通常是整個表的20%左右),這個時候優化器會選擇直接使用聚簇索引,也就是掃全表,由於順序讀取要快於離散讀取,這也就是爲什麼通常不用區分度不大的字段單獨作索引,注意是單獨由於利用此字段查出來的數據會不少,有很大機率走全表掃描。設計

  4. 範圍查詢以後的條件不走索引 根據 MySQL的查詢原理的話,當處理到where的範圍查詢條件後,會將查詢到的行所有返回到服務器端(查詢執行引擎),接下來的條件操做在服務器端進行處理,這也就是爲何範圍條件不走索引的緣由了,由於以後的條件過濾已經不在存儲引擎完成了。可是在 MySQL 5.6之後假如了一個新的功能index condition pushdown(ICP),這個功能容許範圍查詢條件以後的條件繼續走索引,可是須要有幾個前提條件:指針

    1. 查詢條件的第一個條件須要時有邊界的,好比select * from xx where c1=x and c2>x and c3<x,這樣c3是能夠走到索引的;
    2. 支持InnoDB和MyISAM存儲引擎;
    3. where條件的字段須要在索引中;
    4. 分表ICP功能5.7開始支持;
    5. 使用索引覆蓋時,ICP不起做用。
  5. 分頁offset值很大性能問題
    在 MySQL中,分頁當offset值很大的時候,性能會很是的差,好比limit 100000, 20,須要查詢100020條數據,而後取20條,拋棄前100000條,在這個過程當中產生了大量的隨機I/O,這是性能不好的緣由,爲了解決這個問題,切入點即是減小無用數據的查詢,減小隨機I/O

    1. 利用inner join
    select * from t1 inner join (select id from t1 where xxx order by xx limit 1000000,5) as t2 using(id); --子查詢先走索引覆蓋查得id,而後根據獲得的id直接取5條得數據。 
    1. 利用範圍查詢條件來限制取出的數據
    select * from t1 where id > 1000000 order by id limit 0, 5; --即利用條件id > 1000000在掃描索引是跳過1000000條記錄,而後取5條便可,這種處理方式的offset值便成爲0了,但此種方式一般分頁不能用,可是能夠用來分批取數據。
相關文章
相關標籤/搜索