JAVA面試系列 - MySQL InnoDB 索引介紹

概述

先來看到題,建表語句:html

create table user(
    `id` bigint auto_increment COMMENT '主鍵ID',
    `age` int not null COMMENT '年齡',
    `name` varchar(1024) not null COMMENT '姓名',
      `country` varchar(1024) not null COMMENT '國家',
      `city` varchar(1024) not null COMMENT '城市',
      PRIMARY KEY (`id`),
    KEY `IDX_NAME` (`name`),
    KEY `IDX_AGE` (`age`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶信息';

除了主鍵外,創建了2個索引,name和age,那麼下面兩句sql分別會命中哪些索引呢?java

select * from user where name='yang' and age=10;
select * from user where name='yang' or age=10;

若是對這道題沒啥思路的,那麼就往下看吧,看完本文後相信答案也就出來了。mysql

InnoDB是一個將表中的數據存儲到磁盤上的存儲引擎,因此即便關機後重啓咱們的數據仍是存在的。而真正處理數據的過程是發生在內存中的,因此須要把磁盤中的數據加載到內存中,若是是處理寫入或修改請求的話,還須要把內存中的內容刷新到磁盤上。而咱們知道讀寫磁盤的速度很是慢,和內存讀寫差了幾個數量級,因此當咱們想從表中獲取某些記錄時,InnoDB存儲引擎須要一條一條的把記錄從磁盤上讀出來麼?不,那樣會慢死,InnoDB採起的方式是:將數據劃分爲若干個頁,以頁做爲磁盤和內存之間交互的基本單位,InnoDB中頁的大小通常爲 16KB。也就是在通常狀況下,一次最少從磁盤中讀取16KB的內容到內存中,一次最少把內存中的16KB內容刷新到磁盤中。sql

這裏不對InnoDB的頁作過多介紹,有興趣的同窗能夠研究這兩篇文章:InnoDB記錄存儲結構InnoDB數據頁結構數據庫

索引

爲何須要索引呢?索引能夠幫助咱們解決什麼問題?數組

查字典你們都瞭解吧,碰到一個不認識的字,會用筆劃的方式進行查詢,以下圖數據結構

漢語筆劃檢索.png

其實這個目錄就是索引,幫助你可以更快的按照某種規則查找到你須要的內容。試想下,若是沒有這個『筆劃索引』,若是要找一個字,怎麼辦?只能一頁一頁翻了,須要所有遍歷。若是字典的排序是按照筆劃數排序的,那麼你還能用二分查找的方式,加速查找過程。哈哈,扯遠了,回過來,索引的存在,其實就是將無序的數據變成有序(相對),提升檢索速度。post

你們都知道,InnoDB的索引使用的數據結構是B+樹,那麼爲何是B+樹呢?在介紹B+樹索引機制前,咱們先了解另外兩種數據解構,哈希和B樹。優化

哈希索引

說到索引,咱們很容易想到經過哈希的方式實現,時間複雜度O(1)。相信在你的平常業務代碼中,應該會常常出現下面的代碼,將一個list根據某個key轉化爲Map,其實就這個就是索引思想。ui

Map<Long, User> id2User = Maps.uniqueIndex(users, User::getId);

哈希索引示意圖:

hash索引.png

鍵值key經過Hash映射找到bucket。在這裏bucket指的是一個能存儲一條或多條記錄的存儲單位。一個桶的結構包含了一個內存指針數組,其中的每一行數據都會指向下一條,造成鏈表結構,當遇到Hash衝突時,會在桶中進行鍵值的查找。

採用Hash進行檢索效率很是高,若是查找的字段創建了索引,那麼基本上一次檢索就能夠找到數據。可是你們忽略了,查找場景並不僅有精確檢索( where name=xxx ),還有範圍檢索、模糊檢索等等。

Hash索引主要存在如下缺點:

  1. 無法利用索引進行排序,即 ORDER BY,由於 Hash 索引指向的數據是無序的,所以沒法起到排序優化的做用。例如上述案例中須要按照name排序,雖然name字段創建了索引,可是不是有序的,因此對排序效率無任何正向做用。
  2. 不支持最左匹配原則,即聯合索引。由於Hash索引在計算Hash值時,是將索引鍵合併後再一塊兒計算Hash值,因此不會對每一個索引單獨計算Hash值,所以若是用到聯合索引的一個或幾個索引時,聯合索引沒法被利用。
  3. 不支持範圍查詢,一樣是由於 Hash 索引指向的數據是無序的,因此無法對 where id > 10 這樣的範圍查詢語句有任何效率提高的做用。

B樹索引

B樹和B+樹的區別

B樹和B+樹.png

如圖所示,區別有如下兩點:

  1. B+樹中只有葉子節點會帶有指向記錄的指針(ROWID),而B樹則全部節點都帶有,在內部節點出現的索引項不會再出如今葉子節點中。
  2. B+樹中全部葉子節點都是經過指針鏈接在一塊兒,而B樹不會。

B+樹的優勢:

  1. 非葉子節點不會帶上ROWID,這樣,一個塊中能夠容納更多的索引項,一是能夠下降樹的高度。二是一個內部節點能夠定位更多的葉子節點。
  2. 葉子節點之間經過指針來鏈接,範圍掃描將十分簡單,而對於B樹來講,則須要在葉子節點和內部節點不停的往返移動。

B樹的優勢:

  1. 對於在內部節點的數據,可直接獲得,沒必要根據葉子節點來定位。

能夠看到,B+樹的兩個優勢對於提高DB檢索效率都是很是有用的。第一個在必定大小的空間中能夠存放更多索引,下降樹的高度,即檢索時間。第二個在對於範圍掃描更加方便。

B+樹索引

索引結構

單條記錄的結構

先看下mysql中一行記錄的結構示意圖

記錄結構示意圖.png

主要幾個部分:

  • record_type:記錄頭信息的一項屬性,表示記錄的類型,0表示普通記錄、1表示目錄項、2表示最小記錄、3表示最大記錄
  • next_type:記錄頭信息的一項屬性,表示下一條地址的偏移量,爲了方便你們理解,咱們都會用箭頭來代表下一條記錄是誰。
  • 各個列的值:就是各個數據列的值,其中咱們用橘黃色的格子表明c1列,深藍色的格子表明c2列,紅色格子表明c3列。
  • 其餘信息:除了上述3種信息之外的全部信息,包括其餘隱藏列的值以及記錄的額外信息。

單頁結構

而後再看下單個頁的結構示意圖

單頁索引結構.png

3條普通記錄,一條最小記錄,一條最大記錄,5條記錄造成單向鏈表。

多頁結構

上面說的每一個頁的大小是16KB,爲了便於說明問題,假設每頁只能放3條普通記錄,增長記錄就須要增長頁,下面是多頁結構示意圖:

多頁結構示意圖.png

幾點須要注意

  • 下一個數據頁的主鍵值必須大於上一個頁中的主鍵值
  • 新分配的數據頁編號可能並非連續的,也就是說咱們使用的這些頁在存儲空間裏可能並不挨着。它們只是經過維護着上一個頁和下一個頁的編號而創建了鏈表關係
  • 單頁之間是單向鏈表,多頁之間是雙向鏈表

由於這些16KB的頁在物理存儲上並不挨着,因此若是想從這麼多頁中根據主鍵值快速定位某些記錄所在的頁,咱們須要給它們作個目錄,每一個頁對應一個目錄項,每一個目錄項包括下邊兩個部分:

  • 頁的用戶記錄中最小的主鍵值,咱們用key來表示。
  • 頁號,咱們用page_no表示。

索引目錄.png

而後咱們看下如何尋找id=20的記錄。

  1. 先從目錄項中根據二分法快速肯定出主鍵值爲20的記錄在目錄項3中(由於 12 < 20 < 209),它對應的頁是頁9
  2. 再在頁9中根絕二分法快速定位具體的記錄。

這樣是否是查找數據就快了?嗯,正是這個目錄的功勞!對了,忘記說了,這個目錄有個別名,叫索引

下面再看一個高度爲3的B+樹索引:

innodb B+樹索引.png

從圖中能夠看出來,一個B+樹的節點其實能夠分紅好多層,設計InnoDB的大叔們爲了討論方便,規定最下邊的那層,也就是存放咱們用戶記錄的那層爲第0層,以後依次往上加。上邊咱們假設一頁只能放3條普通記錄,其實真實環境中一個頁存放的記錄數量是很是大的,假設,假設,假設全部的數據頁,包括存儲真實用戶記錄和目錄項記錄的頁,均可以存放1000條記錄,那麼:

  • 若是B+樹只有1層,也就是隻有1個用於存放用戶記錄的節點,最多能存放1000條記錄。
  • 若是B+樹有2層,最多能存放1000×1000=1000000條記錄。
  • 若是B+樹有3層,最多能存放1000×1000×1000=1000000000條記錄。
  • 若是B+樹有4層,最多能存放1000×1000×1000×1000=1000000000000條記錄。哇咔咔~這麼多的記錄!!!

你的表裏能存放1000000000000條記錄麼?因此通常狀況下,咱們用到的B+樹都不會超過4層,那咱們經過主鍵去查找某條記錄最多隻須要作4個頁面內的查找,又由於在每一個頁面內有所謂的Page Directory(頁目錄),因此在頁面內也能夠經過二分法實現快速定位記錄,是否是很高效!

彙集和非彙集索引

下面看個例子,表建立語句以下:

create table user(
    `id` bigint auto_increment COMMENT '主鍵ID',
    `age` int not null COMMENT '年齡',
    `name` varchar(1024) not null COMMENT '姓名',
      `country` varchar(1024) not null COMMENT '國家',
      `city` varchar(1024) not null COMMENT '城市',
      PRIMARY KEY (`id`),
    KEY `IDX_NAME` (`name`),
    KEY `IDX_COUNTRY_CITY` (`country`, `city`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶信息';

能夠看到一共創建了3個索引,主鍵id索引、name索引、country+city的聯合索引。其中id就是彙集索引,name就是非彙集索引(二級索引)。

簡單歸納:

  • 彙集索引就是以主鍵建立的索引
  • 非彙集索引就是以非主鍵建立的索引

區別:

  • 彙集索引在葉子節點存儲的是表中的數據
  • 非彙集索引在葉子節點存儲的是主鍵和索引列
  • 使用非彙集索引查詢出數據時,拿到葉子上的主鍵再去查到想要查找的數據。(拿到主鍵再查找這個過程叫作回表)

以上有3個索引,因此會有3顆B+樹

  1. 彙集索引樹,索引是主鍵id,即上面圖中的黃色框對應記錄中的id字段,而後在葉子節點中存儲的是表中數據,即有多少個字段就會在黃色方塊下面有多少個數據方塊
  2. 非彙集索引樹,索引是字段name,在葉子節點中存儲的是主鍵id,即黃色方塊後面只會有一個方塊,存儲主鍵id,而後若是要獲取除id和name外的字段字段,須要在作一次檢索,即回表
  3. 非彙集索引樹,索引字段是country+city,和狀況2相似,知識索引字段有2個方塊,而後葉子節點會存儲主鍵id,因此葉子節點中數據方塊是3個。以下圖

聯合索引.png

索引最左匹配原則

上面提到了聯合索引,聯合索引有一個最左匹配原則,介紹以下:

  • 索引能夠簡單如一個列(a),也能夠複雜如多個列(a, b, c, d),即聯合索引
  • 若是是聯合索引,那麼key也由多個列組成,同時,索引只能用於查找key是否存在(相等),遇到範圍查詢(>、<、between、like左匹配)等就不能進一步匹配了,後續退化爲線性查找。
  • 所以,列的排列順序決定了可命中索引的列數

例子:

  • 若有索引(a, b, c, d),查詢條件a = 1 and b = 2 and c > 3 and d = 4,則會在每一個節點依次命中a、b、c,沒法命中d。(很簡單:索引命中只能是相等的狀況,不能是範圍匹配)

=、in自動優化順序

不須要考慮=、in等的順序,mysql會自動優化這些條件的順序,以匹配儘量多的索引列。

例子:

  • 若有索引(a, b, c, d),查詢條件c > 3 and b = 2 and a = 1 and d < 4a = 1 and c > 3 and b = 2 and d < 4等順序都是能夠的,MySQL會自動優化爲a = 1 and b = 2 and c > 3 and d < 4,依次命中a、b、c。

參考文檔

相關文章
相關標籤/搜索