先來看到題,建表語句: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數據頁結構。數據庫
爲何須要索引呢?索引能夠幫助咱們解決什麼問題?數組
查字典你們都瞭解吧,碰到一個不認識的字,會用筆劃的方式進行查詢,以下圖數據結構
其實這個目錄就是索引,幫助你可以更快的按照某種規則查找到你須要的內容。試想下,若是沒有這個『筆劃索引』,若是要找一個字,怎麼辦?只能一頁一頁翻了,須要所有遍歷。若是字典的排序是按照筆劃數排序的,那麼你還能用二分查找的方式,加速查找過程。哈哈,扯遠了,回過來,索引的存在,其實就是將無序的數據變成有序(相對),提升檢索速度。post
你們都知道,InnoDB的索引使用的數據結構是B+樹,那麼爲何是B+樹呢?在介紹B+樹索引機制前,咱們先了解另外兩種數據解構,哈希和B樹。優化
說到索引,咱們很容易想到經過哈希的方式實現,時間複雜度O(1)。相信在你的平常業務代碼中,應該會常常出現下面的代碼,將一個list根據某個key轉化爲Map,其實就這個就是索引思想。ui
Map<Long, User> id2User = Maps.uniqueIndex(users, User::getId);
哈希索引示意圖:
鍵值key經過Hash映射找到bucket。在這裏bucket指的是一個能存儲一條或多條記錄的存儲單位。一個桶的結構包含了一個內存指針數組,其中的每一行數據都會指向下一條,造成鏈表結構,當遇到Hash衝突時,會在桶中進行鍵值的查找。
採用Hash進行檢索效率很是高,若是查找的字段創建了索引,那麼基本上一次檢索就能夠找到數據。可是你們忽略了,查找場景並不僅有精確檢索( where name=xxx ),還有範圍檢索、模糊檢索等等。
Hash索引主要存在如下缺點:
如圖所示,區別有如下兩點:
B+樹的優勢:
B樹的優勢:
能夠看到,B+樹的兩個優勢對於提高DB檢索效率都是很是有用的。第一個在必定大小的空間中能夠存放更多索引,下降樹的高度,即檢索時間。第二個在對於範圍掃描更加方便。
先看下mysql中一行記錄的結構示意圖
主要幾個部分:
record_type
:記錄頭信息的一項屬性,表示記錄的類型,0
表示普通記錄、1
表示目錄項、2
表示最小記錄、3
表示最大記錄next_type
:記錄頭信息的一項屬性,表示下一條地址的偏移量,爲了方便你們理解,咱們都會用箭頭來代表下一條記錄是誰。各個列的值
:就是各個數據列的值,其中咱們用橘黃色的格子表明c1
列,深藍色的格子表明c2
列,紅色格子表明c3
列。其餘信息
:除了上述3種信息之外的全部信息,包括其餘隱藏列的值以及記錄的額外信息。而後再看下單個頁的結構示意圖
3條普通記錄,一條最小記錄,一條最大記錄,5條記錄造成單向鏈表。
上面說的每一個頁的大小是16KB,爲了便於說明問題,假設每頁只能放3條普通記錄,增長記錄就須要增長頁,下面是多頁結構示意圖:
幾點須要注意
由於這些16KB
的頁在物理存儲上並不挨着,因此若是想從這麼多頁中根據主鍵值快速定位某些記錄所在的頁,咱們須要給它們作個目錄,每一個頁對應一個目錄項,每一個目錄項包括下邊兩個部分:
key
來表示。page_no
表示。
而後咱們看下如何尋找id=20的記錄。
20
的記錄在目錄項3
中(由於 12 < 20 < 209
),它對應的頁是頁9
。頁9
中根絕二分法快速定位具體的記錄。這樣是否是查找數據就快了?嗯,正是這個目錄的功勞!對了,忘記說了,這個目錄有個別名,叫索引。
下面再看一個高度爲3的B+樹索引:
從圖中能夠看出來,一個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+樹
上面提到了聯合索引,聯合索引有一個最左匹配原則,介紹以下:
(a)
,也能夠複雜如多個列(a, b, c, d)
,即聯合索引。(>、<、between、like
左匹配)等就不能進一步匹配了,後續退化爲線性查找。例子:
(a, b, c, d)
,查詢條件a = 1 and b = 2 and c > 3 and d = 4
,則會在每一個節點依次命中a、b、c,沒法命中d。(很簡單:索引命中只能是相等的狀況,不能是範圍匹配)不須要考慮=、in等的順序,mysql會自動優化這些條件的順序,以匹配儘量多的索引列。
例子:
(a, b, c, d)
,查詢條件c > 3 and b = 2 and a = 1 and d < 4
與a = 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。