經常可能發生,某個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 簡單來說,索引的出現其實就是為了提高資料查詢 的效率,就像書的目錄一樣。對於資料庫的表而言,索引其實就是它的 目錄 用於提高讀寫效率的資料結構有很多,以下介紹三種常見 也比...