Mysql簡敘

什么是索引

對于索引的定義你可能并不知道,但是我們日常生活中無時不刻都有用到。當你打電話給某人時,手機通訊錄會按照名字的首字母分組排序,然后你就能根據用戶名很快的找到對應的手機號。在看書時我們想找到相關章節我們會通過書中的目錄來找到對應的頁碼,然后直接翻到我們想看的內容。像通訊錄、目錄等等這些都是索引,而索引的作用也很明顯,它就是為了提高查詢的效率。你試想一下,如果讓你找到字典中的一個字,你只能從第一頁開始找一只到找到這個字,這個效率是有多低。

在關系型數據庫中,索引指的就是對數據庫中一列或者多列值就行排序的存儲結構。而索引的加入,能讓數據庫查詢速度得到質的改變,特別是對于數據量大的表。提高查詢效率這個是索引的作用也是我們大多數程序追求的目標,但是索引也會帶來一些負面影響。例如索引需要占用磁盤空間,簡單的說索引也是一種數據,它也是需要存儲在服務器上。同時當原始數據進行插入和修改時,需要更新索引數據,這會導致程序的寫效率的下降。所以如果正確的使用索引就顯得更加重要了,正確合理的使用索引能大大提高我們程序的運行效率,反之即不能提升程序的效率,反而降低了系統寫的效率。

索引類型

上面了解了什么是索引以及索引的作用,接下來我們來聊聊索引的類型。實現索引的方式不同從而導致索引的類型不同,不同的索引類型所適應的場景也不同。例如我們可以使用二叉樹來實現索引,使用hash來實現索引,還可以使用B+Tree樹來實現索引等等。

為什么有這么多種方式來實現索引呢?難道就沒有一種最好的嗎?答案是沒有絕對最好的實現方式,只有在某些場景中最合適的。下面我們列舉幾種在Mysql中比較常用的索引類型,需要注意的是,Mysql的索引是在存儲引擎層面實現的,所以不同的存儲引擎在實現細節上會有差別,而且并不是所有引擎都支持所有的索引。

B+Tree

數據頁

為了讓你更好的了解B+Tree索引,我們這里先了解一下數據頁。InnoDB中管理存儲空間的最小單位就是數據頁,這個大小默認是16K。數據頁的類型有很多種,但是我們這里只關注表中數據存儲的頁類型,官方把這種頁叫做索引頁。這個數據頁的結構長啥樣呢?


頁數據結構

上面就是一個數據頁的大致結構,實際上會比這個復雜一點,不過這里我們關注三塊,表中的數據,空閑空間,Page Directory。表中的數據就是我們數據庫中表里面的數據,表中的記錄數據按照順序被存放在這個里面。空閑空間就是這個頁中還沒有用完的空間,每次當我們向表中插入一條數據,空間空間的大小就變小一點。

Page Directory是用來加快頁中數據查詢的,請注意是頁中的數據,因為頁的大小有16K,其他結構并占不了多大的空間,大部分空間是用來存儲用戶的數據的,所以為了加快在一個頁內搜索速度才有了Page Directory,你可以簡單的認為在一個頁內搜索速度就是用的二分法,想想是不是查詢很快呢。

那如果我的頁里面的數據存滿了呢?不用說那就再創建一個新的頁唄。整個過程如下圖所示:


頁創建過程

注意這里面只是一個簡圖,為了便于理解我這里省去數據頁的其他結構,只保留了用戶數據。而實際上這些頁的頭部會保存上一頁的頁號和下一頁的頁號。正是通過這些信息,所有的頁被一個個的串起來了,就是像雙向鏈表一樣。

下面我們用以下的表結構來作為示例演示:

CREATE TABLE `t_student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '名字',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

而對應的數據頁如下:


頁內數據和頁之間的結構

從上面的數據頁編號可以看出,為什么不是遞增的呢?因為在物理上這些數據頁并不是連續的,它只能保證頁內的數據是物理上連續的。再說如果表中存儲了很多數據,要保證這些頁全部是物理上連續的也不太現實。這里說的的連續是指在硬盤上物理連續,因為順序I/O和隨機I/O它們的效率是不一樣的,特別是針對于機械硬盤。

索引

如果現在我們要通過ID查詢一條數據該怎么辦呢?如果不做任何優化的情況下你只能從第一個數據頁開始遍歷,然后在頁內你可以通過Page Directory快速查找你要找的數據是不是在其中。如果找到了就返回,如果沒找到繼續讀取下一個數據頁,然后查找,如此反復下去。

我們前面說過,數據頁之間在物理上不是連續的,這就會導致一個問題,如果數據位置靠后,那么會有大量的隨機I/O。

總結上面兩點我們現在的問題主要有兩點,第一我們無法快速的知道我們的數據存在那個數據頁中,只能通過從第一個數據頁節點往后依次遍歷(注意:頁內是能通過二分法快速定位的)。這樣也就導致了第二個問題,遍歷頁時大量隨機I/O。如果現在我們能快速找到我們的數據存在哪個頁,那是不是就能解決上面兩個問題呢?

二叉樹

如果我們將每個頁中最小值和頁號作為用戶數據,然后把這些數據放入到頁中形成一個二叉樹,二叉樹的查詢就跟二分法一樣,那是不是我們就能提高搜索速度呢?

使用二叉樹作為索引

按照我們上面說的,我們使用二叉樹做索引將數據存好了。現在如果我要搜索ID為12的同學,我該怎么搜呢?首先我不需要遍歷每個頁,我從第一層節點開始搜索,因為12最靠近7,所以選右節點頁21號;然后判斷12靠近10,所以選頁9號,然后再頁9號里面通過二分法找找到數據12號同學小藍(頁內查找)。

通過上面這種方法,我們原先需要經過讀取4次隨機I/O,然后進行4次頁內查找才能找到,現在變成3次隨機I/O,1次頁內查找。從上面我們可以知道,我們查詢的消耗為樹高度+1加上頁內查找,而每次讀取頁為隨機I/O。假設我們有100萬條數據,每頁能容納100條數據,對應的二叉樹葉子節點就為1萬個,那么樹的層高至少為14層(2^14),那么每次查詢需要15次隨機I/O和一次頁內查找。假設每次隨機I/O需要10ms,那么算下來15次隨機I/O需要耗時150毫秒,想象這該得有多慢,所以我們必須要降低樹的高度,這樣才能減少隨機I/O的次數。

B+Tree

上面我們知道如果想加快查詢速度還需要降低樹的高度,那么如何降低樹的高度呢?如果我們不是使用二叉樹,簡單的說就是節點不再是存儲兩個節點,而是存儲多個節點呢?這樣是不是就能降低樹的高度呢?

首先我們需要明白一點,我們對頁內數據的查找肯定是比多次讀去隨機I/O要快的,我們降低了樹的高度,減少了隨機I/O次數,增加了頁內搜索的次數。一次頁內搜索先比與多次隨機I/O讀取的消耗我們幾乎可以忽略不計。

使用B+Tree作為索引

按照我們之前的說法,我們將數據如上圖存放(PS:請饒恕我沒有耐心畫全,因為真的很麻煩,而且沒有合適的畫圖軟件,求推薦)。經過修改之后,我們還是假設存100萬條數據,每頁能存放100條數據。如果樹高為1,則我們能存100個頁,如果樹高為2,那我們能存1萬個頁(100100)。如果樹高為3,那能存100萬個頁(100100*100)。

現在再來分析我們的查詢消耗,我們需要3次隨機I/O加上一次頁內查找。之前忘記說了,我們的數據存在葉子節點,查找數據位于哪個頁需要搜索的次數為樹的高度次,最后還要讀取數據所在的頁,所以這又是一次隨機I/O,所以是樹的高度+1次。我們還是假設每次隨機I/O需要10ms,那么這次搜索大概需要30ms左右,相比于之前的150ms時間大大的減少了。

聚族索引

經過上面的分析,我想你對B+Tree索引有了了解。在Innodb中,我們常說到一句話即索引即數據,數據即索引,這里面講的就是聚族索引。我們將數據存放在數據頁中,這里的數據指的是表中的整行記錄,包括了所有字段,而數據本身就是索引。

聚族索引有兩個特點:使用主鍵的大小進行記錄和頁的排序。頁內的記錄是按照順序存放形成一個單鏈表。而各個存放用戶數據的頁也是根據頁內主鍵大小按順序存放形成一個雙向鏈表。存放頁信息的節點和存放用戶數據的節點不在同一個層級,在同一層的的頁也是根據主鍵大小形成按順序組成一個雙向鏈表。第二點就是整顆B+Tree樹存放了完整的用戶記錄。

二級索引

而作為二級索引就沒有這個待遇了,它只保存了索引列和主鍵的信息。例如下面的表:

CREATE TABLE `t_member` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
  `age` int(11) NOT NULL,
  `phone` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
  `sex` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `name_index` (`name`) USING BTREE,
  KEY `age_sex_index` (`age`,`sex`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

在這個表結構中存在一個主鍵索引ID,也就是我們前面說的聚族索引。而剩下的name_indexage_sex_index則是二級索引,所以在這個表中會有三顆B+Tree樹索引。其中name_index這顆樹頁中只存放了name和id字段信息,而age_sex_index這棵樹中只存放了age,sex和id字段信息。

回表

例如現在我按名字查找用戶SQL如下:

SELECT * FROM t_member WHERE `name` = '小明';

這個查詢會使用name_index索引,在該索引中查詢到數據之后就能獲取到這條數據的id,然后我們通過id再回到聚族索引中獲取到這條數據的全部信息,這個過程我們就叫回表

索引覆蓋

通過上面我們知道通過二級索引查詢數據是需要回表的,毫無疑問回表是有消耗的,有沒有什么辦法可以優化一下?答案是索引覆蓋。例如我通過姓名查找用戶這個需求,我只是想查找這個用戶的手機號,那么我們可以優化一下索引,我們創建一個由name和phone組成的索引,然后只返回name和phone這兩個字段。

ALTER TABLE `t_member`
DROP INDEX `name_index` ,
ADD INDEX `name_phone_index` (`name`, `phone`) USING BTREE ;

我們將查詢語句修改為如下:

SELECT `name`,phone FROM t_member WHERE `name` = '小明';

在上面的查詢中,我們可以通過name_phone_index這個二級索引直接獲取到name和phone信息,所以省去了去聚族索引中再次查找的必要,從而省去了回表的消耗,這種方式我們就叫做索引覆蓋

最左匹配

現在我們有個查詢如下:

SELECT * FROM t_member WHERE phone = '15202405150';

現在我想問的是這個查詢能用到索引嗎?如果看懂了B+Tree索引那么你的心里一定有了答案,這個是無法用到索引的。我們直接使用EXPLAIN分析結果看看:

expalin結果.png

從上面的結果可以看出確實用不到索引。對于索引name_phone_index而言,它在頁中的數據長這樣:

name_phone_index頁內數據

這時候的數據的排序規則是name+phone,在索引內能保證的是name+phone是有序的,而對于phone來說并不是有序的,同時對于name是有序的。如果我們通過name來查詢,這個時候是能用到該索引的,而這個原則我們就叫做最左匹配原則。所以在創建索引時,索引的順序是很重要的。

根據這個原則,我們可以省去一些不必要的索引。例如我們根據name創建了一個name_index索引,然后又根據name和phone創建了一個name_phone_index索引,這個時候我們去掉name_index索引。因為根據最左匹配原則,通過name查詢時可以利用到索引的,而name_index就沒有存在的必要了。

索引下推

例如現在我們有如下的查詢:

SELECT * FROM t_member WHERE `name` LIKE '陳%' AND phone = '111111'

這個查詢我們會利用到name_phone_index索引,但是這個索引使用會有一些限制,因為我們的查詢中存在name like '陳%',這樣會導致我們無法使用的phone來進行匹配了。那是不是在這個查詢中,phone這個字段的索引是不是就毫無作用了呢?答案是否定的。

回表過程

這個查詢會先通過name篩選到id為1和2的記錄滿足條件,然后接著根據id通過回表的方式到聚族索引中找到對應的記錄,然后判斷phone的條件。那能不能優化一下呢?

在Mysql5.6之前是沒有優化的,但是在5.6及以后的版本中,Mysql會通過索引下推的****方式來****減少回表的次數。在name_phone_index中篩選name符合要求的數據后,然后會接著在該索引中判斷phone是不是滿足條件,從而減少了回表的次數。在本示例中,它會取消藍色線這次回表,因為在name_phone_index中就能判斷出該記錄不服和條件了。

哈希

哈希索引是基于哈希表實現的,只有精確匹配了所有索引列的查詢才有效。簡單的說,對于索引列的每行數據都會計算出一個哈希碼(hashcode)。對于相同的數據其計算出的hash碼是唯一的,但是需要注意的是不同的哈希碼計算出來的數據也有可能相同,這個取決于哈希算法。算出哈希碼之后,會在哈希表中保存一個指向原始數據的指針,然后通過該指針獲取到原始數據。而hash索引適合的是精確值的搜索,這個是它的優勢。但是它無法使用像like查詢,范圍查詢等。

其實哈希索引跟Java中的HashMap很像,不過需要注意的是Innodb引擎是不支持hash索引的,在Mysql中Memory的默認索引類型就是Hash。鑒于我們使用的最多的是Innodb引擎,這部分只是就不具體提了。

全文索引

全文索引是一種特殊的索引類型,它查找的是文本中的關鍵字,而不是直接比較索引中的值。需要注意的是,全文檢索它并不是像like一樣的查詢,在like查詢中像'%world%'這種它是無法使用的索引的,即使像'world%'這種它能使用到索引也存在很大的限制。

考慮到該索引平常使用的較少這里面就不詳細的介紹了。因為在很多情況下有其他很好的代替產品,像ElasticSearch,Solr等這些能提供更好的全文檢索功能。如果項目只是需要簡單的使用全文檢索又不想引入其他組件,可以考慮使用Mysql中提供的全文索引。

總結

總的來說,Mysql中的索引有很多種類型,而且索引的實現是靠存儲引擎來實現的。即使索引都叫B+Tree它也會因為引擎的不同而有所不同,同時不同的引擎支持的索引類型也不相同。但實際上我們現在使用較多的引擎就是Innodb,而Innodb中使用最多的就是B+Tree索引。所以我們在學習過程中推薦先了解Innodb相關的索引知識,然后再去學習其他引擎的索引相關知識。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容