淺談MySQL InnoDB索引

基於MySQL 5.6.16

前言

一次內存訪問、SSD 硬盤訪問和SATA 硬盤隨機訪問的時間分別約是_______。 html

A 幾微秒,幾毫秒,幾十毫秒mysql

B 幾微秒,幾毫秒,幾十毫秒git

C 幾十納秒,幾十微秒,幾十毫秒程序員

D 幾十納秒,幾十微秒,十幾毫秒
以上騰訊2017實習生題目,答案爲C。github

當前互聯網時代,性能尤其重要,性能差即意味着不可用。既然內存性能最好,是否能夠將數據所有加載在內存中?算法

2018年12月,「美光旗下品牌英睿達(Crucial)宣佈已經開始出貨自家容量最高、速度最快的服務器級內存,128GBDDR4-4266LRDIMM,一條就要3999美圓,約合人民幣2.65萬元。」sql

大數據時代,數據隨隨便便就上T,基本成本、容量等方面考慮,沒法將數據所有加載入內存。因爲沒法所有裝入內存,則必然依賴磁盤存儲。而內存的讀寫速度是磁盤的成千上萬倍(與具體實現有關),所以,存儲的核心問題是「如何減小磁盤讀寫次數」。數據庫

數據結構

哈希表

Hash table,也叫散列表, 是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。哈希表最大的優勢,就是把數據的存儲和查找消耗的時間大大下降,幾乎能夠當作是常數時間(不考慮hash衝突的狀況下,時間複雜度爲O(1))。segmentfault

  • 無序性,致使沒法範圍查找和索引排序。 >、<、between等範圍查詢沒法使用索引。
  • 對完整的key計算hash,因此不支持部分匹配。沒法使用like 'hash%'進行前綴匹配。
  • 當產生hash碰撞的時候,數據庫要遍歷拉鍊中全部的行指針,逐個取出數據行進行比較,數據量越大,衝突越多,查找代價越高。

因爲hash索引的上述缺點,因此實際使用hash索引的狀況不多,MySQL的Memory存儲引擎和NDB分佈式存儲引擎使用了hash結構索引。
當一個數據結構,在支持第一點(順序存儲)的狀況下,對第二點(部分匹配)有一個自然的加強:全部前綴同樣的值都是按順序存儲在一塊兒的,當咱們使用左前綴查詢時,從第一個符合前綴條件的值開始掃描,掃到第一個不符合規則的值便可中止,不用掃描所有內容。數組

  • 二叉搜索樹(BST):讀/寫平均O(log2(n))次;若是樹不平衡,則最差讀/寫O(n)次
  • 自平衡二叉搜索樹(AVL):在BST的基礎上加入了自平衡算法,讀/寫最大O(log2(n))次
  • 紅黑樹(RBT):另外一種自平衡的查找樹,讀/寫最大O(log2(n))次

BST、AVL、RBT很好的將讀寫次數從O(n)優化到O(log2(n));其中,AVL和RBT都比BST多了自平衡的功能,將讀寫次數降到最大O(log2(n))。

假設使用自增主鍵,則主鍵自己是有序的,樹結構的讀寫次數可以優化到樹高(無序的數據會致使插入位置先後的節點移動),樹的高度越低讀寫次數越少;自平衡保證了樹結構的穩定。若是想進一步優化,能夠引入B/B+樹。

B+樹

示例圖:
45b99e82-b0ab-4072-94e3-8792638c879d.png

若是拋開維護操做,那麼B樹就像一棵「m叉搜索樹」(m是子樹的最大個數),時間複雜度爲O(logm(n))。然而,B樹設計了一種高效簡單的維護操做,使B樹的深度維持在約log(ceil(m/2))(n)~logm(n)之間,大大下降樹高。與單純的算法不一樣,磁盤IO次數纔是更大的影響因素。B樹與AVL的時間複雜度是相同的,但因爲B樹的層數少,磁盤IO次數少,實踐中B樹的性能要優於AVL等二叉樹,例如上圖中查找「10」數據,只須要通過三次磁盤I/O便可。
另外,B樹對局部性原理很是友好:

  • 因爲存儲介質的特性,磁盤自己存取就比主存慢不少,對磁盤來講,可以最大化的發揮磁盤技術特性的使用方式是:一次性的讀取或寫入固定大小的一塊數據,並儘量的減小隨機尋道這個操做的次數。爲了提升效率,減小磁 盤I/O,磁盤每每不是嚴格按需讀取,而是每次都會預讀,不管是讀取一行仍是多行,都會將該行或者多行所在的頁所有加載進來,而後再讀取對應的數據記錄。這樣作的理論依據是計算機科學中著名的局部性原理:「空間局部性(sequential locality)」,其理論基礎是認爲數據每每是連續訪問的,當一個數據被用到時,其附近的數據也一般會立刻被使用。
  • 預讀:對於每一個文件的第一個讀請求,系統讀入所請求的頁面並讀入緊隨其後的少數幾個頁面(X86的Linux中一個標準頁面大小是4KB),這時的預讀稱爲同步預讀。對於第二次讀請求,若是所讀頁面不在 Cache 中,即不在前次預讀的頁中,則代表文件訪問不是順序訪問,系統繼續採用同步預讀;若是所讀頁面在 Cache 中,則代表前次預讀命中,操做系統把預讀頁的大小擴大一倍,此時預讀過程是異步的,應用程序能夠不等預讀完成便可返回,只要後臺慢慢讀頁面便可,這時的預讀稱爲異步預讀。任何接下來的讀請求都會處於兩種狀況之一:

    • 若不在cache,操做系統從磁盤中讀取對應的數據頁,而且系統還會將該數據頁以後的連續幾頁也一併讀入到cache中,再將應用須要的數據返回給應用。此狀況操做系統認爲是跳躍讀取,屬於同步預讀。
    • 若命中cache,至關於上次緩存的內容有效,操做系統認爲順序讀盤,則繼續擴大緩存的數據範圍,將以前緩存的數據頁日後的N頁數據再讀取到cache中,屬於異步預讀。

數據庫系統的設計者巧妙利用了操做系統以上特性,將一個節點的大小設爲等於一個頁(MySQL InnoDB的頁爲16KB),新建節點時,InnoDB每次申請磁盤空間時都會是若干地址連續磁盤塊(磁盤塊爲512B)來達到頁的大小16KB,保證一個節點物理上也存儲在一個頁裏。在把磁盤數據讀入到磁盤時會以頁爲基本單位,這樣每一個節點只須要一次I/O就能夠徹底載入。
MySQL 在執行讀操做時,會先從數據庫的緩衝區中讀取,若是不存在與緩衝區中就會嘗試從內存中加載頁面,若是前面的兩個步驟都失敗了,最後就只能執行 I/O 從磁盤中獲取對應的數據頁。

B+樹特色:

  • 非葉子節點不存儲數據,且是葉子節點的索引(稀疏索引)。

    • 保證查找性能的穩定:若是非葉子節點也存儲數據,則最好狀況下查找到根節點,最壞狀況下查找到葉子結點。
    • 單個節點能夠存儲更多的數據,一次性讀入內存中的數據也就越多,相對來講I/O次數也就減小了。
  • 葉子節點存儲數據(稠密索引),各個數據頁之間組成一個有序鏈表(MySQL InnoDB是雙向鏈表),便於範圍/全表掃描,也便於節點分裂。

下圖展現了「葉子節點是否存儲數據」狀況下,兩顆樹的差異,爲了顯示的更直觀點,假設每一個節點只夠存儲兩條數據。
能夠看到上面那顆樹最多須要三次磁盤I/O,而下面的那棵樹須要兩次。
4939881.png
例子:
新聞文章的內容一般都很長,因此內容字段類型通常都是大文本類型。平時更多的是顯示文章列表,若是顯示的列表中僅須要展現標題/副標題,而不須要展現內容片斷的話,能夠將內容字段單獨抽出一張表。剝離內容字段後的文章表記錄相對小了不少,這樣在獲取文章列表時也會有比較少的I/O。
爲何like只能左匹配?
由於MySQL InnoDB使用原數據格式進行存儲(函數索引存的是表中的數據應用函數後獲得的數據),加上B+樹的有序特性,以下簡略圖:
42419202.png
若是sql執行「 like '林%' 」,能夠在查找到第一個「林」以後,向後一直取得數據,直到遇到第一個非「林」開頭到數據,「 like '%1%' 」則不行。
注:
以上列出了了種種B/B+樹作索引的優勢,不表明B/B+樹就是最適合作索引,還有其餘適合作索引的數據結構/存儲引擎,好比LSM樹,二者各有優缺點,適用不一樣的場景。

索引

主鍵索引

索引能夠分爲主鍵索引(聚簇索引),非主鍵索引(非聚簇索引)。

  • 主鍵索引:每一個表都有一個索引是存儲了全部數據的,這個索引既主鍵索引,通常創建在主鍵上,若是表沒有主鍵,則爲自增鍵,或隱藏的Rowid列。

    • 主鍵索引在插入新行和更新主鍵時,可能引發「頁分裂」問題,致使性能降低。因此若是每次插入的主鍵值都是最大的(遞增主鍵),因爲都是在末尾插入,能夠減小數據移動和頁分裂。可是因爲B+樹「50%分裂策略」會形成空間利用率的問題(索引頁面空間利用率在50%左右),目前全部的數據庫都針對B+樹索引的遞增/遞減插入進行了優化,詳情見《從MySQL Bug#67718淺談B+樹索引的分裂優化》或《數據庫索引算法——B樹與B+樹》。
      46975012.png
      46949142.png
  • 非主鍵索引:不存儲數據,僅存儲索引數據和主鍵值。非惟一索引以「索引信息+主鍵」來保證鍵的惟一性。

    • 由於不存儲數據,因此佔用空間會比主鍵索引小不少,I/O也會小。統計一個錶行數是(count),一些優化器會選擇表中最小的索引來做爲統計的目標索引,性能也相應更快。
    • 查詢時,須要經過對應的主鍵,作「回表」查詢,即多一次I/O。

當sql執行「 where name = 'n' 」(name爲非主鍵索引)時:
45476082.png

覆蓋索引

索引包含查詢中所須要的所有數據列,爲覆蓋索引。
例如:對於「 SELECT username, age FROM users WHERE username='林' 」, (username, age) 就是該查詢的一個覆蓋索引。
覆蓋索引可以避免「回表」查詢。
創建覆蓋索引,查詢速度能夠提高數十倍,甚至上千倍,爲何回表查詢這麼耗時?

回表查詢

磁盤I/O

HDD

  • 硬盤內部主要部件爲磁盤盤片、傳動手臂、讀寫磁頭和主軸馬達。實際數據都是寫在盤片上,讀寫主要是經過傳動手臂上的讀寫磁頭來完成。實際運行時,主軸讓磁盤盤片轉動,而後傳動手臂可伸展讓讀取頭在盤片上進行讀寫操做。磁盤物理結構以下圖所示:
    31888738-be8f-4e4e-bc90-354136c4ba79.jpg
    55772790.png
    55810875.png
  • 磁盤讀取時間

    • 尋道時間,表示磁頭在不一樣磁道之間移動的時間。
    • 旋轉延遲,表示在磁道找到時,中軸帶動盤面旋轉到合適的扇區開頭處。
    • 傳輸時間,表示盤面繼續轉動,實際讀取數據的時間。
  • 順序讀寫和隨機讀寫對於機械硬盤來講爲何性能差別巨大?

    1. 隨機讀寫:操做的磁盤地址不是連續的;須要屢次尋道和旋轉延遲,而這個時間多是傳輸時間的許多倍。
    2. 順序讀寫:操做的磁盤地址是連續的;主要時間花費在了傳輸時間,不須要尋道;磁盤會預讀,預讀即在讀取的起始地址連續讀取多個頁面。

SSD
固態驅動器(solid state drives SSDs)沒有旋轉磁盤設備,所有都是採用閃存。SSD內部維護了一張映射表(Mapping Table),HOST每寫入一個Host Page,就會產生一個新的映射關係,這個映射關係會加入(第一次寫)或者更改Mapping Table;當讀取某個Host Page時, SSD首先查找MappingTable中該Host Page對應的Physical Page,而後再訪問Flash讀取相應的Host數據。與傳統的機械磁盤相比,省去了尋道時間和旋轉時間。
SSD內部通常使用NAND Flash來做爲存儲介質,其邏輯結構以下:
57572525.png
SSD中通常有多個NAND Flash,每一個NAND Flash包含多個Block,每一個Block包含多個Page。因爲NAND的特性,其存取都必須以page爲單位,即每次讀寫至少是一個page,一般地,每一個page的大小爲4k或者8k。另外,NAND還有一個特性是,其只能是讀或寫單個page,但不能覆蓋寫入某個page,必須先要清空裏面的內容,再寫入。因爲清空內容的電壓較高,必須是以block爲單位。所以,沒有空閒的page時,必需要找到沒有有效內容的block,先擦寫,而後再選擇空閒的page寫入。
Block中的數據變老或者無效,是指沒有任何映射關係指向它們,用戶不會訪問到這些FLASH空間,它們被新的映射關係所取代。好比有一個Host Page A,開始它存儲在FLASH空間的X,映射關係爲A->X。後來,HOST重寫了該Host Page,因爲FLASH不能覆蓋寫,SSD內部必須尋找一個沒有寫過的位置寫入新的數據,假設爲Y,這個時候新的映射關係創建:A->Y,以前的映射關係解除,位置X上的數據變老失效,咱們把這些數據叫垃圾數據
隨着HOST的持續寫入,FLASH存儲空間慢慢變小,直到耗盡。若是不及時清除這些垃圾數據,HOST就沒法寫入。SSD內部都有垃圾回收機制,它的基本原理是把幾個Block中的有效數據(非垃圾數據,上圖中的綠色小方塊表示的)集中搬到一個新的Block上面去,而後再把這幾個Block擦除掉,這樣就產生新的可用Block了。
77387264.png
上圖中,Block x上面有效數據爲A,B,C,Block y上面有效數據爲D,E,F,G,紅色方塊爲無效數據。垃圾回收機制就是先找一個未寫過的可用Block z,而後把Block x和Block y的有效數據搬移到Block z上面去,這樣Block x和Block y上面就沒有任何有效數據,能夠擦除變成兩個可用的Block。

  • 讀:因爲預讀的緣由,SSD的順序讀仍然比隨機讀要快的多。
  • 寫:寫相同數據量的狀況下:

    • 隨機寫可能會致使大量無效頁面的數據分散在仍然包含有效數據的頁面中。在對這些塊進行垃圾回收期間,必須將全部有效數據移動到其餘的塊。
    • 而當文件被順序寫時,若是數據無效,一般都是整個塊無效,所以不須要移動數據。有時,文件的一部分可能與另外一個文件共享一個塊,但平均而言,只須要移動大約一半這樣的塊,這使得它比隨機寫入的塊的垃圾收集快得多。
select * from user where name like '林%'

假設按照name索引過濾剩下600條數據。則該索引總共會產生1次隨機訪問(查找第一個匹配的節點),和599次順序訪問(按着第一個匹配的節點順序往下查找匹配)。由於該索引中的列並不能知足須要,因此會作「回表」查詢,每個索引行都會產生一次隨機訪問。以上查詢總共有601次隨機訪問和599次順序訪問。
注:MySQL會對特定的查詢作優化,如MySQL5.6以後引入MMR,以上假設爲未被優化狀況。
56a01989-24c4-43c2-91fd-1b06363c2849.png

爲何分辨度不高的列(如性別)不適合建索引?
每一次回表都是耗時都隨機訪問,索引查找加上回表的性能並不會優於全表掃描。MySQL查詢優化器也會自動優化成全表掃描。

案例

SELECT * FROM t_terminal WHERE token LIKE 'hw%' LIMIT 1006000, 5

設備表t_terminal數量4800W,token字段爲索引字段,符合篩選條件的數據爲370W。MySQL版本5.6.29-log。
以上SQL查詢耗時3.5秒:

  • MySQL執行limit時,會掃描全部limit的數據項(100605),再跳過前面數據(100600)。
  • 會執行100605次回表隨機訪問。

若是優化成:

SELECT * FROM t_terminal t1 
INNER JOIN (
  SELECT id FROM t_terminal  WHERE token LIKE 'hw%' LIMIT 1006000, 5
) t2 USING (id);

查詢耗時0.5秒:子查詢中會先篩選出5條limit結果,也就是隻有5條數據作回表查詢。

ORDER BY

有時候排序也會成爲性能殺手,例如查出來的結果集很大,對結果集作排序也會耗去很多時間。
按照索引順序掃描得出的結果天然是有序的,將排序字段加入到索引組中,以免對結果重排序,減小磁盤I/O和內存的使用。

SELECT id, name FROM user WHERE name = '林' ORDER BY age ASC

以上SQL創建組合索引(name, age),利用索引樹已經排好序的特性,查詢結果無需再次排序。

思考:

SELECT id, name FROM user WHERE city = '深圳' AND name LIKE '林%' ORDER BY age ASC

A(city, name, age)和 B(city, age, name)以上哪一種組合索引更合適?

  • A:在name過濾以後的結果集,沒法順序獲取age數據(即全部‘林’開頭的name數據沒有湊在一塊兒),只能在內存中對結果集作重排序。
  • B:在age排序後,沒法順序獲取name數據,只能在內存中對結果集作過濾。

一個 SQL 查詢中同時擁有範圍謂詞和 ORDER BY 時,咱們可以作的就是在這二者之間作出選擇。近幾年排序速度已經提高不少,大多數狀況下A和B同樣快,甚至A比B更快。
可是若是:

  • 索引篩選結果集很大。
  • 程序須要的是其中的部分數據,即SQL加上limit 100。

那麼B會比A快不少(很大的可能,除非符合的數據都排在後面),由於A須要掃描出全部符合name的條件,再按age排序,以後才limit,而B直接找到前100個符合條件的便可。

GROUP BY

group by操做在沒有合適的索引可用的時候,一般先掃描整個表提取數據並建立一個臨時表,而後按照group by指定的列進行排序。在這個臨時表裏面,對於每個group的數據行來講是連續在一塊兒的。完成排序以後,就能夠發現全部的groups,並能夠執行彙集函數(aggregate function)。在沒有使用索引的時候,須要建立臨時表和排序,因此制約group by性能的問題,就是臨時表+排序,儘可能減小磁盤排序,減小磁盤臨時表的建立,是比較有用的處理辦法。在執行計劃中一般能夠看到「Using temporary; Using filesort」。

CREATE TABLE `t1` (
​
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
`c3` int(11) DEFAULT NULL,
`c4` int(11) DEFAULT NULL,
KEY `idx_g` (`c1`,`c2`,`c3`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
​
mysql> explain extended select c1,c2  from t1 group by c2 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: index
possible_keys: idx_g
          key: idx_g
      key_len: 15
          ref: NULL
         rows: 15441
     filtered: 100.00
        Extra: Using index; Using temporary; Using filesort

鬆散索引掃描(Loose Index Scan)

鬆散索引掃描不須要連續的掃描索引中得每個元組,掃描時僅考慮索引中得一部分。當查詢中沒有where條件的時候,鬆散索引掃描讀取的索引元組的個數和groups的數量相同。若是where條件包含範圍預測,鬆散索引掃描查找每一個group中第一個知足範圍條件,而後再讀取最少可能數的keys。
若是查詢可以使用鬆散索引掃描,那麼執行計劃中Etra中提示「 using index for group-by」。

mysql> explain select c1, min(c2)  from t1 group by c1 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: range
possible_keys: idx_g
          key: idx_g
      key_len: 10
          ref: NULL
         rows: 15442
        Extra: Using index for group-by

鬆散索引掃描只須要讀取不多量的數據就能夠完成group by操做,於是執行效率很是高。
60642973.png
鬆散索引條件:

  • 查詢在單一表上。
  • group by指定的全部列是索引的一個最左前綴,而且沒有其它的列。好比表t1( c1,c2,c3,c4)上創建了索引(c1,c2,c3)。若是查詢包含「group by c1,c2」,那麼可使用鬆散索引掃描。可是「group by c2,c3」(不是索引最左前綴)和「group by c1,c2,c4」(c4字段不在索引中)。
  • 若是在選擇列表select list中存在彙集函數,只能使用 min()和max()兩個彙集函數,而且指定的是同一列(若是min()和max()同時存在)。這一列必須在索引中,且緊跟着group by指定的列。好比,select t1,t2,min(t3),max(t3) from t1 group by c1,c2。
  • 若是查詢中存在除了group by指定的列以外的索引其餘部分,那麼必須以常量的形式出現(除了min()和max()兩個彙集函數)。好比,select c1,c3 from t1 group by c1,c2不能使用鬆散索引掃描。而select c1,c3 from t1 where c3 = 3 group by c1,c2可使用鬆散索引掃描。
  • 索引中的列必須索引整個數據列的值(full column values must be indexed),而不是一個前綴索引。好比,c1 varchar(20), INDEX (c1(10)),這個索引沒發用做鬆散索引掃描。(前綴索引,與上面提到的索引的最左前綴是不一樣的)

自從5.5開始,鬆散索引掃描能夠做用於在select list中其它形式的彙集函數,除了min()和max()以外,還支持:

  • AVG(DISTINCT), SUM(DISTINCT)和COUNT(DISTINCT)可使用鬆散索引掃描。AVG(DISTINCT), SUM(DISTINCT)只能使用單一列做爲參數。而COUNT(DISTINCT)可使用多列參數。

緊湊索引掃描(Tight Index Scan)

緊湊索引掃描實現 GROUP BY 和鬆散索引掃描的區別主要在於:緊湊索引掃描須要在掃描索引的時候,讀取全部知足條件索引鍵(注意,是索引健,不包含索引樹內沒有的數據),而後再根據讀取出的數據來完成 GROUP BY 操做獲得相應結果。
若是緊湊索引掃描起做用,那麼必須知足:在查詢中存在常量相等where條件字段(索引中的字段),且該字段在group by指定的字段的前面或者中間。
緊湊索引掃描一樣能夠避免額外的排序操做,可是效率低於鬆散索引。使用緊湊索引掃描,執行計劃Extra通常顯示「using index」,至關於使用了覆蓋索引

mysql> explain extended select c1,c2  from t1 where c1=2 group by c2 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: ref
possible_keys: idx_g
          key: idx_g
      key_len: 5
          ref: const
         rows: 5
     filtered: 100.00
        Extra: Using where; Using index

在MySQL中,MySQL Query Optimizer首先會選擇嘗試經過鬆散索引掃描來實現GROUP BY操做,當發現某些狀況沒法知足鬆散索引掃描實現GROUP BY的要求以後,纔會嘗試經過緊湊索引掃描來實現。當MySQL Query Optimizer發現僅僅經過索引掃描並不能直接獲得GROUP BY的結果以後,他就不得不選擇經過使用臨時表而後再排序的方式來實現GROUP BY了。當沒法使用索引完成GROUP BY的時候,因爲要使用到臨時表且須要filesort,因此咱們必需要有足夠的sort_buffer_size來供MySQL排序的時候使用,並且儘可能不要進行大結果集的GROUP BY操做,由於若是超出系統設置的臨時表大小的時候會出現將臨時表數據copy到磁盤上面再進行操做,這時候的排序分組操做性能將是成數量級的降低。此外,GROUP BY若是在沒法利用到索引的狀況下想避免filesort操做,能夠在整個語句最後添加一個以null排序(ORDER BY null)。

DISTINCT

DISTINCT實際上和GROUP BY的操做很是類似,只不過是在GROUP BY以後的每組中只取出一條記錄而已。因此,DISTINCT的實現和GROUP BY的實現也基本差很少,沒有太大的區別。一樣能夠經過鬆散索引掃描或者是緊湊索引掃描來實現,固然,在沒法僅僅使用索引即能完成DISTINCT的時候,MySQL只能經過臨時表來完成。可是和GROUP BY有一點差異的是,DISTINCT並不須要進行排序。也就是說,在僅僅只是DISTINCT操做的Query若是沒法僅僅利用索引完成操做的時候,MySQL會利用臨時表來作一次數據的「緩存」,可是不會對臨時表中的數據進行filesort操做。固然,若是咱們在進行DISTINCT的時候還使用了GROUP BY並進行了分組,並使用了相似於MAX之類的聚合函數操做,就沒法避免filesort了。

擴展

explain原理

explain並無真的去執行sql語句從而得出行數,而是進行了某種預估。

  • mysql-5.5以前:首先找到查詢第一個記錄所在的page(記爲PLeft),統計PLeft裏的記錄數(記爲Records_PLeft),以後找到最後一個記錄所在的page(記爲PRight),統計PRight的記錄數(Records_PRight),以後將Records_PLeft與Records_PRight取平均,最後乘以總共的page數目(記爲Page_Num)。公式以下:Rows = ((Records_PLeft + Records_PRight) / 2) * Page_Num。
  • mysql-5.5以後:因爲採樣的page數太少了,只採樣了邊界2個,存在比較大的誤差,新版本增長採樣數目,好比採樣10個page,具體來講,mysql除了邊界2個外,還沿着左側page往右連續查找8個page,若是總的page數目小於等於10個,那麼預估的Rows和真實的Rows一致。公式以下:Rows = ((Records_PLeft + Records_P1 + Records_P2 + ... + Records_P8 + Records_PRight) / 10) * Page_Num。

增長增長採樣數目,必定程度上緩解了有偏的問題,可是不許確仍是存在的。

參考:
淺談Mysql的B樹索引與索引優化
數據庫中的索引
程序員須要知道的SSD基本原理
InnoDB一棵B+樹能夠存放多少行數據?
MySQL鬆散索引掃描與緊湊索引掃描
mysql 原理:explain
MySQL order by,group by和distinct原理

相關文章
相關標籤/搜索