Mysql索引數據結構及優化建議

一、mysql數據結構

Mysql的兩種主要的存儲引擎都依賴的數據結構為B+tree,一種從B-tree改進而來的樹狀數據結構
本節將從幾個方面來介紹:
1. 介紹B-tree和B+tree;
2. 介紹兩種主要的存儲引擎如何實現索引;

1.1 介紹B-tree和B+tree

1.1.1 B-tree

B-tree名為多路搜索平衡樹,在此先定義一組值[key,data]key即為鍵,data即為key鍵所指向的值。
在大多數的文獻與書籍中,對于B-tree的定義有一些差別,本文中參考的是清華大學出版社的《數據結構(C語言版)》(2007年版)。

一棵m階的 B 樹,或為空樹,或為滿足下列特性的m叉樹:

  1. 樹中每一個結點至多有m棵子樹;
  2. 若根結點不是葉子結點,則至少有兩棵子樹;
  3. 除根結點之外的所有非終端結點至少有?m/2?棵子樹;
    4.所有的非終端結點中包含下列信息數據(n,A{0} ,K{1},A{1},K{2} ,A{2},...,K{n},A{n}), 其中:K{i}(i=1,...,n)為關鍵字,且K{i}<K{i+1}(i=1,...,n-1);A{i}(i=0,...,n)為指向子樹根結點的指針,且指針A_{i-1}所指子樹中所有結點的關鍵字均小于K{i}(i=1,...,n),A{n} 所指子樹中所有結點的關鍵字均大于K{n} ,n(?m/2??1≤n≤m?1)為關鍵字的個數;
  4. 所有的葉子結點都出現在同一層次上,并且不帶信息(可以看作是外部結點或查找失敗的結點,實際上這些結點不存在,指向這些結點的指針為空)。

B-tree示例圖:

B-tree

1.1.2 B+tree

B+treeB-tree的變體,對于階數為m的樹其中改變的地方有:

  1. B+tree中每個結點關鍵字個數范圍變為?m/2?≤n≤m,即結點的最左邊不是子樹指針而是關鍵字key;
  2. 內節點不存儲data,只存儲key;葉子節點不存儲指針,并且所有的關鍵字key都會在葉子結點再存儲一遍
  3. 一般B+tree都帶有每個葉子節點的指向相鄰葉子節點的順序指針

在做了以上改變后,B+tree的查找則都必須要查找到葉結點,而B-tree則可能在某個內結點即停止查找;還有B+tree的查找可以有根結點和起始葉結點兩個出發點。
B+tree示例圖:

B+tree

1.2 兩種存儲引擎如何實現索引

目前比較主流的存儲引擎貌似是MyISAM和InnoDB兩種,這里先介紹這兩個

1.2.1 MyISAM實現索引

MyISAM是MySQL的默認數據表類型,基于了傳統的ISAM類型,ISAMIndexed Sequential Access Method(有索引的順序訪問方法)的縮寫,MyISAM使用B+tree存儲引擎結構,葉節點的data域存放的是數據記錄的地址。
MyISAM采用的是索引文件和數據文件分離存儲,索引文件中存儲的是數據文件中相應數據的地址,只對索引采取B+tree數據結構。

`MyISAM`索引原理圖

這里使用別人已有的教學例子

設表共有三列,假設我們以Col1為主鍵,則上圖是一個MyISAM表的主索引(Primary key)示意??梢钥闯?code>MyISAM的索引文件僅僅保存數據記錄的地址。在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復。如果我們在Col2上建立一個輔助索引,則此索引的結構如圖

輔助索引原理圖

這樣在利用索引查詢時,會按照B+tree的查詢算法進行查找,當查找到指定的key時,則會讀取到相應葉結點的data域,根據其中的地址信息取出相應的數據。

1.2.2 InnoDB實現索引

InnoDB使用的也是B+tree數據結構存儲器引擎,但是和MyISAM差別比較大。
首先InnoDB的數據文件本身就是索引文件。即數據文件就按照B+tree的結構存儲,這棵樹的key即是InnoDB中的主鍵,這棵樹的葉結點對應的data域存儲的是完整的數據記錄。

輔助索引原理圖

這種索引叫做聚集索引。因為InnoDB的數據文件本身要按主鍵聚集,所以InnoDB要求表必須有主鍵,對比MyISAM,MyISAM則不是非要主鍵,MyISAM的索引叫做非聚集索引,如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識數據記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含字段作為主鍵,這個字段長度為6個字節,類型為長整形。
第二點不同,在InnoDB中的輔助索引和MyISAM中的輔助索引也不一樣,InnoDB的輔助索引也采用的B+tree結構存儲,但是輔助索引樹的葉結點的data域則存儲的是相應的主鍵值,而不是像MyISAM存儲地址。
例如用上述圖中的第三列做InnoDB的輔助索引,則得下圖:
輔助索引原理圖

聚集索引這種實現方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄。但這樣的好處是,如果輔助索引data存放的數據指針,當數據移動或者數據頁分裂時,需要更新data域行指針的值,這就增加維護成本。data存在主鍵的值,就沒有這個問題。數據移動和數據頁分裂,主鍵索引會自動更新。data關聯主鍵的值,不需要更新,相當于增加一個間接層。這個間接層對性能的影響也很小,因為通過主鍵定位記錄是非??斓?。

由此我們清楚了InnoDB的主鍵最好使用單調有序的字段,并且不適合太長的字段,因為每個輔助索引都存儲主索引字段,太長的主鍵會造成輔助索引空間太大,一般使用的是自增的整型字段。

二、 索引分類

2.1 B-Tree索引及B+Tree索引

前面已經介紹了InnoDBMyISAM兩種最常用的存儲引擎所基于的數據結構---B-TreeB+Tree,以及是如何存儲索引和鍵值的,基于這兩種數據結構的索引就是B-Tree索引及B+Tree索引。
本文的示例數據表People,創建如下表:

建表語句

接著介紹下對于這種索引有效的查詢類型。

全值匹配

全值匹配指的是和索引中的所有列進行匹配,以上面的例子即為可查詢姓zhang,名san,出生于1960-01-01的人

匹配最左前綴

即只使用索引的第一列,上述例子中即可用索引查詢姓zhang的人

匹配列前綴

也可以只匹配某一列的值得開頭部分。上述例子中即可用索引查詢姓氏中以zh開頭的信息。也是只使用了索引的第一列。

匹配范圍值

前面提到的索引即可范圍查詢按字母順序姓氏在Lizhang之間的人。同樣是只使用了第一列

精確匹配某一列并范圍匹配另外一列

上述例子中即可精確匹配所有姓氏為zhang,并且按字母順序名字在sansi之中的信息。
即第一列surname全匹配,第二列name范圍匹配。

只訪問索引的查詢

B-Tree通??梢灾С帧爸辉L問索引的查詢”,即查詢只需要訪問索引,而無需訪問數據行。

上述中創建的索引叫做多列索引,對應的另一類即為單列索引,在‘高性能索引策略/多列索引’中會詳細介紹。

2.2 哈希索引 及 R-Tree索引

hash 哈希索引基于哈希表實現,只有精確匹配索引所有列的查詢才有效。對于每一行數據,存儲引擎都會對所有的索引列計算一個哈希嗎(hash code),哈希嗎是一個較小的值,并且不同鍵值的行計算出來的哈希碼也不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。 在MySQL中,只有Memory引擎顯式支持哈希索引。 InnoDB引擎有一個特殊的功能叫做“自適應哈?!?,當InnodeDB注意到某些索引值被使用的非常頻繁時,它會在內存中基于B-Tree索引之上在創建一個哈希索引,這樣就讓B-Tree索引也具有哈希索引的一些優點,比如快速的哈希查找。

空間數據索引(R-Tree) MyISAM表支持空間索引,可以用做地理數據存儲。和B-Tree索引不同,這類索引無須前綴查詢。空間索引會從所有維度來索引數據。查詢時,可以有效地使用任意維度來組合查詢。必須使用mysql的GIS相關函數如MBRCONTAINS()等來維護數據。mysql的GIS支持并不完善,所以大部分人都不會使用這個特性。開源數據庫中對GIS的解決方案做的比較好的是PostgreSQL的postGIS。

2.3 關于B-tree索引的限制

  1. 最左前綴匹配原則,即必須按照多列索引的最左列開始查找,或者每一列索引的最左前面的幾個字符匹配查找,如果不是按照索引的最左列開始查找,則無法使用索引。例如上面的例子中無法用索引查找名為san的數據。類似的,也無法查找姓氏以某個字母為結尾的數據。
  2. 不能跳過索引中的列,例如上述例子中的索引無法用于查詢surname = peng and birthday = 1994-05-17;的信息,因為沒有指定name條件。
  3. 如果查詢中有某一個列的范圍查詢,則其右邊所有列都無法使用索引進行優化查找。例如上述表中,條件為where surname = peng and name = tu% and birthday = 1994-05-17的查詢無法利用到birthday索引,因為name列有個模糊查詢,屬于范圍查詢,但是范圍查詢前面的列是可以利用索引的。

由此可以得出,類似的在where條件查詢中想進行模糊查詢時,aaa%則可以利用上索引,而%aaa%以及%aaa則無法利用上索引,以及范圍查詢條件最好放到后面,或者范圍查詢列值的數量有限時,可以通過使用多個等于條件來代替范圍查詢。
還有,盡可能將需要做范圍查詢的列放到索引的后面,這樣優化器能使用盡可能多的索引列。 索引的順序很重要

三、 高性能的索引策略

3.1 獨立的列

‘獨立的列’是指索引列不能是表達式的一部分,也不能是函數的參數。
例如:mysql> select x from table where x+1 = 5;
雖然可以很容易的分辯出x的值為4,但是Mysql則無法利用該索引(如果x是索引的話)。盡量養成簡化where的習慣,始終將索引單獨的的放在符號的一側,這樣Mysql才可以利用上索引。

3.2 索引選擇性 和 前綴索引

先介紹索引選擇性
索引的選擇性是指,不重復的索引值和數據表的記錄總數(T)的比值,范圍在 1/T1 之間。不重復的索引值也稱為基數(cardinality)。因為選擇性高的索引可以讓Mysql在查找時過濾掉更多的行,所以索引的選擇性越高則查詢效率越高。唯一索引的基數即為數據的條數,其選擇性為1,所以唯一索引的性能最好。
對于TEXT或是VARCHAR類型的列,當這個列中的值長度很大又必須利用其進行查詢時,就必須使用這個列的前幾位值以作索引,即前綴索引,因為整個列的值當做索引時B+tree會占用非常大的空間,查找也不方便。
而制定前綴索引時要注意的一點就是這個前綴索引的選擇性需要和整個列的選擇性接近,這樣性能不會影響太多,同時還不能太長而占用太多空間。
這有一種尋找最佳前綴索引的方式,即根據選擇性的定義來進行計算其完整列的選擇性及其前綴的選擇性以便對比。
假設:有一個表中的某一列,名為session,其值為十六進制的ID
首先進行完整列的選擇性的計算
mysql> SELECT COUNT(DISTINCT session) / COUNT( * ) FROM table;
如果是唯一ID,則其值應為1,然后計算這個列的前綴長度為x的選擇性,
mysql> SELECT COUNT(DISTINCT LEFT( session , x )) / COUNT( * ) FROM table;
得到一個小數值,可以改變x的值來計算不同前綴的選擇性,最后在多個值中,綜合考慮選擇性接近性和前綴長度的兩個方面,可以選出一個較為合適的前綴索引。
創建一個前綴長度為x的索引:
mysql> ALTER TABLE table ADD KEY (session (x) );

小貼士:例如某個列的值存的是郵箱時,可以先字符串反轉,或者根據@標識符前后顛倒,再存入數據庫,再建立前綴索引,這樣就可以根據前綴索引快速查出特定郵箱域名的所有信息了。

3.3 多列索引

Mysql執行查詢時,如果是使用多列索引,則會先查詢符合第一列索引的數據集,然后再在這一部分數據集中查詢出符合第二列的數據,以此類推,這樣在不用掃描數據的情況下就能選出數據;
而如果一個多列索引拆分成多個單列索引的話,Mysql在執行查詢時,只會從中選出一個限制最嚴格的索引以供使用,其他的索引就浪費了,所以在上述情況中多列索引性能要好
:在Mysql 5.0之后,Mysql添加了‘索引合并’策略,一定程度上可以使用表上的多個單列索引來定位數據。
實際上,Mysql 5.0之后有種算法可以查詢能夠同時使用這兩個單列索引進行掃描,并將結果合并。
這種算法的三個變種: or條件的聯合(union), and條件的相交(intersection) 及 組合前兩種情況的聯合及相交
例如:

`and`優化

可見在Extra列中,顯示為兩種索引的相交優化。
雖說如此,這種處理會帶來一些負面影響:

  • 當服務器需要對多個索引做聯合操作時,即有多個or條件時,通常需要消耗CPU和內存資源在算法的緩存、排序和合并操作上。當其中一些索引的選擇性不高而導致數據量很大時此情況更甚。
  • 其次,這些優化計算所消耗的成本是不會被優化器計入“查詢成本”中,當消耗了更多的CPU和內存資源而不知曉時,還是蠻可怕的。
  • 還有一個數據庫服務器有多個任務需要查詢時,這種優化會影響查詢的并發性,降低效率。

此外,這種優化有使用限制,例如,我的where條件中的使用的是a_idb_id兩種單列索引,也只有在查詢這兩列的值的情況下才會運用到優化,當查詢這兩列之外的值時就無法使用優化。如下圖:

優化失效

查詢多一個gender字段時,Extra列顯示并無優化策略。相同模式的sql語句,可能有時能使用索引,有時不能使用索引。是否能使用索引,取決于mysql查詢優化器對統計數據分析后,是否認為使用索引更快。
所以相比之下,對于一些常用多個需要聯合查詢的條件創建一個多列索引更好些。

3.4 選擇合適的索引列順序

這一節主要是針對 B-Tree索引,常用的存儲引擎即為InnoDBMyISAM。

一條經驗法則(從書上學來的),即 : 將選擇性最高的列放到索引放到索引的最前列。
按照上述的關于選擇性的介紹中,可以用一條sql計算出所有的需要用到的索引的相對應的選擇性,又或者在知道了該表中的大致數據記錄數的情況下,用show index from table來查看所有的索引對應的基數值(Cardinality),也能大致比較出索引選擇性的高低。

查看索引

上圖可見People表中所有索引的基數。
也就是提醒大家,別忘了where子句中的排序、分組和范圍條件等因素,說不定你就在這踩了坑。

3.5 覆蓋索引

如果一個索引包含了所有需要查詢的字段的值,就稱之為“覆蓋索引”。
覆蓋索引有以下幾點好處:

  • 覆蓋索引對于I/O密集型的應用很有幫助,因為數據量比較小則可以放入主存中,加快讀取速度
  • MyISAM存儲引擎在內存中只緩存了引擎,數據則依賴操作系統緩存,所以只查詢索引值會飛快
  • InnoDB存儲引擎使用的聚簇索引,覆蓋索引更有用。因為InnoDB的二級索引的B-Tree的葉結點存儲的是對應的一級索引,所以如果二級索引覆蓋了所要查詢的值則會少一次利用一級索引查詢。效率提升很多。

當發起一個索引覆蓋查詢時,在Extra列中可見“Using index”的信息。
例如:

覆蓋索引

建立的多列索引包含了所要查詢的字段,索引可直接查詢Using index而提升速度。
:
其一:當查詢中使用了一個 * 時,肯定是無法使用覆蓋索引的;
其二:如果不符合最左前綴匹配原則,也無法使用索引覆蓋。

3.6 冗余索引

冗余即多余
例如,創建了key (a_id, b_id)索引時,如果再創建一個key (a_id)就是多余的,因為它只是多列索引的前綴而已
但是當創建key (b_id)時,就不屬于冗余索引了,因為上述的多列索引是無法單獨使用b_id 作索引查詢。
理論上冗余索引會帶來一定程度上的查詢影響,與當前條查詢語句相關的索引數量越多,效率越低,原因在于優化器需要從information_schema中獲取相關索引的metadata信息并分析,索引數量越多,這個過程越漫長。雖然一般來說這個影響不太大。

四、總結

索引太復雜,原理要理解,創建需謹慎,使用多思考。
(這句是給我自己說的)

以上。

參考文獻
[1]:《數據結構(C語言版)》(2007年版),清華大學出版社,編著者為嚴蔚敏,吳偉民。
[2]:http://blog.codinglabs.org/articles/theory-of-mysql-index.htm
[3]:《高性能MySQL》,電子工業出版社,Baron Schwartz,Peter Zaitsev,Vadim 著;寧海元,周振興,彭立勛等 譯

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容