【MySQL實戰45講】索引部分整理

本文摘抄自 極客時間【MySQL實戰45講】面試

爲何要有索引?索引的做用是什麼?算法

索引的出現其實就是爲了提升數據查詢的效率,就像書的目錄同樣。一本書咱們能夠經過目錄中快速的定位其中的某一個知識點;對於數據庫而言索引其實就是它的目錄,能夠經過索引快速的定位都某一條或多條記錄。數據庫

<!-- more -->數組

常見索引模型

Hash表

哈希表是一個以 鍵-值(key-value) 存儲數據的結構,咱們只要輸入待查找的值即 key,就能夠找到對應的值即 value。性能優化

結構特色

把值放在數組裏,用一個哈希函數把 key 轉換成一個肯定的位置,而後把 value 放在數組的這個位置。當多個 key 值通過哈希函數的換算會出現同一個值的狀況。這時候會拉出一個鏈表進行存儲。併發

案例

假設如今維護一個身份證信息和姓名的表,表示根據身份證查找對應的名字,這時對應的哈希索引示意圖以下函數

image.png

圖 1 哈希表示意圖性能

圖中,User2 和 User4 根據身份證號算出來的值都是 N,因此後邊跟了一個鏈表存儲。若是要找到 User2 對應的名字是什麼,首先經過哈希函數計算出 ID_card_n2 的值爲 N,而後按順序遍歷找到 User2。優化

在這裏四個 ID_card_n 的值並非遞增的,這樣作的好處就是增長的時候會很快,直接日後邊追加;缺點是數據的存儲並非有序的,因此在作區間查詢時會進行全表掃描,速度會很是慢。url

哈希表這個結構只適用於等值查詢。

有序數組

結構特色

將數據存放到數組中,數據在數組中是按順序存儲的,從左到右依次從大到小或從小到大。

案例

以哈希表中的例子,使用有序數組實現的結果以下

image.png

圖 2 有序數組示意圖

這裏假設身份證是沒有重複的,這個數組是按照身份證的遞增順序保存的。這時候若是刷要查詢 ID_card_n2 對應的姓名,用二分查找法能夠快速定位到這條記錄,同時若是要進行範圍查詢也是很快的,好比要查找身份證號在 [ID_card_X, ID_card_N] 區間的 User,能夠先用二分查找法找到 ID_card_X 的值,若是沒有則找到大於 ID_card_X 的第一個值,而後向右遍歷,知道找到第一個大於 ID_card_N 的值退出循環。

可是往數組中插入一條記錄的時候須要挪動後邊全部的元素,這樣的成本過高,一樣的刪除一條記錄也會致使後邊全部的元素往前挪動。

有序數組結構適用於等值和範圍查詢,可是插入和刪除的效率太慢。

二叉搜索樹

結構特色

二叉搜索樹每一個節點大於左兒子小於右兒子。

案例

上文例子,二叉搜索樹實現以下

image.png 圖 3 二叉搜索樹示意圖

若是咱們要找到 ID_card_n2 的話,根據二叉搜索樹的特色,按照圖中的搜索順序依次是:UserA→UserC→UserF→User2。

InnoDB索引模型

InnoDB 使用了 B+ 樹索引模型,數據都是存儲在 B+ 樹中的,每個索引都對應一棵 B+ 樹。

B-Tree

image.png

B樹 是一棵多路平衡樹且有如下特色:

  1. m階B樹 表示該樹每一個節點最多有 m 個孩子;
  2. 除根節點和葉子節點外,其它每一個節點至少有 ceil(m / 2) 個孩子;
  3. 若根節點不是葉子節點,則至少有兩個孩子;
  4. 全部葉子節點都在同一層,葉子節點不包含任何關鍵字;
  5. 每一個葉子節點包含 n 個關鍵字信息;
    • Ki(i = 1...n) 爲關鍵字,且關鍵字按順序升序排序 k(i-1)<Ki;
    • Pi 爲指向子樹根的節點,且指針 P(i-1) 指向子樹中全部節點的關鍵字均小於 Ki,但都大於 K(i-1);
    • 關鍵字個數 n 必須知足: ceil(m / 2) - 1 <= n <= m-1

image.png

B+樹做爲數據庫索引的優點

image.png

B+ Tree 是在 B-Tree 基礎上的優化,使其更適合實現外存儲索引結構,InnoDB引擎就是基於它實現索引結構,B+Tree 相對於 B-Tree 的優點:

  1. B+樹的磁盤讀取代價低:B+樹 全部的內部節點沒有關鍵字的具體信息(只存儲了key的信息),這樣可使內部節點相對更小。一個硬盤塊中包含的節點信息越多,一次性讀取內存中的關鍵字也就越多,相對來講就是 IO 讀寫次數的下降,也能夠說是每次 IO 操做的可觀看數據也就越多;
  2. B+樹便於執行掃庫操做:B樹在分支節點上都保存着數據,要找到具體的順序數據,必須用中序遍歷的方式按序掃庫;因爲B+樹的數據都存儲在葉子節點上,全部節點均爲索引,因此 B+樹 直接從葉子節點挨個掃一遍就完了;B+樹 支持範圍查詢(rang-query)很是方便,而B樹不支持;
  3. B+ 樹查詢效率更加穩定:因爲 B+樹 的數據都存儲在葉子節點上,分支節點均爲索引,因此對於任意關鍵字的查找都必須從根節點走到分支節點,全部關鍵字查詢路徑長度相同,每一個數據的查詢效率差很少。對於 B樹 而言,分支節點也保存有數據,對於每個數據的查詢所走的路徑長度也是不同的,效率也就不同;

B+Tree 與 B-Tree 的不一樣

  • 非葉子節點只存儲鍵值信息
  • 全部葉子節點之間都有一個鏈指針
  • 數據記錄都存放在葉子節點中

索引維護

image.png

B+樹 爲了維護索引的有序性,在插入新值的時候須要作必要的維護,若是插入新的行 ID 值爲 700,則只須要在 R5 的記錄後插入一個新的將是。若是插入值的 ID 爲400,須要邏輯上挪動後面的數據,空出位置。

若是 R5 所在的數據頁已經滿了,根據 B+樹 的算法,這時候須要申請一個新的數據頁,而後挪動部分數據過去,這個過程稱爲頁分裂。頁分裂還會影響數據頁的利用率,本來在一個頁的數據,如今分到兩個頁中,總體的空間利用率下降大約 50%。

當相鄰的兩個頁因爲刪除了數據,利用率很低以後,會將數據頁作合併,合併的過程能夠認爲是分裂過程的逆過程。

關於索引重建

對於如下這兩個重建索引的做法,說出你的理解。若是有不合適的,爲何,更好的方法是什麼?

// 方案1
alter table T drop index k;
alter table T add index(k);
// 方案2
alter table T drop primary key;
alter table T add primary key(id);

爲何重建索引?

索引可能由於刪除,或者頁分裂等緣由,致使數據頁有空洞,重建索引的過程會建立一個新的索引,把數據按順序插入,這樣頁面的利用率最高,也就是索引更緊湊、更省空間。

解答

重建主鍵的過程不合理:

  1. 不管是刪除仍是建立主鍵都會整個表重建
  2. 連續執行兩個語句,至關於第一個語句白作
  3. 兩個語句可使用 alter table T engine=InnoDB 代替

索引類型

主鍵索引

主鍵索引葉子節點中存儲的是整行數據,從物理結構的角度也叫 聚簇索引

非主鍵索引

  • 非主鍵索引的葉子節點存儲的是主鍵的值,從物理存儲的角度也叫 二級索引
  • 二級索引存儲主鍵,是爲了減小出現行移動或數據頁分裂時二級索引的維護工做,但會讓二級索引佔用更多的空間。

基於主鍵索引和普通查詢的查詢的區別

  • 主鍵索引:只須要搜索主鍵這棵樹。
  • 普通索引:先搜索普通索引對應的樹獲得ID,在根據ID在主鍵索引的樹上找到對應的數據,這個過程稱爲回表。

聚簇索引的注意點有哪些?

聚簇索引最大限度的提升了 IO 密集型應用的性能,但它也有限制:

  1. 插入速度嚴重依賴於插入順序,按照主鍵的順序插入是最快的,不然會出現頁分裂,嚴重影響性能。因此通常 InnoDB 表,都會定義一個自增的id做爲主鍵。

    面試問題:爲何主鍵須要自增ID,或者爲何主鍵須要帶有時間行關聯。

  2. 更新主鍵的代價很高,由於將會致使被更新的行移動。所以,InnoDB表通常主鍵不可更新。

    MySQL默認狀況下,主鍵是容許更新的。MongoDB主鍵是不容許更新的。

  3. 二級索引訪問須要回表。

    有種狀況無需二次查找,就是索引覆蓋。

  4. 主鍵ID建議使用整型。由於,每一個主鍵索引的 B+Tree 節點的鍵值能夠存儲更多主鍵ID,每一個非主鍵索引的 B+Tree 節點的數據能夠存儲更多的主鍵ID。

什麼場景適合使用業務字段直接作主鍵?

  • 只有一個索引
  • 該索引必須有惟一索引
  • KV場景,因爲沒有其餘索引,因此不用考慮其餘索引的葉子節點大小的問題
  • 此時直接將這個索引設置爲主鍵,能夠避免回表

覆蓋索引

若是執行的語句是 select ID from T where k between 3 and 5; ,這時只須要查 ID 的值,而ID的值已經在 k 索引樹上了,所以能夠直接拿到查詢結果,不須要回表。也就是說索引 k 已經覆蓋了咱們的查詢需求,咱們稱爲覆蓋索引

覆蓋索引的優點

覆蓋索引能夠減小樹的搜索次數,顯著提高查詢性能,因此使用覆蓋索引是一個經常使用的性能優化手段。

聯合索引

業務例子

一個市民信息表上,是否有必要將身份證號 和 名字 創建聯合索引?

若是有一個高配請求,要根據市民的身份照查詢他的姓名,這個聯合索引就頗有意義了。它能夠在這個高配請求上用到覆蓋索引,不須要回表。

最左前綴原則

image.png

前提

如今有這麼一個聯合索引,(name,age)。

索引項是按照索引定義裏面出現的字段順序排序的,不僅是索引的所有定義,只要知足最左前綴,就能夠利用索引來加速檢索。

  • 這個最左前綴能夠是聯合索引的最左N個字段
  • 也能夠是字符串索引的最左M個字符

例子

  • 查詢條件 name='張三',能夠快速定位到 ID4,而後向後遍歷獲得全部的結果集
  • 查詢條件 name like '張%' 時,此時也能夠用上索引,查找到 ID3,而後向後遍歷獲得全部結果集

創建索引時,如何安排索引內的字段排序?

  • 原則是,若是經過調整順序,能夠少維護一個索引,name這個順序每每就是須要優先考慮採用的。
  • 若是既有聯合索引,又有基於 a、b 各自的查詢。查詢條件裏只有 b 的查詢,是沒法使用 (a, b) 這個聯合索引的,此時就要同時維護 (a, b) ,(b) 這兩個索引。
  • 考慮空間問題,好比市民表,name 字段比 age 字段大,建議建立一個 (name, age) 和一個 (age)

索引建立合理性例子

create table `geek` (
	`a` int(11) not null,
	`b` int(11) not null,
	`c` int(11) not null,
	`d` int(11) not null,
	primary key (`a`, `b`),
	key `c` (`c`),
	key `ca` (`c`, `a`),
	key `cb` (`c`, `b`),
) engine=InnoDB;

既然主鍵包含了a、b這兩個字段,那意味着單獨在字段c上建立一個索引,就已經包含三個字段了,爲何要建立「ca」、「cb」這兩個索引?

同事告訴他,是由於他們的業務裏面有這樣的兩種語句:

select * from geek where c=N order by a limit 1;select * from geek where c=N order by b limit 1;

問題,這位同事解釋的對嗎,爲了這兩個查詢模式,這兩個索引是否都是必須的?爲何?

  • 索引ca 的組織是先按c 排序,再按a 排序,同時記錄主鍵,主鍵部分只有b,這個跟索引c的排序結果是同樣的
  • 索引cb 的組織是先按c 排序,再按b 排序,同時記錄主鍵,主鍵部分只有a
  • 因此,ca 能夠去掉,cb 須要保留

索引下推

此處仍是以市民表的聯合索引 (name, age) 爲例,需求爲 檢索出表中 名字第一個字是張,並且年齡是 10歲的全部男孩。

select * from tuser where name like '張%' and age=10 and ismale=1;

根據最左前綴原則,這個語句在搜索索引樹的時候,只能用 「張」,找到一個知足條件的記錄 ID3。而後判斷其餘條件是否知足條件。

在 MySQL5.6 以前,只能從 ID3 開始一個個的回表,到主鍵索引上找出數據行,在對比字段。

image.png

在 MyQL5.6 以後引入了索引下推優化(index condition pushdown),能夠在索引遍歷的過程當中,對索引中包含的字段先作判斷,直接過濾掉不知足條件的記錄,從而減小了回表的次數。

image.png

MyISAM索引實現

索引實現原理

MyISAM 引擎同 InnoDB 同樣,都是使用 B+Tree 做爲索引結構。差異在於:

  • InnoDB 的數據文件自己就是索引文件。MyISAM 索引文件和數據文件是分離開的,索引文件僅保存數據記錄的地址。

主鍵索引和輔助索引

image.png

image.png

上圖分別爲主索引和輔助索引,因爲 MyISAM 的索引文件中僅保存了數據的地址,因此在 MyISAM 中主索引和輔助索引在結構上沒有本質的區別,只是主索引要求 key 的惟一性,而輔助索引的 key 是能夠重複的。

因爲 MyISAM 的 data 域中保存的是數據記錄的地址,因此 MyISAM 索引檢索的算法爲首先按照 B+Tree 搜索算法搜索索引,若是指定的 key 存在,則取出 data 域的值,而後以 data 域的值爲地址,讀取相應的數據記錄。

MyISAM的索引方式也叫作「非彙集」的,之因此這麼稱呼是爲了與InnoDB的彙集索引區分。

MyISAM 與 InnoDB 的區別

  1. 事務處理上

    MyISAM:強調的是性能,查詢的速度比 InnoDB 類型更快,可是不提供事務支持。

    InnoDB:提供事務支持。

  2. 外鍵

    MyISAM:不支持外鍵。

    InnoDB:支持外鍵。

  3. MyISAM:只支持表級鎖。

    InnoDB:支持行級鎖和表級鎖,默認是行級鎖,行鎖大幅度提升了多用戶併發操做的性能。InnoDB 比較適合於插入和更新操做比較多的狀況,而 MyISAM 則適用於頻繁的查詢的狀況。另外, InnoDB 表的行鎖也不是絕對的,若是在執行一個 SQL 語句時, MySQL 不能肯定要掃描的範圍,InnoDB 表一樣會鎖全表,例如: update table set num=1 where name like '%aaa%';

  4. 全文檢索

    MyISAM:支持全文檢索。

    InnoDB:不支持全文檢索。

  5. 表主鍵

    MyISAM:容許沒有主鍵的表存在。

    InnoDB:若是沒有主鍵,則會自動生成一個 6 字節的主鍵(用戶不可見)。

  6. 表的具體行數

    MyISAM: select count(*) from table ,MyISAM 只要簡單的讀出保存好的行數。由於 MyISAM 內置了一個計數器, count(*) 時它直接從計數器中讀。

    InnoDB:不保存表的具體行數,也就是說,執行 select count(*) from table 的時候,InnoDB 要掃描一遍整表來計算有多少行。

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索