Mysql索引--轉(zhuǎn)載

參考地址:
javaGuide
數(shù)據(jù)庫兩大神器【索引和鎖】
# MySQL B+樹索引和哈希索引的區(qū)別
淺談MySQL的B樹索引與索引優(yōu)化

1.索引的基礎知識

首先Mysql的基本存儲結(jié)構(gòu)是(記錄都存在頁里邊):

image.png
image.png
  • 各個數(shù)據(jù)頁可以組成一個雙向鏈表
  • 每個數(shù)據(jù)頁中的記錄又可以組成一個單向鏈表
    • 每個數(shù)據(jù)頁都會為存儲在它里邊兒的記錄生成一個頁目錄,在通過主鍵查找某條記錄的時候可以在頁目錄中使用二分法快速定位到對應的槽,然后再遍歷該槽對應分組中的記錄即可快速找到指定的記錄
    • 其他列(非主鍵)作為搜索條件:只能從最小記錄開始依次遍歷單鏈表中的每條記錄

所以說,如果我們寫select * from user where username = 'Java3y'這樣沒有進行任何優(yōu)化的sql語句,默認會這樣做:

  • 定位到記錄所在的頁
    • 需要遍歷雙向鏈表,找到所在的頁
  • 從所在的頁內(nèi)中查找相應的記錄
    • 由于不是根據(jù)主鍵查詢,只能遍歷所在頁的單鏈表了

很明顯,在數(shù)據(jù)量很大的情況下這樣查找會很慢

2.索引提高搜索速度

索引做了些什么可以讓我們查詢加快速度呢?

其實就是將無序的數(shù)據(jù)變成有序(相對)

image.png

要找到id為8的記錄簡要步驟:

image.png

很明顯的是:沒有用索引我們是需要遍歷雙向鏈表來定位對應的頁,現(xiàn)在通過“目錄”就可以很快地定位到對應的頁上了!

其實底層結(jié)構(gòu)就是B+樹,B+樹作為樹的一種實現(xiàn),能夠讓我們很快地查找出對應的記錄。

索引實現(xiàn)參考地址

3.索引降低增刪改的速度

B+樹是平衡樹的一種。

平衡樹:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。

如果一棵普通的樹在極端的情況下,是能退化成鏈表的(樹的優(yōu)點就不復存在了)

image.png

B+樹是平衡樹的一種,是不會退化成鏈表的,樹的高度都是相對比較低的(基本符合矮矮胖胖(均衡)的結(jié)構(gòu))【這樣一來我們檢索的時間復雜度就是O(logn)】!從上一節(jié)的圖我們也可以看見,建立索引實際上就是建立一顆B+樹。

  • B+樹是一顆平衡樹,如果我們對這顆樹增刪改的話,那肯定會破壞它的原有結(jié)構(gòu)
  • 要維持平衡樹,就必須做額外的工作。正因為這些額外的工作開銷,導致索引會降低增刪改的速度。

4.B+樹索引和哈希索引的區(qū)別

一個經(jīng)典的B+樹索引數(shù)據(jù)結(jié)構(gòu)見下圖:

image

B+樹是一個平衡的多叉樹,從根節(jié)點到每個葉子節(jié)點的高度差值不超過1,而且同層級的節(jié)點間有指針相互鏈接。
在B+樹上的常規(guī)檢索,從根節(jié)點到葉子節(jié)點的搜索效率基本相當,不會出現(xiàn)大幅波動,而且基于索引的順序掃描時,也可以利用雙向指針快速左右移動,效率非常高。
因此,B+樹索引被廣泛應用于數(shù)據(jù)庫、文件系統(tǒng)等場景。
哈希索引的示意圖則是這樣的:
image

簡單地說,哈希索引就是采用一定的哈希算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節(jié)點到葉子節(jié)點逐級查找,只需一次哈希算法即可立刻定位到相應的位置,速度非常快。

從上面的圖來看,B+樹索引和哈希索引的明顯區(qū)別是:

如果是等值查詢,那么哈希索引明顯有絕對優(yōu)勢,因為只需要經(jīng)過一次算法即可找到相應的鍵值;當然了,這個前提是,鍵值都是唯一的。如果鍵值不是唯一的,就需要先找到該鍵所在位置,然后再根據(jù)鏈表往后掃描,直到找到相應的數(shù)據(jù);

從示意圖中也能看到,如果是范圍查詢檢索,這時候哈希索引就毫無用武之地了,因為原先是有序的鍵值,經(jīng)過哈希算法后,有可能變成不連續(xù)的了,就沒辦法再利用索引完成范圍查詢檢索;

同理,哈希索引也沒辦法利用索引完成排序,以及l(fā)ike ‘xxx%’ 這樣的部分模糊查詢(這種部分模糊查詢,其實本質(zhì)上也是范圍查詢);

哈希索引也不支持多列聯(lián)合索引的最左匹配規(guī)則;

B+樹索引的關(guān)鍵字檢索效率比較平均,不像B樹那樣波動幅度大,在有大量重復鍵值情況下,哈希索引的效率也是極低的,因為存在所謂的哈希碰撞問題。

5.B樹與B+樹

1.B樹解決了什么問題

如果拋開維護操作,那么B樹就像一棵“m叉搜索樹”(m是子樹的最大個數(shù)),時間復雜度為O(logm(n))。然而,B樹設計了一種高效簡單的維護操作,使B樹的深度維持在約log(ceil(m/2))(n)~logm(n)之間,大大降低樹高

image.png

再次強調(diào):

不要糾結(jié)于時間復雜度,與單純的算法不同,磁盤IO次數(shù)才是更大的影響因素。讀者可以推導看看,B樹與AVL的時間復雜度是相同的,但由于B樹的層數(shù)少,磁盤IO次數(shù)少,實踐中B樹的性能要優(yōu)于AVL等二叉樹。

同二叉搜索樹類似,每個節(jié)點存儲了多個key和子樹,子樹與key按順序排列。

頁表的目的是擴展內(nèi)存+加速磁盤讀寫。一個頁(Page)通常4K(等于磁盤數(shù)據(jù)塊block的大小,見inode與block的分析),從磁盤讀寫的角度出發(fā),操作系統(tǒng)每次以頁為單位將內(nèi)容從磁盤加載到內(nèi)存(以攤分尋道成本),修改頁后,再擇期將該頁寫回磁盤。考慮到頁表的良好性質(zhì),可以使每個節(jié)點的大小約等于一個頁(使m非常大),這每次加載的一個頁就能完整覆蓋一個節(jié)點,以便選擇下一層子樹;對子樹同理。對于頁表來說,AVL(或RBT)相當于1個key+2個子樹的B樹,由于邏輯上相鄰的節(jié)點,物理上通常不相鄰,因此,讀入一個4k頁,頁面內(nèi)絕大部分空間都將是無效數(shù)據(jù)。

假設key、子樹節(jié)點指針均占用4B,則B樹節(jié)點最大m * (4 + 4) = 8m B;頁面大小4KB。則m = 4 * 1024 / 8 = 512,一個512叉的B樹,1000w的數(shù)據(jù),深度最大 log(512/2)(10^7) = 3.02 ~= 4。對比二叉樹如AVL的深度為log(2)(10^7) = 23.25 ~= 24,相差了5倍以上。震驚!B樹索引深度竟然如此!

另外,B樹對局部性原理非常友好。如果key比較小(比如上面4B的自增key),則除了頁表的加成,緩存還能進一步預讀加速。美滋滋~

2.B樹的剩余問題

然而,如果要實際應用到數(shù)據(jù)庫的索引中,B樹還有一些問題:

  1. 未定位數(shù)據(jù)行
  2. 無法處理范圍查詢

問題1

數(shù)據(jù)表的記錄有多個字段,僅僅定位到主鍵是不夠的,還需要定位到數(shù)據(jù)行。有3個方案解決:

  1. 直接將key對應的數(shù)據(jù)行(可能對應多行)存儲在節(jié)點中。
  2. 數(shù)據(jù)行單獨存儲;節(jié)點中增加一個字段,定位key對應數(shù)據(jù)行的位置。
  3. 修改key與子樹的判斷邏輯,使子樹大于等于上一key小于下一key,最終所有訪問都將落于葉子節(jié)點;葉子節(jié)點中直接存儲數(shù)據(jù)行或數(shù)據(jù)行的位置。

方案1中,數(shù)據(jù)行通常非常大,存儲數(shù)據(jù)行將減少頁面中的子樹個數(shù),m減小樹高增大。假設數(shù)據(jù)行占用200B,可忽略組織B樹的指針,則新的m = 4 * 1024 / 200 = 20.48 ~= 21,深度最大 log(21/2)(10^7) ~= 7。增加了一倍以上的IO,不考慮。

方案2中,節(jié)點增加了一個字段。假設是4B的指針,則新的m = 4 * 1024 / 12 = 341.33 ~= 341,深度最大 log(341/2)(10^7) = 3.14 ~= 4。與3差別不大,可以考慮。

方案3的節(jié)點m與深度不變,但時間復雜度變?yōu)榉€(wěn)定的O(logm(n))。考慮。

問題2

實際業(yè)務中,范圍查詢的頻率非常高,B樹只能定位到一個索引位置(可能對應多行),很難處理范圍查詢。給出2種方案:

  1. 不改動:查詢的時候先查到左界,再查到右界,然后DFS(或BFS)遍歷左界、右界之間的節(jié)點。
  2. 在“問題1-方案3”的基礎上,由于所有數(shù)據(jù)行都存儲在葉子節(jié)點,B樹的葉子節(jié)點本身也是有序的,可以增加一個指針,指向當前葉子節(jié)點按主鍵順序的下一葉子節(jié)點;查詢時先查到左界,再查到右界,然后從左界到有界線性遍歷。

乍一看感覺方案1比方案2好——時間復雜度和常數(shù)項都一樣,方案1還不需要改動。但是別忘了局部性原理,不管節(jié)點中存儲的是數(shù)據(jù)行還是數(shù)據(jù)行位置,方案2的好處在于,葉子節(jié)點連續(xù)存儲,對頁表和緩存友好。而方案1則面臨節(jié)點邏輯相鄰、物理分離的缺點。

3.B+樹

綜上,問題1的方案2與問題2的方案1可整合為一種方案(基于B樹的索引),問題1的方案3與問題2的方案2可整合為一種(基于B+樹的索引)。實際上,數(shù)據(jù)庫、文件系統(tǒng)有些采用了B樹,有些采用B+樹。


image.png

主要變動如上所述:

修改key與子樹的組織邏輯,將索引訪問都落到葉子節(jié)點
按順序?qū)⑷~子節(jié)點串起來(方便范圍查詢)

6.索引優(yōu)化

1.優(yōu)先使用自增key作為主鍵

前面的分析中,假設用4B的自增key作為索引,則m可達到512,層高僅有3。使用自增的key有兩個好處:

1.自增key一般為int等整數(shù)型,key比較緊湊,這樣m可以非常大,而且索引占用空間小。最極端的例子,如果使用50B的varchar(包括長度),那么m = 4 * 1024 / 54m = 75.85 ~= 76,深度最大 log(76/2)(10^7) = 4.43 ~= 5,再加上cache缺失、字符串比較的成本,時間成本增加較大。同時,key由4B增長到50B,整棵索引樹的空間占用增長也是極為恐怖的(如果二級索引使用主鍵定位數(shù)據(jù)行,則空間增長更加嚴重)。

2.自增的性質(zhì)使得新數(shù)據(jù)行的插入請求必然落到索引樹的最右側(cè),發(fā)生節(jié)點分裂的頻率較低,理想情況下,索引樹可以達到“滿”的狀態(tài)。索引樹滿,一方面層高更低,一方面刪除節(jié)點時發(fā)生節(jié)點合并的頻率也較低。

2.最左前綴匹配

在mysql建立聯(lián)合索引時會遵循最左前綴匹配的原則,即最左優(yōu)先,在檢索數(shù)據(jù)時從聯(lián)合索引的最左邊開始匹配,示例:
對列col1、列col2和列col3建一個聯(lián)合索引

KEY index_col1_col2_col3 on test(col1,col2,col3);

聯(lián)合索引 index_col1_col2_col3 實際建立了(col1)、(col1,col2)、(col,col2,col3)三個索引。

SELECT * FROM table WHERE col1="1" AND clo2="2" AND clo4="4"

上面這個查詢語句執(zhí)行時會依照最左前綴匹配原則,檢索時會使用索引(col1,col2)進行數(shù)據(jù)匹配。索引的字段可以是任意順序的。

3.=、in自動優(yōu)化順序

不需要考慮=、in等的順序,mysql會自動優(yōu)化這些條件的順序,以匹配盡可能多的索引列。

如有索引(a, b, c, d),查詢條件c > 3 and b = 2 and a = 1 and d < 4與a = 1 and c > 3 and b = 2 and d < 4等順序都是可以的,MySQL會自動優(yōu)化為a = 1 and b = 2 and c > 3 and d < 4,依次命中a、b、c。

4.索引列不能參與計算

有索引列參與計算的查詢條件對索引不友好(甚至無法使用索引),如from_unixtime(create_time) = '2014-05-29'。

原因很簡單,如何在節(jié)點中查找到對應key?如果線性掃描,則每次都需要重新計算,成本太高;如果二分查找,則需要針對from_unixtime方法確定大小關(guān)系。

因此,索引列不能參與計算。上述from_unixtime(create_time) = '2014-05-29'語句應該寫成create_time = unix_timestamp('2014-05-29')。

5.能擴展就不要新建索引

如果已有索引(a),想建立索引(a, b),盡量選擇修改索引(a)為索引(a, b)。

新建索引的成本很容易理解。而基于索引(a)修改為索引(a, b)的話,MySQL可以直接在索引a的B+樹上,經(jīng)過分裂、合并等修改為索引(a, b)。

6.不需要建立前綴有包含關(guān)系的索引

如果已有索引(a, b),則不需要再建立索引(a),但是如果有必要,則仍然需考慮建立索引(b)。

7.選擇區(qū)分度高的列作索引

很容易理解。如,用性別作索引,那么索引僅能將1000w行數(shù)據(jù)劃分為兩部分(如500w男,500w女),索引幾乎無效。

區(qū)分度的公式是count(distinct <col>) / count(*),表示字段不重復的比例,比例越大區(qū)分度越好。唯一鍵的區(qū)分度是1,而一些狀態(tài)、性別字段可能在大數(shù)據(jù)面前的區(qū)分度趨近于0。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容