本文摘抄自 極客時間【MySQL實戰45講】面試
爲何要有索引?索引的做用是什麼?算法
索引的出現其實就是爲了提升數據查詢的效率,就像書的目錄同樣。一本書咱們能夠經過目錄中快速的定位其中的某一個知識點;對於數據庫而言索引其實就是它的目錄,能夠經過索引快速的定位都某一條或多條記錄。數據庫
<!-- more -->數組
常見索引模型
Hash表
哈希表是一個以 鍵-值(key-value) 存儲數據的結構,咱們只要輸入待查找的值即 key,就能夠找到對應的值即 value。性能優化
結構特色
把值放在數組裏,用一個哈希函數把 key 轉換成一個肯定的位置,而後把 value 放在數組的這個位置。當多個 key 值通過哈希函數的換算會出現同一個值的狀況。這時候會拉出一個鏈表進行存儲。併發
案例
假設如今維護一個身份證信息和姓名的表,表示根據身份證查找對應的名字,這時對應的哈希索引示意圖以下函數
圖 1 哈希表示意圖性能
圖中,User2 和 User4 根據身份證號算出來的值都是 N,因此後邊跟了一個鏈表存儲。若是要找到 User2 對應的名字是什麼,首先經過哈希函數計算出 ID_card_n2 的值爲 N,而後按順序遍歷找到 User2。優化
在這裏四個 ID_card_n 的值並非遞增的,這樣作的好處就是增長的時候會很快,直接日後邊追加;缺點是數據的存儲並非有序的,因此在作區間查詢時會進行全表掃描,速度會很是慢。url
哈希表這個結構只適用於等值查詢。
有序數組
結構特色
將數據存放到數組中,數據在數組中是按順序存儲的,從左到右依次從大到小或從小到大。
案例
以哈希表中的例子,使用有序數組實現的結果以下
圖 2 有序數組示意圖
這裏假設身份證是沒有重複的,這個數組是按照身份證的遞增順序保存的。這時候若是刷要查詢 ID_card_n2 對應的姓名,用二分查找法能夠快速定位到這條記錄,同時若是要進行範圍查詢也是很快的,好比要查找身份證號在 [ID_card_X, ID_card_N] 區間的 User,能夠先用二分查找法找到 ID_card_X 的值,若是沒有則找到大於 ID_card_X 的第一個值,而後向右遍歷,知道找到第一個大於 ID_card_N 的值退出循環。
可是往數組中插入一條記錄的時候須要挪動後邊全部的元素,這樣的成本過高,一樣的刪除一條記錄也會致使後邊全部的元素往前挪動。
有序數組結構適用於等值和範圍查詢,可是插入和刪除的效率太慢。
二叉搜索樹
結構特色
二叉搜索樹每一個節點大於左兒子小於右兒子。
案例
上文例子,二叉搜索樹實現以下
圖 3 二叉搜索樹示意圖
若是咱們要找到 ID_card_n2 的話,根據二叉搜索樹的特色,按照圖中的搜索順序依次是:UserA→UserC→UserF→User2。
InnoDB索引模型
InnoDB 使用了 B+ 樹索引模型,數據都是存儲在 B+ 樹中的,每個索引都對應一棵 B+ 樹。
B-Tree
B樹 是一棵多路平衡樹且有如下特色:
- m階B樹 表示該樹每一個節點最多有 m 個孩子;
- 除根節點和葉子節點外,其它每一個節點至少有
ceil(m / 2)
個孩子; - 若根節點不是葉子節點,則至少有兩個孩子;
- 全部葉子節點都在同一層,葉子節點不包含任何關鍵字;
- 每一個葉子節點包含 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
;
B+樹做爲數據庫索引的優點
B+ Tree 是在 B-Tree 基礎上的優化,使其更適合實現外存儲索引結構,InnoDB引擎就是基於它實現索引結構,B+Tree 相對於 B-Tree 的優點:
- B+樹的磁盤讀取代價低:B+樹 全部的內部節點沒有關鍵字的具體信息(只存儲了key的信息),這樣可使內部節點相對更小。一個硬盤塊中包含的節點信息越多,一次性讀取內存中的關鍵字也就越多,相對來講就是 IO 讀寫次數的下降,也能夠說是每次 IO 操做的可觀看數據也就越多;
- B+樹便於執行掃庫操做:B樹在分支節點上都保存着數據,要找到具體的順序數據,必須用中序遍歷的方式按序掃庫;因爲B+樹的數據都存儲在葉子節點上,全部節點均爲索引,因此 B+樹 直接從葉子節點挨個掃一遍就完了;B+樹 支持範圍查詢(rang-query)很是方便,而B樹不支持;
- B+ 樹查詢效率更加穩定:因爲 B+樹 的數據都存儲在葉子節點上,分支節點均爲索引,因此對於任意關鍵字的查找都必須從根節點走到分支節點,全部關鍵字查詢路徑長度相同,每一個數據的查詢效率差很少。對於 B樹 而言,分支節點也保存有數據,對於每個數據的查詢所走的路徑長度也是不同的,效率也就不同;
B+Tree 與 B-Tree 的不一樣
- 非葉子節點只存儲鍵值信息
- 全部葉子節點之間都有一個鏈指針
- 數據記錄都存放在葉子節點中
索引維護
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);
爲何重建索引?
索引可能由於刪除,或者頁分裂等緣由,致使數據頁有空洞,重建索引的過程會建立一個新的索引,把數據按順序插入,這樣頁面的利用率最高,也就是索引更緊湊、更省空間。
解答
重建主鍵的過程不合理:
- 不管是刪除仍是建立主鍵都會整個表重建
- 連續執行兩個語句,至關於第一個語句白作
- 兩個語句可使用
alter table T engine=InnoDB
代替
索引類型
主鍵索引
主鍵索引葉子節點中存儲的是整行數據,從物理結構的角度也叫 聚簇索引。
非主鍵索引
- 非主鍵索引的葉子節點存儲的是主鍵的值,從物理存儲的角度也叫 二級索引。
- 二級索引存儲主鍵,是爲了減小出現行移動或數據頁分裂時二級索引的維護工做,但會讓二級索引佔用更多的空間。
基於主鍵索引和普通查詢的查詢的區別
- 主鍵索引:只須要搜索主鍵這棵樹。
- 普通索引:先搜索普通索引對應的樹獲得ID,在根據ID在主鍵索引的樹上找到對應的數據,這個過程稱爲回表。
聚簇索引的注意點有哪些?
聚簇索引最大限度的提升了 IO 密集型應用的性能,但它也有限制:
-
插入速度嚴重依賴於插入順序,按照主鍵的順序插入是最快的,不然會出現頁分裂,嚴重影響性能。因此通常 InnoDB 表,都會定義一個自增的id做爲主鍵。
面試問題:爲何主鍵須要自增ID,或者爲何主鍵須要帶有時間行關聯。
-
更新主鍵的代價很高,由於將會致使被更新的行移動。所以,InnoDB表通常主鍵不可更新。
MySQL默認狀況下,主鍵是容許更新的。MongoDB主鍵是不容許更新的。
-
二級索引訪問須要回表。
有種狀況無需二次查找,就是索引覆蓋。
-
主鍵ID建議使用整型。由於,每一個主鍵索引的 B+Tree 節點的鍵值能夠存儲更多主鍵ID,每一個非主鍵索引的 B+Tree 節點的數據能夠存儲更多的主鍵ID。
什麼場景適合使用業務字段直接作主鍵?
- 只有一個索引
- 該索引必須有惟一索引
- KV場景,因爲沒有其餘索引,因此不用考慮其餘索引的葉子節點大小的問題
- 此時直接將這個索引設置爲主鍵,能夠避免回表
覆蓋索引
若是執行的語句是 select ID from T where k between 3 and 5;
,這時只須要查 ID 的值,而ID的值已經在 k 索引樹上了,所以能夠直接拿到查詢結果,不須要回表。也就是說索引 k 已經覆蓋了咱們的查詢需求,咱們稱爲覆蓋索引。
覆蓋索引的優點
覆蓋索引能夠減小樹的搜索次數,顯著提高查詢性能,因此使用覆蓋索引是一個經常使用的性能優化手段。
聯合索引
業務例子
一個市民信息表上,是否有必要將身份證號 和 名字 創建聯合索引?
若是有一個高配請求,要根據市民的身份照查詢他的姓名,這個聯合索引就頗有意義了。它能夠在這個高配請求上用到覆蓋索引,不須要回表。
最左前綴原則
前提
如今有這麼一個聯合索引,(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 開始一個個的回表,到主鍵索引上找出數據行,在對比字段。
在 MyQL5.6 以後引入了索引下推優化(index condition pushdown),能夠在索引遍歷的過程當中,對索引中包含的字段先作判斷,直接過濾掉不知足條件的記錄,從而減小了回表的次數。
MyISAM索引實現
索引實現原理
MyISAM 引擎同 InnoDB 同樣,都是使用 B+Tree 做爲索引結構。差異在於:
- InnoDB 的數據文件自己就是索引文件。MyISAM 索引文件和數據文件是分離開的,索引文件僅保存數據記錄的地址。
主鍵索引和輔助索引
上圖分別爲主索引和輔助索引,因爲 MyISAM 的索引文件中僅保存了數據的地址,因此在 MyISAM 中主索引和輔助索引在結構上沒有本質的區別,只是主索引要求 key 的惟一性,而輔助索引的 key 是能夠重複的。
因爲 MyISAM 的 data 域中保存的是數據記錄的地址,因此 MyISAM 索引檢索的算法爲首先按照 B+Tree 搜索算法搜索索引,若是指定的 key 存在,則取出 data 域的值,而後以 data 域的值爲地址,讀取相應的數據記錄。
MyISAM的索引方式也叫作「非彙集」的,之因此這麼稱呼是爲了與InnoDB的彙集索引區分。
MyISAM 與 InnoDB 的區別
-
事務處理上
MyISAM:強調的是性能,查詢的速度比 InnoDB 類型更快,可是不提供事務支持。
InnoDB:提供事務支持。
-
外鍵
MyISAM:不支持外鍵。
InnoDB:支持外鍵。
-
鎖
MyISAM:只支持表級鎖。
InnoDB:支持行級鎖和表級鎖,默認是行級鎖,行鎖大幅度提升了多用戶併發操做的性能。InnoDB 比較適合於插入和更新操做比較多的狀況,而 MyISAM 則適用於頻繁的查詢的狀況。另外, InnoDB 表的行鎖也不是絕對的,若是在執行一個 SQL 語句時, MySQL 不能肯定要掃描的範圍,InnoDB 表一樣會鎖全表,例如:
update table set num=1 where name like '%aaa%';
。 -
全文檢索
MyISAM:支持全文檢索。
InnoDB:不支持全文檢索。
-
表主鍵
MyISAM:容許沒有主鍵的表存在。
InnoDB:若是沒有主鍵,則會自動生成一個 6 字節的主鍵(用戶不可見)。
-
表的具體行數
MyISAM:
select count(*) from table
,MyISAM 只要簡單的讀出保存好的行數。由於 MyISAM 內置了一個計數器, count(*) 時它直接從計數器中讀。InnoDB:不保存表的具體行數,也就是說,執行
select count(*) from table
的時候,InnoDB 要掃描一遍整表來計算有多少行。
本文由博客一文多發平臺 OpenWrite 發佈!