MySQL索引憑什麼能讓查詢效率提升這麼多?

背景

我相信你們在數據庫優化的時候都會說到索引,我也不例外,你們也基本上能對數據結構的優化回答個一二三,以及頁緩存之類的都能扯上幾句,可是有一次阿里P9的一個面試問我:你能從計算機層面開始說一下一個索引數據加載的流程麼?(就是想讓我聊IO)linux

我當場就去世了....由於計算機網絡和操做系統的基礎知識真的是個人盲區,不事後面我惡補了,廢話很少說,咱們就從計算機加載數據聊起,講一下換個角度聊索引。c++


若是以爲看完文章有所收穫的話,能夠關注我一下哦
知乎:禿頂之路
b站:linux亦有歸途
天天都會更新咱們的公開課錄播以及編程乾貨和大廠面經
或者直接點擊連接c/c++ linux服務器開發高級架構師
來課堂上跟咱們講師面對面交流
須要大廠面經跟學習大綱的小夥伴能夠加羣973961276獲取面試


正文

MySQL的索引本質上是一種數據結構

讓咱們先來了解一下計算機的數據加載。數據庫

磁盤IO和預讀:

先說一下磁盤IO,磁盤讀取數據靠的是機械運動,每一次讀取數據須要尋道、尋點、拷貝到內存三步操做。編程

尋道時間是磁臂移動到指定磁道所須要的時間,通常在5ms如下;緩存

尋點是從磁道中找到數據存在的那個點,平均時間是半圈時間,若是是一個7200轉/min的磁盤,尋點時間平均是600000/7200/2=4.17ms;服務器

拷貝到內存的時間很快,和前面兩個時間比起來能夠忽略不計,因此一次IO的時間平均是在9ms左右。聽起來很快,但數據庫百萬級別的數據過一遍就達到了9000s,顯然就是災難級別的了。網絡

考慮到磁盤IO是很是高昂的操做,計算機操做系統作了預讀的優化,當一次IO時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到。數據結構

每一次IO讀取的數據咱們稱之爲一頁(page),具體一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數據時候,實際上才發生了一次IO。架構

(忽然想到個我剛畢業被問過的問題,在64位的操做系統中,Java中的int類型佔幾個字節?最大是多少?爲何?)

那咱們想要優化數據庫查詢,就要儘可能減小磁盤的IO操做,因此就出現了索引。

索引是什麼?

MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。

MySQL 中經常使用的索引在物理上分兩類,B-樹索引和哈希索引。

本次主要講BTree索引。

BTree索引

BTree又叫多路平衡查找樹,一顆m叉的BTree特性以下:

  • 樹中每一個節點最多包含m個孩子。
  • 除根節點與葉子節點外,每一個節點至少有[ceil(m/2)]個孩子(ceil()爲向上取整)。
  • 若根節點不是葉子節點,則至少有兩個孩子。
  • 全部的葉子節點都在同一層。
  • 每一個非葉子節點由n個key與n+1個指針組成,其中[ceil(m/2)-1] <= n <= m-1 。
![](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600"></svg>)

這是一個3叉(只是舉例,真實會有不少叉)的BTree結構圖,每個方框塊咱們稱之爲一個磁盤塊或者叫作一個block塊,這是操做系統一次IO往內存中讀的內容,一個塊對應四個扇區,紫色表明的是磁盤塊中的數據key,黃色表明的是數據data,藍色表明的是指針p,指向下一個磁盤塊的位置。

來模擬下查找key爲29的data的過程:

一、根據根結點指針讀取文件目錄的根磁盤塊1。【磁盤IO操做1次

二、磁盤塊1存儲17,35和三個指針數據。咱們發現17<29<35,所以咱們找到指針p2。

三、根據p2指針,咱們定位並讀取磁盤塊3。【磁盤IO操做2次

四、磁盤塊3存儲26,30和三個指針數據。咱們發現26<29<30,所以咱們找到指針p2。

五、根據p2指針,咱們定位並讀取磁盤塊8。【磁盤IO操做3次

六、磁盤塊8中存儲28,29。咱們找到29,獲取29所對應的數據data。

因而可知,BTree索引使每次磁盤I/O取到內存的數據都發揮了做用,從而提升了查詢效率。

可是有沒有什麼可優化的地方呢?

咱們從圖上能夠看到,每一個節點中不只包含數據的key值,還有data值。而每個頁的存儲空間是有限的,若是data數據較大時將會致使每一個節點(即一個頁)能存儲的key的數量很小,當存儲的數據量很大時一樣會致使B-Tree的深度較大,增大查詢時的磁盤I/O次數,進而影響查詢效率。

B+Tree索引

B+Tree是在B-Tree基礎上的一種優化,使其更適合實現外存儲索引結構。在B+Tree中,全部數據記錄節點都是按照鍵值大小順序存放在同一層的葉子節點上,而非葉子節點上只存儲key值信息,這樣能夠大大加大每一個節點存儲的key值數量,下降B+Tree的高度。

![](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600"></svg>)

B+Tree相對於B-Tree有幾點不一樣:

非葉子節點只存儲鍵值信息, 數據記錄都存放在葉子節點中, 將上一節中的B-Tree優化,因爲B+Tree的非葉子節點只存儲鍵值信息,因此B+Tree的高度能夠被壓縮到特別的低。

具體的數據以下:

InnoDB存儲引擎中頁的大小爲16KB,通常表的主鍵類型爲INT(佔用4個字節)或BIGINT(佔用8個字節),指針類型也通常爲4或8個字節,也就是說一個頁(B+Tree中的一個節點)中大概存儲16KB/(8B+8B)=1K個鍵值(由於是估值,爲方便計算,這裏的K取值爲〖10〗^3)。

也就是說一個深度爲3的B+Tree索引能夠維護10^3 10^3 10^3 = 10億 條記錄。(這種計算方式存在偏差,並且沒有計算葉子節點,若是計算葉子節點實際上是深度爲4了)

咱們只須要進行三次的IO操做就能夠從10億條數據中找到咱們想要的數據,比起最開始的百萬數據9000秒不知道好了多少個華萊士了。

並且在B+Tree上一般有兩個頭指針,一個指向根節點,另外一個指向關鍵字最小的葉子節點,並且全部葉子節點(即數據節點)之間是一種鏈式環結構。因此咱們除了能夠對B+Tree進行主鍵的範圍查找和分頁查找,還能夠從根節點開始,進行隨機查找。

數據庫中的B+Tree索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index)。

上面的B+Tree示例圖在數據庫中的實現即爲彙集索引,彙集索引的B+Tree中的葉子節點存放的是整張表的行記錄數據,輔助索引與彙集索引的區別在於輔助索引的葉子節點並不包含行記錄的所有數據,而是存儲相應行數據的彙集索引鍵,即主鍵。

當經過輔助索引來查詢數據時,InnoDB存儲引擎會遍歷輔助索引找到主鍵,而後再經過主鍵在彙集索引中找到完整的行記錄數據。

![](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600"></svg>)

不過,雖然索引能夠加快查詢速度,提升 MySQL 的處理性能,可是過多地使用索引也會形成如下弊端

  • 建立索引和維護索引要耗費時間,這種時間隨着數據量的增長而增長。
  • 除了數據表佔數據空間以外,每個索引還要佔必定的物理空間。若是要創建聚簇索引,那麼須要的空間就會更大。
  • 當對錶中的數據進行增長、刪除和修改的時候,索引也要動態地維護,這樣就下降了數據的維護速度。
注意:索引能夠在一些狀況下加速查詢,可是在某些狀況下,會下降效率。

索引只是提升效率的一個因素,所以在創建索引的時候應該遵循如下原則:

  • 在常常須要搜索的列上創建索引,能夠加快搜索的速度。
  • 在做爲主鍵的列上建立索引,強制該列的惟一性,並組織表中數據的排列結構。
  • 在常用錶鏈接的列上建立索引,這些列主要是一些外鍵,能夠加快錶鏈接的速度。
  • 在常常須要根據範圍進行搜索的列上建立索引,由於索引已經排序,因此其指定的範圍是連續的。
  • 在常常須要排序的列上建立索引,由於索引已經排序,因此查詢時能夠利用索引的排序,加快排序查詢。
  • 在常用 WHERE 子句的列上建立索引,加快條件的判斷速度。

如今你們知道索引爲啥能這麼快了吧,其實就是一句話,經過索引的結構最大化的減小數據庫的IO次數,畢竟,一次IO的時間真的是過久了。。。

總結

就面試而言不少知識其實咱們能夠很容易就掌握了,可是要以學習爲目的,你會發現不少東西咱們得深刻到計算機基礎上才能發現其中奧祕,不少人問我怎麼記住這麼多東西,其實學習自己就是一個很無奈的東西,既然咱們不能不學那爲啥很差好學?去學會享受呢?最近我也在惡補基礎,後面我會開始更新計算機基礎和網絡相關的知識的。

相關文章
相關標籤/搜索