索引一種數據結構,其目的是爲了更快的查詢數據,在數據量很大的表中,創建良好的索引可以提高極大的性能。mysql
由於數據庫存儲數據量大,是不可能存儲在內存中以供查詢的,因此對於數據的查詢必然會跟磁盤打交道,因此只有瞭解了磁盤io和預讀的基本知識,咱們才能真正的理解索引的原理。算法
磁盤讀取數據靠的是機械運動,每次讀取數據花費的時間可 以分爲尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁道所須要的時間,主流磁盤通常在5ms如下;旋轉延遲就是咱們常常據說的磁 盤轉速,好比一個磁盤7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤 IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500MIPS的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的時間能夠執行40萬條指令,數據庫動輒十萬百萬乃至千萬級數 據,每次9毫秒的時間,顯然是個災難。考 慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局 部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體一頁 有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO。sql
以前說到了磁盤讀取數據的方式,那麼咱們的索引是如何配合這個方式更快的搜索到數據呢?首先,咱們要了解索引的數據結構。咱們這裏講到的索引B+TREE的索引,由於這個索引咱們最經常使用,實際上索引還有哈希索引,空間數據索引,全文索引。數據庫
首先咱們來理解B+TREE的數據結構: B+Tree是一種多路搜索樹(並非二叉的):緩存
B+的特性:數據結構
B+TREE的深度最可能是O(log[M/2]N),在路徑上的每一個節點須要用O(logM)s時間複雜度來肯定是哪一個分支(使用二分查找),inset和delete可能須要O(M)的工做量來調整該節點上的全部信息,因此insert和delete的運行時間最壞狀況是O(Mlog[M]N),而每次的查詢只須要花費O(logN)。從剛剛的時間複雜度能夠看出當在內存中查詢時,Mzuihaode選擇是3或者4,再增大時速度就會增長。可是咱們的數據存儲是在磁盤中的,相比讀取一個存儲器所花費的時間,M增大所增長的時間花費不值一提。此時M的值選擇爲使得一個內部節點可以裝入一個磁盤區塊的最大值,因此M取值範圍爲[32,256],這樣當一片樹葉上元素是滿的,並且樹葉是滿的那麼硬盤上一個區塊就被裝滿了。這樣意味着,一個記錄總能夠在不多的磁盤訪問中被找到,由於此時B樹深度只有2或3,而根能夠直接加載到內存中,因此整個訪問速度就會很快。app
因此咱們再來看上圖: 若是要查找數據項30,那麼首先會把磁盤塊1由磁盤加載到內存,此時發生一次IO,在內存中用二分查找確 定29在28和65之間,鎖定磁盤塊1的P2指針,內存時間由於很是短(相比磁盤的IO)能夠忽略不計,經過磁盤塊1的P2指針的磁盤地址把磁盤塊3由磁 盤加載到內存,發生第二次IO,30在28和35之間,鎖定磁盤塊3的P2指針,經過指針加載磁盤塊8到內存,發生第三次IO,同時內存中作二分查找找到 30,結束查詢,總計三次IO。真實的狀況是,3層的b+樹能夠表示上百萬的數據,若是上百萬的數據查找只須要三次IO,性能提升將是巨大的,若是沒有索引,每一個數據項都要發生一次IO,那麼總共須要百萬次的IO,顯然成本很是很是高。函數
有些查詢不當的使用索引,使得Mysql沒法使用已有的索引。好比查詢中列不是獨立的,則Mysql不會使用索引。獨立的列指的是:索引不能是表達式的一部分,更不能是函數的參數,例如:工具
select app_id from app where app_id + 1 = 5;
複製代碼
MySQL沒法解析app_id + 1這個表達式,因此沒法使用索引。性能
有時候索引的字段特別的長,這會讓索引變得又大又慢。這種狀況能夠經過只取出字段前面幾個字符來作索引,這樣能夠節約索引空間,從而提升索引的效率,可是這樣會減小索引的選擇性。索引的選擇性,指的是不重複的索引值(基數)跟數據表的總數的比值。索引選擇性越高查詢的效率就越快,由於這樣索引能幫助Mysql查找時過濾掉更多的行。好比主鍵和惟一索引,這種時候性能最好。因此在選擇索引長度時要同時考慮索引選擇性才能達到性能最優化。
大多數對於索引理解不夠的,因此容易犯如下兩個:
聚簇索引並非一個單獨的索引形式,而是Mysql在B+TREE索引上的數據存儲形式。InnoDB的聚簇索引實現就是在統一結構裏面保存了B+TREE索引和數據行如圖:
從上面幾張圖能夠看出,非聚簇索引表的主鍵索引跟普通的索引沒有區別,他直接就是索引裏有個指向數據所在指針的形式,可是聚簇索引表的主鍵索引「就是」一張表,因此不須要非聚簇索引表那樣數據的獨立行儲存。咱們直接來看二者的對比圖:
瞭解了他們的區別咱們接着來討論聚簇索引的優勢:
下面咱們來看看聚簇索引的缺點:
覆蓋索引指的是包含了一個查詢全部須要查詢字段的值。覆蓋索引是一個很是有用的工具,可以極大的提高效率,由於索引的葉子節點已經包含了全部須要的數據,無需再去讀取數據行。其優勢以下:
由於覆蓋索引是索引中存儲了列的值,因此覆蓋索引的適用範圍僅適用於B+TREE的時候.
掃描索引自己很快,由於只須要一條索引記錄移動到下一條索引記錄就好了。可是若是索引不能覆蓋查詢的全部列,那就只能每一條索引記錄都要去查詢一次對應的行。這基本上都是隨機IO,因此按索引順序讀取數據的速度反而要比順序的全表掃描慢,尤爲是IO密集型的工做負載時。 只有當索引的列順序和order by的字句順序徹底一致,而且全部列的排序方向(正序倒序)都同樣時,Mysql才能使用索引來對結果作排序。固然若是最左前綴在where條件中已是一個常量了,能夠不用知足最左前綴的order by子句。
索引可讓查詢鎖定更少的行。好比以前咱們遇到過得for update語句若是用了主鍵的索引,那麼就只鎖定一行,而其餘的就會鎖表。還有不少狀況查詢的時候只鎖定索引查出來的行,這樣的話能減小不少鎖的開銷。還有個InnoDB的細節須要注意: InnoDB在二級索引(輔助索引)上用的是共享鎖(讀鎖),可是訪問主鍵索引就用的是排他鎖(寫鎖)(其實也就是以前咱們工做中遇到的那個用主鍵的索引就是行鎖,可是其餘索引就是表鎖)。這種狀況就無法使用以前講到的覆蓋索引(二級索引訪問主鍵索引),而且使用 for update 比share in share mode或非鎖定查詢要慢得多。
數據庫的優化是一個很是重要的工做,不少時候不能犯教條主義錯誤,最主要的方式仍是要經過本身操做來驗證到底該如何優化。極可能一次優化在100M的數據量的時候起做用了,可是1G的時候又會變成慢查詢,這種狀況就要去思考如何才能避免再出這樣的問題。數據庫能夠存儲一些歷史數據做爲記錄,可是大多數狀況咱們須要的是最近的數據或者是少數的幾個熱數據,這種狀況下就須要用高速緩存的方式來完成這方面的工做,而不是一味的只用數據庫。這才設計構思的時候很重要。還有索引不是萬能的,千萬不要亂建索引,有時候還會形成速度變得更低,並且還浪費了空間,再創建索引的時候也要好好思考一下這個索引的必要性和用處。另外優化慢查詢有時候也未必非要加索引,不少時候經過一些語句的構造也能實現優化的目的。以上內容主要來自我以前閱讀的一本書籍《高性能MySQL》,配合一些算法知識再加上我自身的一些經驗,寫在這裏只是起到拋磚引玉的做用,數據庫優化的路還很長,坑還不少,但願與你們一塊兒學習,共同進步。