Mysql45講讀書筆記 04講深入淺出索引(上)

一 序

本文屬於極客時間 Mysql45講讀書筆記系列。

索引的出現其實就是爲了提高數據查詢的效率。

二 索引的常見模型

   常見的有數據結構有:哈希表、有序數組和搜索樹。

哈希表是一種以鍵-值(key-value)存儲數據的結構,我們只要輸入待查找的值即key,就可以找到其對應的值即Value。哈希的思路很簡單,把值放在數組裏,用一個哈希函數把key換算成一個確定的位置,然後把value放在數組的這個位置。

解決hash衝突,使用了常見的鏈表法。

   假設,你現在維護着一個身份證信息和姓名的表,需要根據身份證號查找對應的名字,這時對應的哈希索引的示意圖如下所示:

圖中,User2和User4根據身份證號算出來的值都是N,但沒關係,後面還跟了一個鏈表。假設,這時候你要查ID_card_n2對應的名字是什麼,處理步驟就是:首先,將ID_card_n2通過哈希函數算出N;然後,按順序遍歷,找到User2。

    這個跟Java裏面的hashmap解決hash衝突是一個原理。

  • 優點:只有等值查詢的場景。
  • 缺點:不適合範圍查找。

而有序數組在等值查詢和範圍查詢場景中的性能就都非常優秀。還是上面這個根據身份證號查名字的例子,如果我們使用有序數組來實現的話,示意圖如下所示:

這裏我們假設身份證號沒有重複,這個數組就是按照身份證號遞增的順序保存的。這時候如果你要查ID_card_n2對應的名字,用二分法就可以快速得到,這個時間複雜度是O(log(N))。

  • 優點:適合範圍查找。
  • 缺點:插入移動數據成本高,只適用於靜態存儲引擎

還是上面根據身份證號查名字的例子,如果我們用二叉搜索樹來實現的話,示意圖如下所示:

二叉搜索樹的特點是:每個節點的左兒子小於父節點,父節點又小於右兒子。這樣如果你要查ID_card_n2的話,按照圖中的搜索順序就是按照UserA -> UserC -> UserF -> User2這個路徑得到。這個時間複雜度是O(log(N))。

  數據庫存儲卻並不使用二叉樹而是用多叉樹來替代,因爲索引不止存在內存中,還要寫到磁盤上,多叉樹比二叉樹的高度低,

上面介紹了不同的數據結構,以及它們的適用場景,下面介紹innodb的索引模型。

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),兩棵樹的示例示意圖如下。

  

圖4 InnoDB的索引組織結構

從圖中不難看出,根據葉子節點的內容,索引類型分爲主鍵索引和非主鍵索引。

主鍵索引的葉子節點存的是整行數據。在InnoDB裏,主鍵索引也被稱爲聚簇索引(clustered index)。

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

根據上面的索引結構說明,我們來討論一個問題:基於主鍵索引和普通索引的查詢有什麼區別?

  • 如果語句是select * from T where ID=500,即主鍵查詢方式,則只需要搜索ID這棵B+樹;
  • 如果語句是select * from T where k=5,即普通索引查詢方式,則需要先搜索k索引樹,得到ID的值爲500,再到ID索引樹搜索一次。這個過程稱爲回表。

也就是說,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該儘量使用主鍵查詢。

索引維護

B+樹爲了維護索引有序性,在插入新值的時候需要做必要的維護。以上面這個圖爲例,如果插入新的行ID值爲700,則只需要在R5的記錄後面插入一個新記錄。如果新插入的ID值爲400,就相對麻煩了,需要邏輯上挪動後面的數據,空出位置。

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

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

當然有分裂就有合併。當相鄰兩個頁由於刪除了數據,利用率很低之後,會將數據頁做合併。合併的過程,可以認爲是分裂過程的逆過程。

  (要理解這個頁分裂,還可以看看這篇 MYSQL INNODB數據存儲結構

關於主鍵的選擇:

  •   從性能看:自增主鍵的插入數據模式,正符合了我們前面提到的遞增插入的場景。每次插入一條新記錄,都是追加操作,都不涉及到挪動其他記錄,也不會觸發葉子節點的分裂。而有業務邏輯的字段做主鍵,則往往不容易保證有序插入,這樣寫數據成本相對較高。
  • 從存儲空間的角度來看:由於每個非主鍵索引的葉子節點上都是主鍵的值。如果用身份證號做主鍵,那麼每個二級索引的葉子節點佔用約20個字節,而如果用整型做主鍵,則只要4個字節,如果是長整型(bigint)則是8個字節。

顯然,主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小。

有特定的KV場景下適合用業務字段直接做主鍵,由於沒有其他索引,所以也就不用考慮其他索引的葉子節點大小的問題。

這時候我們就要優先考慮上一段提到的“儘量使用主鍵查詢”原則,直接將這個索引設置爲主鍵,可以避免每次查詢需要搜索兩棵樹。

小結:

老師給了個題目:對於上面例子中的InnoDB表T,如果你要重建索引 k,改怎麼辦?

你的兩個SQL語句可以這麼寫:

alter table T drop index k;
alter table T add index(k);

如果你要重建主鍵索引,也可以這麼寫:

alter table T drop primary key;
alter table T add primary key(id);

通常:相對普通索引而言,刪除主鍵索引消耗大(畢竟還要更新引用主鍵索引的二級索引), 所以推薦1.

索引詳細介紹可以看這篇:https://blog.csdn.net/bohu83/article/details/81104432

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