完全理解 MySQL 的索引機制,終於再也不由於 MySQL 優化而被面試官鄙視了

前言html

每當咱們遇到數據庫查詢耗時過長,總會第一時間想到,在常用的條件上添加索引。咱們知道索引會幫咱們更快地查詢到想要的數據,可是咱們真的清楚究竟什麼是索引,爲何索引能幫咱們將查詢時間縮短十倍百倍甚至更多嗎?接下來請你們根據下文,一塊兒深刻索引的世界吧。mysql

從磁盤上獲取數據,講究在哪兒消耗了時間?

什麼是磁盤 IO?面試

磁盤讀取數據靠的是機械運動,通常來講,一次讀取數據的時間 = 尋道時間 + 旋轉延遲 + 傳輸時間。sql

在這裏插入圖片描述

在這裏插入圖片描述

名詞解釋數據庫

  • 尋道時間:磁頭移動到指定磁道所須要的時間,主流磁盤通常在 5ms 如下,平均 3-15ms。
  • 旋轉延遲:指盤片旋轉將請求數據所在的扇區移動到讀寫磁盤下方所須要的時間。磁盤轉速,好比一個磁盤 7200 轉,表示每分鐘能轉 7200 次,也就是說 1 秒鐘能轉 120 次,旋轉延遲就是 1/120/2 = 4.17ms。
  • 傳輸時間:從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,遠遠小於前面消耗的時間,幾乎能夠忽略不計。

什麼是預讀?數組

由於磁盤 IO 是很是昂貴的操做,因此計算機系統對此作了一些優化,當一次 IO 時,不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,由於局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰的數據也會很快被訪問到,因此每次讀取頁的整數倍(一般一個節點就是一頁)。緩存

什麼是索引,索引的數據結構爲什麼選擇 B+Tree?

定義網絡

索引是幫助 MySQL 高效獲取數據的數據結構。總結一下,索引是一種排好序的數據結構。數據結構

索引存儲在文件裏,以下圖所示(針對 InnoDB 而言):架構

在這裏插入圖片描述

InnoDB 的索引和數據都存放在同一文件中,而 MyIsAm 的索引和數據分別存放在不一樣的文件中。

咱們知道,MySQL 的 InnoDB 引擎下的索引數據結構爲 B+tree 和 hash,爲何在這麼多數據結構中會選擇 B+tree 和 hash 呢?

首先咱們先來了解一下如下四種數據結構(不會詳細分析,畢竟主題是 MySQL 索引)。

1. 二叉樹

特徵:要保證父節點大於左子結點,小於右子節點。

極端狀況下會產生以下所示的樹:

在這裏插入圖片描述

2. 平衡二叉樹(AVL)

特徵:它或者是一顆空樹,或者具備如下性質的二叉排序樹:它的左子樹和右子樹的深度之差 (平衡因子) 的絕對值不超過 1,且它的左子樹和右子樹都是一顆平衡二叉樹。

在這裏插入圖片描述

隨着數據的不斷增長,雖然平衡二叉樹在二叉樹的基礎上作了優化,可是樹的高度仍是會增長而且不可控。

3. 紅黑樹

特徵:紅黑樹,Red-Black Tree 「RBT」是一個自平衡 (不是絕對的平衡) 的二叉查找樹 (BST),樹上的每一個節點都遵循下面的規則:

  • 每一個節點都有紅色或黑色
  • 樹的根始終是黑色的
  • 沒有兩個相鄰的紅色節點(紅色節點不能有紅色父節點或紅色子節點, 並無說不能出現連續的黑色節點)
  • 從節點(包括根)到其任何後代 NULL 節點(葉子結點下方掛的兩個空節點,而且認爲他們是黑色的)的每條路徑都具備相同數量的黑色節點

在這裏插入圖片描述

和平衡二叉樹同樣,在數據量很大的狀況下,紅黑樹也沒法保證樹的高度。

4. B 樹

特徵(參考《數據結構》) :

  • 樹中的每一個結點最多含有 m 個孩子;
  • 除了根結點和葉子結點,其餘結點至少有 [ceil(m/2)(表明是取上限的函數)] 個孩子
  • 若根結點不是葉子結點時,則至少有兩個孩子(除了沒有孩子的根結點)
  • 全部的葉子結點都出如今同一層中,葉子結點不包含任何關鍵字信息

一顆 m=3 階的 B 樹以下所示:

在這裏插入圖片描述

B-tree 又作了優化,能夠在磁盤容量容許的狀況可控樹的高度。

可是,B-tree 每一個節點都保存了數據,所以在每個磁盤頁中所能存放的節點數就變得少了,而 B+tree 的中間節點不保存數據,因此磁盤頁能容納更多節點元素,更「矮胖」,將高度降得越低。

一個 m 階 B+ 樹的性質(和 B 樹有一些共同點,可是 B+ 樹具有一些新的特性):

  1. 有 K 個子樹的根節點和中間節點包含 K 個元素(B 樹種是 K-1 個元素),每一個元素不保存數據,只用來索引,全部的數據都保存在葉子節點上
  2. 全部的葉子節點包含了全部的元素的信息,且全部的葉子節點根據元素的大小從小到大組成一個鏈表
  3. 根節點以及全部的中間節點同時在於子節點,在子節點中是最大(或最小)元素

下圖是一個 3 階 B+ 樹:

在這裏插入圖片描述

B+ Tree 的優勢

查詢單個元素:

  • B+ 樹中間節點不存儲數據,所以一樣大小的磁盤頁能夠存儲更多的元素,當數據量相同的時候,B+ 樹要比 B 樹更加「矮胖」,所以 IO 次數更少
  • B 樹查詢時只要找到匹配的元素便可,性能不穩定(最好狀況是查找根節點,最壞狀況是查找葉子節點)。B+ 樹查詢必須最終查找到葉子節點(元素數據都在葉子節點中),所以 B+ 樹每次查找都是穩定的。

範圍查詢:

  • B 樹範圍查詢須要進行中序遍歷,B+ 樹只須要在葉子節點組成的鏈表上遍歷便可,比 B 樹要簡單不少。

從文首可知,從 MySQL 獲取數據消耗的時間主要是 IO 操做消耗的時間,所以減小 IO 操做次數,才能縮短獲取數據須要的時間,而通常獲取數據須要操做的 IO 次數等於樹的高度,因此減小樹的高度,也就是減小 IO 次數,從而達到減小獲取數據消耗的時間。

此時,假如咱們要查詢索引爲 35 的數據:

  1. 第一步:65>50 因此進入左邊節點
  2. 第二步:發現 65 在節點中,獲取到 65 的索引
  3. 第三步:經過索引直接定位到葉子節點上的數據

結論:咱們只經過三次 IO 操做就獲取到 65 的數據,大大的節省 IO 操做的時間,這在大數據量的狀況下,節省的時間更加不可想象。

爲何要選擇 B+ 樹

此時咱們的內心的流程是這樣的:如何減小獲取數據的時間 —-> 減小 IO 操做 ——> 如何減小 IO 操做 —> 減小樹的高度 —> 什麼樹能穩定的可控樹的高度 —>(B 樹和 B+ 樹)—> 那爲何選擇 B+ 樹 —–> 由於 B+ 樹節點不保存所有數據,所以在一頁(一個節點)上可以存更加多的索引數據,讓樹的高度更低。

還有一點很重要:

對於組合索引,B+tree 索引是按照索引列名 (從左到右的順序) 進行順序排序的,所以能夠將隨機 IO 轉換爲順序 IO 提高 IO 效率;而且能夠支持 order by/group 等排序需求;適合範圍查詢。

另外

MySQL 還支持 Hash 索引,可是 Hash 索引只能自適應,也就是說不能由咱們手動指定,只能在優化器階段,由優化器自主優化是使用 B+tree 仍是 Hash 結構的索引,所以 Hash 在此就不贅述了。

如何建立高性能索引?

1. 最左前綴匹配原則

特性解釋:

當 B+ 樹的數據項是複合的數據結構,好比(name、age、sex)的時候,B+ 數是按照從左到右的順序來創建搜索樹的,好比當 (張三,20,F) 這樣的數據來檢索的時候,B+ 樹會優先比較 name 來肯定下一步的所搜方向,若是 name 相同再依次比較 age 和 sex,最後獲得檢索的數據;但當 (20,F) 這樣的沒有 name 的數據來的時候,B+ 樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候 name 就是第一個比較因子,必需要先根據 name 來搜索才能知道下一步去哪裏查詢。 好比當 (張三,F) 這樣的數據來檢索時,B+ 樹能夠用 name 來指定搜索方向,但下一個字段 age 的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是 F 的數據了,這個是很是重要的性質,即索引的最左匹配特性。

經過實例來看

建立表,並創建組合索引:

在這裏插入圖片描述

在這裏插入圖片描述

上述 SQL 可使用到 (name,age,sex) 這個索引。

在這裏插入圖片描述

上述 SQL 可使用到 (name,age,sex) 這個索引中的 name,由於缺乏 age,因此也沒法使用到 sex。

在這裏插入圖片描述

上述 SQL 沒法使用到 (name,age,sex) 這個索引,由於缺乏最左列 name,違反了最左前綴原則。

2. 選擇區分度高的列做爲索引

經過

select count(Distinct columnName)/count(*) from Table

獲取這個列在表中的度,度的值範圍在 (0,1],度越大越好,主鍵索引的度爲 1,通常來講,咱們將度越大的列放在組合索引越左的位置上,以便於最快的速度過濾掉無效的數據。

度的值很難肯定,通常須要 join 的字段咱們都要求是 0.1 以上,即平均 1 條掃描 10 條記錄。

3. 前綴索引

如何建立前綴索引:

ALTER TABLE person ADD KEY(name(7));

前綴索引是針對大類型字段,好比 varchar、text、blob,若是使用這樣的列作索引的話,會很消耗內存資源,並且大而慢。並且 MySQL 不容許索引這些列的完整長度。

那麼咱們如何解決此類索引問題呢?

一般咱們能夠選擇索引開始的部分字符,這樣能夠大大的節約索引空間,從而提升索引效率,但這樣會下降索引的度。

那麼咱們如何選擇前綴,使得前綴的度接近於完成列的度,並且前綴又能足夠短(以便節約索引空間)。

//獲取完整列的度 A
SELECT COUNT(*) cnt,name FROM person GROUP BY name ORDER BY cnt DESC;
//獲取前N個字符的列的度 B
SELECT COUNT(*) cnt,LEFT(name,N) name FROM person GROUP BY name ORDER BY cnt DESC;

當 B 接近於 A 的時候,N 即爲該大字段所要設置的字符長度。

前綴索引:

ALTER TABLE person ADD KEY (name (N));

前綴索引的缺點:沒法使用前綴索引作 ORDER BY 和 GRUOP BY,也沒法使用前綴索引作覆蓋索引。

4. 索引列不能參與計算

在這裏插入圖片描述

上述 SQL 沒法使用到 (name,age,sex) 這個索引,由於 name 參與了計算,因此致使整個索引都沒法使用。

5. 儘可能的擴展索引,不要新建索引

索引的數目不是越多越好。每一個索引都須要佔用磁盤空間,索引越多,須要的磁盤空間就越大。修改表時,對索引的重構和更新很麻煩。越多的索引,會使更新表變得很浪費時間。

好比:表中已經有 name 的索引,如今要加 (name,age) 的索引,那麼只須要修改原來的索引便可

6. 重複索引和冗餘索引

重複索引:相同列上按照相同順序建立的相同類型的索引。

冗餘索引:已有索引 (name,age),如今 建立索引 (name) 就是一個冗餘索引,由於,索引 (name) 徹底能夠被 (name,age) 替代。然而 (name,age)、(age) 並非 (name,age) 的冗餘索引。

另外當 Id 列是主鍵,(name,Id) 是冗餘索引,由於二級緩存的葉子節點包含了主鍵值。直接使用 (name) 做爲索引便可。

如何進行慢查詢優化?

首先咱們來看下一個 SQL 的執行過程:

在這裏插入圖片描述

接下來爲你們介紹一個慢查詢優化神器——explain 命令。

explain 命令你們應該都很熟悉,具體使用說明你們能夠參照官網 explain 官網

而後爲你們介紹慢查詢優化基本步驟:

  1. 設置 SQL_NO_CACHE 後,查看 SQL 是否真的很慢
  2. 使用 explain 命令來查詢 MySQL 的查詢計劃
  3. 瞭解業務的使用場景
  4. (經過上面建立索引的規則)添加索引
  5. 分解關聯查詢
  6. 優化 limit 分頁
  7. 優化表結構

寫高性能 SQL,須要注意什麼?

1. SQL 語句中 IN 包含的值不該過多

MySQL 對於 IN 作了相應的優化,即將 IN 中的常量所有存儲在一個數組裏面,並且這個數組是排好序的。可是若是數值較多,產生的消耗也是比較大的。再例如:select id from table_name where num in (1,2,3) 對於連續的數值,能用 between 就不要用 in 了;再或者使用鏈接來替換。

**2. SELECT 語句儘可能使用具體列代替 ***

SELECT * 增長不少沒必要要的消耗(CPU、IO、內存、網絡帶寬);增長了使用覆蓋索引的可能性;當表結構發生改變時,前斷也須要更新。因此要求直接在 select 後面接上字段名。

3. 當能肯定查詢 n 條數據的時候(n 不宜過大),使用 limit n

這是爲了使 EXPLAIN 中 type 列達到 const 類型。

4.count()

count() 函數有兩種含義:統計行數、統計列數。

好比:count(*) 表明統計的行數;count(talbe.cloumn) 表明統計的是這個列不爲 null 的數量。

5.Union

須要將 where、order by、limit 這些限制放入到每一個子查詢,才能重分提高效率。另外如非必須,儘可能使用 Union all,由於 union 會給每一個子查詢的臨時表加入 distinct,對每一個臨時表作惟一性檢查,效率較差。

6. 關聯查詢優化

  1. 確保 ON 和 USING 字句中的列上有索引
  2. 確保任何的 GROUP BY 和 ORDER BY 中的表達式只涉及到一個表中的列,這樣 MySQL 纔有可能使用索引來優化。

7. 區分 in 和 exists, not in 和 not exists

in 和 exists 主要是形成了驅動順序的改變(這是性能變化的關鍵),若是是 exists,那麼之外層表爲驅動表,先被訪問,若是是 IN,那麼先執行子查詢。因此 IN 適合於外表大而內表小的狀況;EXISTS 適合於外表小而內表大的狀況。

關於 not in 和 not exists,推薦使用 not exists,不只僅是效率問題,not in 可能存在邏輯問題。

8. 不建議使用 % 前綴模糊查詢

Like "% name" 或者 LIKE "% name%",這種查詢會致使索引失效而進行全表掃描。可是可使用 LIKE "name%"。

9. 避免在 where 子句中對字段進行 null 值判斷

對於 null 的判斷會致使引擎放棄使用索引而進行全表掃描。

10. 分段查詢

在一些查詢中,可能一些查詢的時間範圍過大,形成查詢緩慢。主要的緣由是掃描行數過多。這個時候能夠經過程序,分段進行查詢,循環遍歷,將結果合併處理進行展現。

這些優化手段只是諸多優化手段裏面的不多的幾種,漫漫優化路,仍是須要咱們在之後的實戰中,慢慢積累。

總結

本文從數據結構層面深刻剖析了索引,解釋爲何在衆多數據結構中選擇了 B+ 樹,以及如何建立高性能的索引,並例舉了許多你們平時開發中時常遇到的 MySQL 優化案例,但願能給你們帶來幫助。

Spring Cloud 微服務精彩系列

  1. 阿里面試官問我:到底知不知道什麼是Eureka,此次,我沒沉默
  2. 萬字詳解Ribbon架構,針對面試高頻題多角度細說Ribbon
  3. 什麼是Hystrix,阿里技術最終面,遺憾的倒在Hystrix面前!
  4. 2萬字好文全方位深刻學習SpringCloud Fegin,面試不在彷徨
  5. Zuul,據說SpringCloud不許備要我了,但是爲何面試還要每天問我?
  6. Zuul,據說SpringCloud不許備要我了,但是爲何面試還要每天問我?

相關文章
相關標籤/搜索