深入淺出索引&&閱讀筆記

索引的出現其實就是爲了提高數據查詢的效率,就像書的目錄一樣。對於數據庫的表而言,索引其實就是它的“目錄”。

索引的常見模型

  • 哈希表
  • 有序數組
  • 搜索樹

哈希表適用於只有等值查詢的場景。

有序數組等值查詢範圍查詢場景中的性能非常優秀,單看查詢確實爽,但是需要更新的時候就麻煩了,數組你懂的,插入刪除都是大問題。所以,有序數組索引只適用於靜態存儲引擎,比如你要保存的是2017年某個城市的所有人口信息,這類不會再修改的數據。

二叉搜索樹:特徵相信不必多言~ 時間複雜度爲O(log(N)),前提是平衡二叉樹。爲了這個條件,更新的時間複雜度也是O(log(N))。

也有多叉樹,就是每個節點有多個兒子,兒子之間的大小保證從左到右遞增。

二叉樹的搜索效率是最高的,但是數據庫存儲卻不用二叉樹,因爲索引不止存在內存中,還要寫到磁盤上

一棵100萬節點的平衡二叉樹,樹高20。一次查詢可能需要訪問20個數據塊。在機械硬盤時代,從磁盤隨機讀一個數據塊需要10 ms左右的尋址時間。也就是說,對於一個100萬行的表,如果使用二叉樹來存儲,單獨訪問一個行可能需要20個10 ms的時間,這個查詢可真夠慢的。

使用“N叉”樹,能讓一個查詢儘量少讀磁盤,N叉樹由於在讀寫上的性能優點,以及適配磁盤的訪問模式,已經被廣泛應用在數據庫引擎中了

InnoDB 的索引模型

InnoDB裏,表都是根據主鍵順序以索引的形式存放,這種存儲方式的表稱爲索引組織表。InnoDB使用的是B+樹索引,數據都是存儲在B+樹中。每一個索引在InnoDB中對應一顆B+樹。

假設,我們有一個主鍵列爲ID的表,表中有字段k,並且在k上有索引。

這個表的建表語句是:

mysql> create table T(
id int primary key, 
k int not null, 
name varchar(16),
index (k))engine=InnoDB;

表中R1~R5的(ID,k)值分別爲(100,1)、(200,2)、(300,3)、(500,5)和(600,6),兩棵樹的示例示意圖如下。
在這裏插入圖片描述
圖中可看出,根據葉子節點的內容,索引類型分爲主鍵索引非主鍵索引
主鍵索引的葉子節點存的是整行數據。在InnoDB裏,主鍵索引也被稱爲聚簇索引(clustered index)。

非主鍵索引的葉子節點內容是主鍵的值。在InnoDB裏,非主鍵索引也被稱爲二級索引(secondary index)。

基於主鍵索引和普通索引的查詢有什麼區別?

  • 主鍵索引只需要根據主鍵搜索B+樹
  • 普通索引需要先從普通索引B+樹上找到主鍵ID,再從主鍵索引樹尋找一次,也就是有一個回表的過程

所以,我們應該儘量使用主鍵查詢

索引維護

索引用着爽,但是需要維護,有時候維護的成本挺大。

如果插入的主鍵是處於索引樹的一箇中間節點的情況,需要邏輯上挪的數據,空出位置。

而更糟的情況是,如果因爲該數據頁已經滿了,根據B+樹的算法,這時候需要申請一個新的數據頁,然後挪動部分數據過去。這個過程稱爲頁分裂。在這種情況下,性能自然會受影響。

頁分裂操作還影響數據頁的利用率。原本放在一個頁的數據,現在分到兩個頁中,整體空間利用率降低大約50%。

有分裂就有合併。當相鄰兩個頁由於刪除了數據,利用率很低之後,會將數據頁做合併。

主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小,從性能存儲空間方面考量,自增主鍵往往是更合理的選擇,保證了有序插入,維護成本較小

ps:數據庫分庫情況下,用自增主鍵肯定走不通了,我們可以使用推特的雪花算法,生成全局唯一發號器同時,id也是保持遞增的。

覆蓋索引

還是上圖那個表。
如果執行的語句是select ID from T where k between 3 and 5,這時只需要查ID的值,而ID的值已經在k索引樹上了,可以直接提供查詢結果,不需要回表。索引k已經“覆蓋了”我們的查詢需求,我們稱爲覆蓋索引。

由於覆蓋索引可以減少樹的搜索次數,顯著提升查詢性能,所以使用覆蓋索引是一個常用的性能優化手段。

最左前綴原則

假設這個市民表的定義是這樣的:

CREATE TABLE `tuser` (
  `id` int(11) NOT NULL,
  `id_card` varchar(32) DEFAULT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `ismale` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id_card` (`id_card`),
  KEY `name_age` (`name`,`age`)
) ENG

爲每一種查詢都設計一個索引,索引是不是太多了?如果不設索引,我又不想它走全表掃描,但是有了索引,我這個查詢又很少用到,是不是有點浪費?

B+樹這種索引結構,可以利用索引的“最左前綴”,來定位記錄。

用(name,age)這個聯合索引來分析

在這裏插入圖片描述
索引項是按照索引定義裏面出現的字段順序排序的

查詢所有名字是“張三” 的人,可以快速定位到ID4,往後遍歷即可得到所有需要的結果。
要查所有名字第一個字是“張” 的人,使用模糊查詢,前提是sql語句長這樣where name like ‘張%’(而不是where name like ‘%張%’,後者是不會走索引查詢的),這時也用上了這個索引,查找到ID3,往後遍歷。

只要滿足最左前綴,就可以利用索引來加速檢索。這個最左前綴可以是聯合索引的最左N個字段,也可以是字符串索引的最左M個字符。

在建立聯合索引的時候,如何安排索引內的字段順序?

這裏的評估標準是,索引的複用能力。因爲有最左前綴的存在,當已經有了(a,b)這個聯合索引後,一般就不需要單獨在a上建立索引了。因此,第一原則是,如果通過調整順序,可以少維護一個索引,那麼這個順序往往就是需要優先考慮採用的。

那麼,如果既有聯合查詢,又有基於a、b各自的查詢呢?查詢條件裏面只有b的語句,是無法使用(a,b)這個聯合索引的,這時候你不得不維護另外一個索引,也就是說你需要同時維護(a,b)、(b) 這兩個索引。

這時候,要考慮的原則就是空間了。比如上面這個市民表的情況,name字段是比age字段大的 ,那我就建議你創建一個(name,age)的聯合索引和一個(age)的單字段索引。

索引下推

滿足最左前綴原則的時候,可在索引中定位記錄

那如果不符合最左前綴原則,怎麼辦?

以市民表的聯合索引(name, age)爲例。如果現在有一個需求:檢索出表中“名字第一個字是張,而且年齡是10歲的所有男孩”。那麼,SQL語句是這麼寫的:

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

根據最左前綴,是找到張*了,但是接下來得遍歷判斷年齡了。

在MySQL 5.6之前,只能從ID3開始一個個回表。到主鍵索引上找出數據行,再對比字段值。

而MySQL 5.6 引入的索引下推優化(index condition pushdown), 可以在索引遍歷過程中,對索引中包含的字段先做判斷,直接過濾掉不滿足條件的記錄,減少回表次數

圖3 無索引下推執行流程:
這個過程InnoDB並不會去看age的值,只是按順序把“name第一個字是’張’”的記錄一條條取出來回表。因此,需要回表4次。

在這裏插入圖片描述
圖4 索引下推執行流程:
InnoDB在(name,age)索引內部就判斷了age是否等於10,對於不等於10的記錄,直接判斷並跳過。只需要對ID4、ID5這兩條記錄回表取數據判斷,就只需要回表2次。在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章