Mysql-高性能索引

索引一種數據結構,其目的是爲了更快的查詢數據,在數據量很大的表中,創建良好的索引可以提高極大的性能。mysql

磁盤io與預讀

由於數據庫存儲數據量大,是不可能存儲在內存中以供查詢的,因此對於數據的查詢必然會跟磁盤打交道,因此只有瞭解了磁盤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是一種多路搜索樹(並非二叉的):緩存

  1. 定義任意非葉子結點最多隻有M個兒子;且M>2;
  2. 根結點的兒子數爲[2, M];
  3. 除根結點之外的非葉子結點的兒子數爲[M/2, M];
  4. 每一個結點存放至少M/2-1(取上整)和至多M-1個關鍵字;(至少2個關鍵字)
  5. 非葉子結點的關鍵字個數=指向兒子的指針個數-1;
  6. 非葉子結點的關鍵字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
  7. 非葉子結點的指針:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於等於K[M-1]的子樹,其它P[i]指向關鍵字屬於[K[i], K[i+1])的子樹;
  8. 全部葉子結點位於同一層;
  9. 爲全部葉子結點增長一個鏈指針;
  10. 全部關鍵字都在葉子結點出現; 如圖,是一個M爲3的B-TREE
    mysql-gao-xing-neng-suo-yin

B+的特性:數據結構

  1. 全部關鍵字都出如今葉子結點的鏈表中(稠密索引),且鏈表中的關鍵字剛好是有序的;
  2. 不可能在非葉子結點命中;
  3. 非葉子結點至關因而葉子結點的索引(稀疏索引),葉子結點至關因而存儲(關鍵字)數據的數據層;
  4. 更適合文件索引系統;

B+TREE索引性能分析:

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查找時過濾掉更多的行。好比主鍵和惟一索引,這種時候性能最好。因此在選擇索引長度時要同時考慮索引選擇性才能達到性能最優化。

多列索引

大多數對於索引理解不夠的,因此容易犯如下兩個:

  1. 爲不少個列建立獨立索引。在多個列上創建單獨索引並不能提升Mysql的查詢性能。5.0之後的Mysql引入了「索引合併」的策略,這樣能夠多個單列索引就行掃描,並將結果進行合併。這種算法有三種變形: OR條件聯合,AND條件相交。這種狀況下,合併結果會耗用大量的CPU,並且這個優化過程不計入「查詢成本」中。因此這些成本會被低估,有時候效率甚至低於全盤掃描,並且容易致使優化的時候注意不到這個點。
  2. 建立多列索引的順序有誤。查詢的時候沒有按照索引的順序來查詢,也會致使Mysql沒法使用索引。在建立多列索引的時候有一些有用的原則:在不考慮排序和分組的狀況下,將選擇性最高的列放在前面。可是不少時候這樣用也未必是好的,仍是要根據具體的狀況來判斷。
聚簇索引

聚簇索引並非一個單獨的索引形式,而是Mysql在B+TREE索引上的數據存儲形式。InnoDB的聚簇索引實現就是在統一結構裏面保存了B+TREE索引和數據行如圖:

mysql-gao-xing-neng-suo-yin
聚簇索引有時候對性能頗有幫助,但有時候也對性能形成嚴重的問題。下面咱們用幾張圖來分辨下非聚簇索引的表和聚簇索引的表的區別:
mysql-gao-xing-neng-suo-yin
非聚簇索引表的數據如上圖
mysql-gao-xing-neng-suo-yin
非聚簇索引表的主鍵索引圖
mysql-gao-xing-neng-suo-yin
聚簇索引的主鍵索引圖

從上面幾張圖能夠看出,非聚簇索引表的主鍵索引跟普通的索引沒有區別,他直接就是索引裏有個指向數據所在指針的形式,可是聚簇索引表的主鍵索引「就是」一張表,因此不須要非聚簇索引表那樣數據的獨立行儲存。咱們直接來看二者的對比圖:

mysql-gao-xing-neng-suo-yin

瞭解了他們的區別咱們接着來討論聚簇索引的優勢:

  1. 能夠把相關數據保存在一塊兒,查詢時只需在磁盤讀取少數數據頁就能得到全部的所需數據。
  2. 數據訪問速度快,由於數據和索引在一塊兒因此查詢起來很快。
  3. 使用覆蓋索引掃描的查詢能夠直接使用葉節點的主鍵值。二級索引(輔助索引)使用主鍵做爲"指針" 而不是使用地址值做爲指針的好處是,減小了當出現行移動或者數據頁分裂時輔助索引的維護工做,使用主鍵值看成指針會讓輔助索引佔用更多的空間,換來的好處是InnoDB在移動行時無須更新輔助索引中的這個"指針"。也就是說行的位置會隨着數據庫裏數據的修改而發生變化(後面缺點處會講到B+樹節點分裂以及頁分裂),使用聚簇索引就能夠保證無論這個主鍵B+樹的節點如何變化,輔助索引樹都不受影響。

下面咱們來看看聚簇索引的缺點:

  1. 聚簇索引在數據都放在內存中的數據毫無優點。
  2. 插入速度在按照主鍵順序插入的時候影響不大,但不按照順序加入的時候,速度會受到影響並且插入後要使用optimize table優化一下表,能更新索引統計數據並釋放成簇索引中的未使用的空間。
  3. 更新聚簇索引的成本很高,由於InnoDB會強制將每一個被更新的行移動到新的位置上。
  4. 基於聚簇索引的表插入新行,或者主鍵被更新致使須要移動行時,可能面臨「頁分裂的問題」。當該行插入到一個某個已經滿了的頁的時候,存儲引擎會將頁分裂成兩個頁面來容納該行,這樣的頁分裂操做會致使表佔用更多空間。並且這樣會形成數據存儲不連續,行比較稀疏的問題,也會致使全盤掃描變慢。頁分裂還會形成大量數據的移動,一次插入至少修改到3個頁。而順序插入的時候,不多遇到須要大量修改頁的狀況,最大的性能瓶頸就在自動增減主鍵的時候鎖的開銷。如圖:
    mysql-gao-xing-neng-suo-yin
    順序添加
    mysql-gao-xing-neng-suo-yin
    插入無序值的時候
  5. 二級索引(輔助索引)可能比想象的要大,由於二級索引葉子節點包含了引用行主鍵的列。並且二級索引須要兩次查找。
覆蓋索引

覆蓋索引指的是包含了一個查詢全部須要查詢字段的值。覆蓋索引是一個很是有用的工具,可以極大的提高效率,由於索引的葉子節點已經包含了全部須要的數據,無需再去讀取數據行。其優勢以下:

  1. 索引條目比起數據行要小得多,因此若是隻須要讀取索引,那MySQL就能極大的減小數據訪問量。
  2. 由於索引是按照列值順序存儲的,因此對於IO密集型的範圍查詢,只查找索引就會比去硬盤中隨機查詢每一行數據快得多。
  3. 數據庫引擎(如MyISAM)在內存中只緩存索引,數據緩存依賴於操做系統緩存,所以訪問數據須要系統調用,這個會形成嚴重的性能問題。
  4. 上面說的聚簇索引,覆蓋索引的時候就特別有用了。

由於覆蓋索引是索引中存儲了列的值,因此覆蓋索引的適用範圍僅適用於B+TREE的時候.

索引掃描來作排序

掃描索引自己很快,由於只須要一條索引記錄移動到下一條索引記錄就好了。可是若是索引不能覆蓋查詢的全部列,那就只能每一條索引記錄都要去查詢一次對應的行。這基本上都是隨機IO,因此按索引順序讀取數據的速度反而要比順序的全表掃描慢,尤爲是IO密集型的工做負載時。 只有當索引的列順序和order by的字句順序徹底一致,而且全部列的排序方向(正序倒序)都同樣時,Mysql才能使用索引來對結果作排序。固然若是最左前綴在where條件中已是一個常量了,能夠不用知足最左前綴的order by子句。

索引和鎖

索引可讓查詢鎖定更少的行。好比以前咱們遇到過得for update語句若是用了主鍵的索引,那麼就只鎖定一行,而其餘的就會鎖表。還有不少狀況查詢的時候只鎖定索引查出來的行,這樣的話能減小不少鎖的開銷。還有個InnoDB的細節須要注意: InnoDB在二級索引(輔助索引)上用的是共享鎖(讀鎖),可是訪問主鍵索引就用的是排他鎖(寫鎖)(其實也就是以前咱們工做中遇到的那個用主鍵的索引就是行鎖,可是其餘索引就是表鎖)。這種狀況就無法使用以前講到的覆蓋索引(二級索引訪問主鍵索引),而且使用 for update 比share in share mode或非鎖定查詢要慢得多。

一些微小的建議

數據庫的優化是一個很是重要的工做,不少時候不能犯教條主義錯誤,最主要的方式仍是要經過本身操做來驗證到底該如何優化。極可能一次優化在100M的數據量的時候起做用了,可是1G的時候又會變成慢查詢,這種狀況就要去思考如何才能避免再出這樣的問題。數據庫能夠存儲一些歷史數據做爲記錄,可是大多數狀況咱們須要的是最近的數據或者是少數的幾個熱數據,這種狀況下就須要用高速緩存的方式來完成這方面的工做,而不是一味的只用數據庫。這才設計構思的時候很重要。還有索引不是萬能的,千萬不要亂建索引,有時候還會形成速度變得更低,並且還浪費了空間,再創建索引的時候也要好好思考一下這個索引的必要性和用處。另外優化慢查詢有時候也未必非要加索引,不少時候經過一些語句的構造也能實現優化的目的。以上內容主要來自我以前閱讀的一本書籍《高性能MySQL》,配合一些算法知識再加上我自身的一些經驗,寫在這裏只是起到拋磚引玉的做用,數據庫優化的路還很長,坑還不少,但願與你們一塊兒學習,共同進步。

相關文章
相關標籤/搜索