MySQL學習筆記(四)索引(上)

2021-09-22 21:25:19 字數 4095 閱讀 7437

經常可能發生,某個sql查詢比較慢,分析完原因後,可能就會想到「給某個欄位加個索引」這樣的方案。

索引的出現就是為了提高資料查詢的效率。

實現索引的方式有很多種,所以這裡也就引入了索引模型。比較常見的,雜湊表、有序陣列和搜尋樹。

雜湊表以鍵值對儲存資料,只要輸入對應的key,就可以找到對應的value。雜湊的思路,把值放到陣列裡,用乙個雜湊函式把key換算成乙個確定的位置,然後把value放在陣列的這個位置。

可能多個key值經過雜湊函式的換算會出現同乙個值的情況。處理這種情況的一種方法是,拉出乙個鍊錶。

圖中user2和user4根據身份證號算出來的值都是n,但是沒關係,後面有鍊錶,按順序遍歷,找到user2。

四個id_card_n的值並不是遞增的,這樣做的好處是增加新的user時速度會很快。但缺點是因為不是有序的,所以雜湊索引做區間查詢的速度是很慢的。假如想查詢身份證號在[id_card_x,  id_card,y]這個區間的所有使用者,就必須全部掃瞄一遍。

所以雜湊表這種結構適用於只有等值查詢的場景,比如memcached及其他一些nosql引擎。

有序陣列在等值查詢和範圍查詢場景中的效能都非常優秀。

這裡假設身份證號沒有重複,這個陣列是按照遞增的順序保證的。如果要查id_card_n2對應的名字,用二分法就可以快速得到,這個時間複雜度是o(log(n))。

同時很顯然支援範圍查詢,查詢身份證號在[id_card_x,  id_card_y]這個區間的所有使用者,先用二分法找到id_card_x(如果不存在,就找到大於id_card_x的第乙個user),然後向右遍歷,直到查到第乙個大於id_card_y的,退出迴圈。

但是有序陣列的缺點在於更新資料的時候,往中間插入乙個記錄就必須得挪動後面所有的記錄,成本太高。

所以有序陣列只適用於靜態儲存引擎。(比如2023年某個城市所有人口資訊,這類不會再修改的資料)

二叉搜尋樹的特點事:每個節點的左兒子小於父節點,父節點又小於右兒子。

如果要查id_card_n2, 就需要 usera -> userc -> userf -> user2 這個路徑,時間複雜度為o(log(n))。

二叉樹搜尋效率最高,但實際上大多數資料庫儲存並不使用二叉樹。因為索引不止存在記憶體中,還要寫在磁碟上。

一棵100萬個節點的平衡二叉樹,樹高20。一次查詢可能需要訪問20個資料塊。從磁碟隨機讀乙個資料塊需要10ms左右的定址時間,對於100萬行的表,單獨訪問乙個行可能需要20個10ms的時間,這個查詢可真夠慢的。

為了讓乙個查詢盡量少讀磁碟,就必須盡量少訪問資料塊。就應該使用n叉樹。n 取決於資料塊的大小。

innodb的乙個整數字段索引為例,這個n差不多是1200。高是4時就可以存1200的三次方個節點,已經17億了。樹根的資料塊總是在記憶體中,查詢乙個值最多隻需要訪問3次磁碟。其實,樹的第二層也有很大概率在記憶體中。樹的第二層也有很大概率中,那訪問此判斷的平均次數就更少了。

n叉樹由於讀寫上的效能優點,被廣泛運用到資料庫引擎中。

資料庫底層儲存的核心就是基於這些資料模型的。

mysql中,不同儲存引擎的索引的工作方式並不一樣。即使多個儲存引擎支援同一種型別的索引,其底層實現也可能不同。

innodb中,表都是根據主鍵順序以索引的形式存放,這種儲存方式的表稱為索引組織表。innodb使用b+樹索引模型,所以資料都是儲存在b+樹中的。

每乙個索引在innodb裡面對應一棵b+樹。

假設,有乙個主鍵列為id的表,表中有字段k,並且在k上有索引。

表中r1~r5的(id, k)值分別為(100, 1) (200, 2) (300, 3) (500, 5) (600, 6)

從圖中可以看出,索引型別分為主鍵索引和非主鍵索引。

主鍵索引的葉子節點存的是整行資料,innodb裡,主鍵索引也被稱為聚簇索引(clustered index)。非主鍵索引的葉子節點內容是主鍵的值,innodb裡面也稱為二級索引(secondary index)。

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

如果語句是select * from t where id=500 即主鍵查詢方式,則只需要搜尋id這棵b+樹。

如果語句是select * from t where k = 5 即普通索引查詢方式,則需要先搜尋k索引樹,得到id的值為500,再到id索引樹搜尋一次。這個過程稱為回表。

也就是,非主鍵索引的查詢會多掃瞄一棵索引樹,因為應該盡量使用主鍵查詢。

以上圖為例,當插入新值的時候,需要維護索引有序性。如果插入新的行id值為700,則只需要在r5的記錄後面插入乙個新記錄。如果新插入的id為400,就需要邏輯上挪動後面的資料,空出位置。

而如果r5所在的資料頁已經滿了,根據b+樹的演算法,這時候需要申請乙個新的資料頁,然後挪部分資料過去。這個過程稱為頁**。在這種情況下,效能自然會受影響。

頁**還影響利用率,原本乙個頁的資料,現在放入兩個頁,整體空間利用率降低50%。

有**就有合併。當相鄰兩個頁刪除了資料,利用率很低之後會將資料頁做合併。合併的過程,可以認為是**過程的逆過程。

自增主鍵是指自增列上定義的主鍵,在建表語句中一般是這麼定義:not null primary key auto_increment

插入新記錄的時候可以不指定id的值,系統會自動獲取當前id最大值加1作為下一條記錄的id值。都是追加,不會挪動其他其他記錄,也不會觸發葉子節點的**。

有業務邏輯的字段做主鍵,往往不容易保證有序插入,這樣寫資料成本相對較高。

除了考慮效能外,還可以從儲存空間的角度來看。假設表中有乙個唯一字段,比如字串型別的身份證號,那應該用身份證號做主鍵,還是用自增欄位做主鍵?

由於每個非主鍵索引的葉子節點都是主鍵的值。如果用身份證號做主鍵,那麼每個二級索引的葉子節點占用約20個位元組,而如果用整型做主鍵,則只有4個位元組,如果是長整型則是8個位元組。

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

所以從效能和儲存空間角度來看,自增主鍵是更合理的選擇。

適合用業務字段直接做主鍵的:

1.只有乙個索引

2.該索引必須是唯一索引

這時就優先考慮上一段提到的「盡量使用主鍵查詢」原則,直接將這個索引設為主鍵,可以避免每次查詢需要搜尋兩棵樹。

b+樹能配合磁碟的讀寫特性,減少單次查詢的磁碟訪問次數。

對於上面例子的表t,如果要重建索引k:

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);

通過兩個alter語句重建索引k,以及兩個alter語句重建主鍵索引是否合理?

為什麼要重建,因為索引可能因為刪除,或者頁**等原因,導致資料頁有空洞,重建索引的過程會建立乙個新的索引,把資料按順序插入,這樣頁面的利用率最高,也就是索引更緊湊、更省空間。

重建索引k的做法是合理的,可以達到省空間的目的。到那時重建主鍵的過程不合理,不論是刪除主鍵還是建立主鍵,都會將整個表重建。所以連著執行這兩個語句的話,第乙個語句就白做了。

這兩個語句,可以用:alter table t engine=innodb 代替。

深入理解MySQL索引 上

簡單來說,索引的出現就是為了提高資料查詢的效率,就像字典的目錄一樣。如果你想快速找乙個不認識的字,在不借助目錄的情況下,那我估計你的找好長時間。索引其實就相當於目錄。索引的出現是為了提高查詢效率,但是實現索引的而方式有很多種,所以這裡也就引入了索引模型的概念。可以用於提高查詢效率的資料結構有好多種,...

MySQL學習筆記(四)索引(下)

在上述那個表中,如果執行了select from t where k between 3 and 5,需要執行幾次樹的搜尋操作,會掃瞄多少行?表的初始化語句 現在看看select from t where k between 3 and 5這條sql查詢語句的執行流程 1 在k索引樹上找到k 3記錄...

MySQL學習 3 深入淺出索引(上)

innodb的索引模型 參考資料 寫在後面 環境 mysql5.7.24,for linux glibc2.12 x86 64 簡單來說,索引的出現其實就是為了提高資料查詢 的效率,就像書的目錄一樣。對於資料庫的表而言,索引其實就是它的 目錄 用於提高讀寫效率的資料結構有很多,以下介紹三種常見 也比...