MYSQL實戰四十五講總結筆記_04、索引_上

前言:整理歸納,個人溫習之用,請支持正版極客時間

1、索引的常見模型

*概念:索引的出現其實就是爲了提高數據查詢的效率,就像書的目錄一樣,常見的索引模型有哈希表、有序數組和搜索樹
 
*哈希表是一種以鍵 - 值(key-value)存儲數據的結構,我們只要輸入待查找的鍵即 key,就可以找到其對應的值即 Value。哈希的思路很簡單,把值放在數組裏,用一個哈希函數把 key 換算成一個確定的位置,然後把 value 放在數組的這個位置。
衝突:多個 key 值經過哈希函數的換算,會出現同一個值的情況;解決方式:鏈表
不足:哈希索引做區間查詢的速度是很慢,只適用於等值查詢的場景,比如 Memcached 及其他一些 NoSQL 引擎。
 
*有序數組按順序存儲。查詢用二分法就可以快速查詢,時間複雜度是:O(log(N))。在等值查詢和範圍查詢場景中的性能就都非常優秀,缺點是需要更新數據的時候,你往中間插入一個記錄就必須得挪動後面所有的記錄,成本太高,有序數組索引只適用於靜態存儲引擎
 
*二叉搜索樹的特點是:每個節點的左兒子小於父節點,父節點又小於右兒子,查詢時間複雜度是 O(log(N)),前提是這是平衡二叉樹,更新時間複雜度也是 O(log(N))
 
*樹可以有二叉,也可以有多叉。多叉樹就是每個節點有多個兒子,兒子之間的大小保證從左到右遞增。二叉樹是搜索效率最高的。
 
*大多數數據庫存儲卻並不使用二叉樹。樹高過高,影響查詢效率。原因:索引不止存在內存中,還要寫到磁盤上。
例:想象一下一棵 100 萬節點的平衡二叉樹,樹高 20。一次查詢可能需要訪問 20 個數據塊。在機械硬盤時代,從磁盤隨機讀一個數據塊需要 10 ms 左右的尋址時間。也就是說,對於一個 100 萬行的表,如果使用二叉樹來存儲,單獨訪問一個行可能需要 20 個 10 ms 的時間
解決:爲了讓一個查詢儘量少地讀磁盤,就必須讓查詢過程訪問儘量少的數據塊。那麼,我們就不應該使用二叉樹,而是要使用“N 叉”樹。這裏,“N 叉”樹中的“N”取決於數據塊的大小。
以 InnoDB 的一個整數字段索引爲例,這個 N 差不多是 1200。這棵樹高是 4 的時候,就可以存 1200 的 3 次方個值,這已經 17 億了。考慮到樹根的數據塊總是在內存中的,一個 10 億行的表上一個整數字段的索引,查找一個值最多隻需要訪問 3 次磁盤。其實,樹的第二層也有很大概率在內存中,那麼訪問磁盤的平均次數就更少了。
 
*數據庫底層存儲的核心就是基於這些數據模型的。每碰到一個新數據庫,我們需要先關注它的數據模型,這樣才能從理論上分析出這個數據庫的適用場景

 

2、InnoDB 的索引模型

*在 InnoDB 中,表都是根據主鍵順序以索引的形式存放的,這種存儲方式的表稱爲索引組織表。InnoDB 使用了 B+ 樹索引模型,每一個索引在 InnoDB 裏面對應一棵 B+ 樹。
 
*例:有一個主鍵列爲 ID 的表,表中有字段 k,並且在 k 上有索引,表中 R1~R5 的 (ID,k) 值分別爲 (100,1)、(200,2)、(300,3)、(500,5) 和 (600,6),兩棵樹的示例示意圖如下
mysql> create table T(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=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 索引樹搜索一次。這個過程稱爲回表,基於非主鍵索引的查詢需要多掃描一棵索引樹

 

3、索引維護

*B+ 樹爲了維護索引有序性,在插入新值的時候需要做必要的維護。
以上面這個圖爲例,如果插入新的行 ID 值爲 700,則只需要在 R5 的記錄後面插入一個新記錄。如果新插入的 ID 值爲 400,就相對麻煩了,需要邏輯上挪動後面的數據,空出位置。而更糟的情況是,如果 R5 所在的數據頁已經滿了,根據 B+ 樹的算法,這時候需要申請一個新的數據頁,然後挪動部分數據過去。這個過程稱爲頁分裂。
 
*頁分裂、頁合併:一個數據頁滿了,按照B+Tree算法,新增加一個數據頁,叫做頁分裂,會導致性能下降。空間利用率降低大概50%。當相鄰的兩個數據頁利用率很低的時候會做數據頁合併,合併的過程是分裂過程的逆過程。
 
*例:在一些建表規範裏面要求建表語句裏一定要有自增主鍵。來分析一下哪些場景下應該使用自增主鍵,而哪些場景下不應該
解:自增主鍵:自增列上定義的主鍵,在建表語句中一般是這麼定義的: NOT NULL PRIMARY KEY AUTO_INCREMENT。
好處:插入新記錄的時候可以不指定 ID 的值,系統會獲取當前 ID 最大值加 1 作爲下一條記錄的 ID 值。
自增主鍵的插入數據模式,正符合了前面提到的遞增插入的場景。每次插入一條新記錄,都是追加操作,都不涉及到挪動其他記錄,也不會觸發葉子節點的分裂。而有業務邏輯的字段做主鍵,則往往不容易保證有序插入,這樣寫數據成本相對較高。
除了考慮性能外,還可以從存儲空間的角度來看。假設表中確實有一個唯一字段,比如字符串類型的身份證號,那應該用身份證號做主鍵,還是用自增字段做主鍵呢?由於每個非主鍵索引的葉子節點上都是主鍵的值。如果用身份證號做主鍵,那麼每個二級索引的葉子節點佔用約 20 個字節,而如果用整型做主鍵,則只要 4 個字節,如果是長整型(bigint)則是 8 個字節。
主鍵長度越小,普通索引的葉子節點就越小,普通索引佔用的空間也就越小。
從性能和存儲空間方面考量,自增主鍵往往是更合理的選擇
 
*在Key-Value的場景下,即只有一個索引且是唯一索引,則適合直接使用業務字段作爲主鍵索引。
 

4、總結

*問題:對於上面例子中的 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);

對於上面這兩個重建索引的作法,說出你的理解。如果有不合適的,爲什麼,更好的方法是什麼?

答:重建索引 k 的做法是合理的,可以達到省空間的目的。但是,重建主鍵的過程不合理。不論是刪除主鍵還是創建主鍵,都會將整個表重建。所以連着執行這兩個語句的話,第一個語句就白做了。可以用這個語句代替 : alter table T engine=InnoDB。
 
*爲什麼要重建索引?索引可能因爲刪除,或者頁分裂等原因,導致數據頁有空洞,重建索引的過程會創建一個新的索引,把數據按順序插入,這樣頁面的利用率最高,也就是索引更緊湊、更省空間
 
B+樹主鍵索引的葉子節點是page (頁),一個頁裏面可以存多個行,通過二分法去定位行數據
 
*如果插入的數據是在主鍵B+樹葉子結點的中間,後面的所有頁如果都是滿的狀態,只會分裂它要寫入的那個頁面。每個頁面之間是用指針串的,改指針就好了,不需要後面頁都進行頁分裂
 
*插入數據,如果是在某個數據滿了的頁的首尾,爲了減少數據移動和頁分裂,會先去前後兩個頁看看是否滿了,如果沒滿會先將數據放到前後兩個頁上
 
*如果記錄表中 R1~R5 的 (ID,k) 值分別爲 (100,1)、(200,1)、(300,1)、(500,1) 和 (600,1),那麼非主鍵索引k=1的節點,應該記錄(1,100),(1,200)... (1,600),沒搞懂這個
 

 

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