1. 程式人生 > 其它 >MySQL一次查詢理論上需要幾次磁碟I/O?

MySQL一次查詢理論上需要幾次磁碟I/O?

MySQL的查詢需要遍歷幾次B+樹,理論上需要幾次磁碟I/O?

 

一、前言 這個問題是博主去年面試的時候被大佬問過的問題,當時也不大清楚裡面的原理,硬著頭皮回答的,當然,最終面試也沒過,哈哈。最近剛好研究了這塊的一些東西,就有種恍然大悟的感覺,這裡分享給大家,歡迎拍磚~

二、遍歷B+樹的次數 首先,既然問題是一次查詢,那我們肯定是要知道mysql使用的儲存引擎是哪個,要根據儲存引擎的不同判斷索引的結構,然後通過索引的B+樹來回答這些問題。 MySQL中MyISAM和InnoDB的索引方式以及區別與選擇

1、mysql的innodb引擎的聚集索引和非聚集索引 網上看到很多資料,有的叫innodb的索引為聚集索引,有的叫做聚簇索引,其實都是一樣的,只是在翻譯過來了時候命名產生了分歧,聚簇(集)索引的葉子節點就是資料節點,而非聚簇(集)索引的葉子節點仍然是索引節點,只不過有指向對應資料塊的指標。非聚簇(集)索引在innodb引擎中,又叫做二級索引,輔助索引等。

2、分別遍歷了幾次B+樹 主鍵索引從上至下遍歷一次B+樹,直到找到具體的主鍵,拿到葉子結點儲存的資料。 二級索引需要遍歷兩次B+樹,第一次遍歷是找到對應的主鍵,第二次遍歷是根據主鍵找到具體的資料。

比如查詢二級索引的sql,先通過遍歷二級索引的B+樹來找到對應的主鍵,然後回表即通過主鍵遍歷聚集索引B+樹,拿到具體的資料。(PS:mysql裡面每次新建索引都會生成新的B+樹,這也是索引檔案會隨著索引欄位不斷增加的原因)

這部分是要參照索引的圖來的,如圖:

(1)主鍵索引(聚集索引)

(2)輔助索引(二級索引)

3、回表的概念 回表就是通過輔助索引拿到主鍵id之後,要再去遍歷聚集索引的B+樹,這個過程就叫做回表。回表的操作更多的是隨機io,隨機io在效能上還是比較低下的,例如:

  • 比如user表中有三個欄位,a,b,c,給a和b建立聯合索引idex_a_b(a,b) sql:select * from user where a=1 and b=2; (1)首先是用二級索引index_a_b來查詢,速度會很快。(順序IO) (2)拿到主鍵id之後,這個主鍵id並不是順序排列的,還要用主鍵去查詢聚簇索引(隨機io) (3)當隨機io很多,也就是拿到的主鍵id很多的時候,回表的代價是巨大的。所以最好是選用覆蓋索引或者讓where 之後的條件篩選更多的資料

三、聚集索引和非聚集索引執行一次sql的io次數 1、聚集索引

大致步驟如下:

(1) 資料量小的話,直接把索引放到記憶體中,記憶體的O(logn)消耗是遠遠低於磁碟io的,所以可以忽略不計 (2) 資料量大的話,採用索引結構,我們這部分先從二叉樹說起,對於普通二叉樹,第一個步驟是二分,每次判斷都是一次半數的數量級檢索。假如有100W的資料,大概的時間複雜度是:log2N=1000000即N=20的節點獲取,也就是磁碟I/O複雜度最大為O(20),二分的時間複雜度是O(log2N)。 (3) 但是對於

資料庫來說,儲存場景會更加複雜,二叉樹的效能雖然好,但我們還是想要樹的高度更低一些,儲存的資料更多一些。因此mysql引入了B+樹的概念。除了根節點之外,第二層級的數量得到了充分的擴充套件,相對於普通的二叉樹,B+樹的結構更加龐大又不失美感,假設葉節點不同元素佔用情況為:左右指標各佔4Byte,id值8Byte,目標記錄指標4Byte,那麼一個4Kb的磁碟塊將大致可以容納250個下級指標,100萬行目標記錄只需log250N=1000000即N=3的I/O次數,充分提升了每次節點I/O帶來的檢索效用,時間複雜度是O(lognN),這裡的n是非葉子結點的個數。(PS:實際上innodb的資料頁大小是16kb,這個n會更大,那麼對應的,io次數也會更少) (4) 在實際的查詢中,IO次數可能會更小,因為有可能會把部分用到的索引讀取到記憶體中,相對於磁碟IO來說,記憶體的io消耗可以忽略不計。一般來說B+Tree的高度一般都在2-4層,MySQL的InnoDB儲存引擎在設計時是將根節點常駐記憶體的,也就是說查詢某一鍵值的行記錄時最多隻需要1~3次磁碟I/O操作(根節點的那次不算磁碟I/O)。

關於二分,我們假設有50W資料,下面看一下效果

1 50W 2 25W 3 12.5W 4 6W 5 3W 6 1.5W 7 7000 8 3500 9 1800 //第9次的時候,資料範圍就已經很小了,當然,它的效率還不夠高,但是比遍歷所有資料要快多了 根據以上的解釋,我們可以知道聚集索引的磁碟IO次數大概是1-3次,這一切都是因為高效的B+樹。如果大家也碰到類似的問題,就照著上面一頓胡扯,絕對很穩了。

2、輔助索引

(1) 參考上面對於B+樹的解釋,輔助索引獲取主鍵的時間複雜度是 lognN(假設第二層級是n個節點) (2) 再通過lognN獲取主鍵對應的資料列 參考 io解釋 四、引申問題 在進行相關測試的時候,可能會因為一部分索引放到了記憶體,從而造成一定的誤差。因此咱們這邊就來探討探討,這個放進記憶體的索引有多大。

1、多大的索引資料可以放到記憶體中?

(1) 要參照自己的mysql設定,一般是innodb_buffer_pool_size的值,這個值預設是128M,具體的要根據機器的效能設定。

關於Innodb_buffer_pool_size:《深入淺出MySQL》一文中這樣描述Innodb_buffer_pool_size:
該引數定義了 InnoDB 儲存引擎的表資料和索引資料的最大記憶體緩衝區大小。和 MyISAM 儲存引擎不同,
 MyISAM 的 key_buffer_size只快取索引鍵, 而 innodb_buffer_pool_size 卻是同時為資料塊和索引塊做快取,
這個特性和 Oracle 是一樣的。這個值設得越高,訪問 表中資料需要的磁碟 I/O 就越少。在一個專用的資料庫
 伺服器上,可以設定這個引數達機器 實體記憶體大小的 80%。儘管如此,還是建議使用者不要把它設定得太大,
因為對實體記憶體的競 爭可能在作業系統上導致記憶體排程。

(2) 記憶體緩衝區主要包含 上面第一條提到的記憶體緩衝區主要包括:資料快取(innodb的行資料),索引資料,緩衝資料(在記憶體中修改尚未重新整理(寫入)到磁碟的資料),內部結構(自適應雜湊索引,行鎖等。)

(3) 所以說,放到記憶體中的索引大小,和這些配置息息相關,當索引在記憶體中的時候,自然是用不到磁碟io的

具體參考: 如何在MySQL中分配innodb_buffer_pool_size

2、mysql一次普通查詢經過的步驟 從查詢過程上看,大致步驟是:

檢視快取中是否存在id,
 如果有 則從記憶體中訪問,否則要訪問磁碟,
並將索引資料存入記憶體,利用索引來訪問資料,
對於資料也會檢查資料是否存在於記憶體,
如果沒有則訪問磁盤獲取資料,讀入記憶體。
 返回結果給使用者。

據實際的情況分析。

五、總結 寫完部落格之後,越發感覺到自己的才疏學淺。對於mysql來說,這些本來就都是基礎知識,但我到現在才算明白了一點點。而且這一點點也許還不夠準確,實在是讓人絕望呢。翻看mysql的手冊,上面有很多介紹,也有很多配置項都沒有用過,各位且學且珍惜吧