官方定義:索引(Index)是幫助MySQL高效獲取數據的數據結構
本質:索引是數據結構php
查詢是數據庫的最主要功能之一。咱們都但願查詢速度能儘量快,所以數據庫系統的設計者會從查詢算法角度優化css
最基本的查詢算法固然是順序查找(linear search),這種複雜度爲O(n)的算法在數據量很大時顯然是糟糕的
好在CS的發展提供了不少更優秀的查找算法,如二分查找(binary search)、二叉樹查找(binary tree search)等
稍微分析一下會發現,每種查找算法都只能應用於特定數據結構,如二分查找要求被檢索數據有序,而二叉樹查找只能應用於二叉查找樹,但數據自己的組織結構不可能徹底知足各類數據結構
(例如,理論上不可能同時將兩列都按順序進行組織)
因此,在數據以外,數據庫系統還維護着知足特定查找算法的數據結構
,這些數據結構以某種方式引用(指向)數據,這樣就能夠在這些數據結構上實現高級查找算法
這種ADT,就是索引
java
Col2
的查找,可維護一個右邊所示二叉查找樹,每一個節點分別包含索引鍵值及一個指向對應數據記錄物理地址的指針,這樣就能夠運用二叉查找在O(log2 N)內取到相應數據
雖然這是一個貨真價實的索引,但實際數據庫系統幾乎沒有使用二叉查找樹或其進化品種紅黑樹(red-black tree)實現node
目前大部分數據庫系統及文件系統都採用B Tree或其變種B+Tree做爲索引結構mysql
定義數據記錄爲一個二元組[key, data]web
B Tree有以下特色:算法
若是某個指針在節點node的左右相鄰key分別是keyi,keyi+1且不爲null,則其指向節點的全部key小於v(keyi+1)且大於v(keyi)sql
因爲B Tree的特性,按key檢索數據的算法很是直觀數據庫
bTreeSearch(node, key) { if(node == null) return null; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return bTreeSearch(point[i]->node); } return bTreeSearch(point[i+1]->node); } data = bTreeSearch(root, my_key);
關於B-Tree有一系列有趣的性質,例如一個度爲d的B-Tree,設其索引N個key,則其樹高h的上限爲
檢索一個key,其查找節點個數的漸進時間複雜度爲
B-Tree有許多變種,其中最多見的是B+Tree,MySQL廣泛用其實現索引
與B Tree相比,B+Tree有如下不一樣點緩存
myshaym中指向的是數據的物理地址
因爲並非全部節點都具備相同的域,所以B+Tree中葉節點和內節點通常大小不一樣
這點與B Tree不一樣,雖然B Tree中不一樣節點存放的key和指針可能數量不一致,可是每一個節點的域和上限是一致的,因此在實現中B Tree每每對每一個節點申請同等大小的空間
通常來講,B+Tree比B Tree更適合實現外存儲索引結構
在經典B+Tree的基礎上進行了優化,增長了順序訪問指針
如圖4所示,在B+Tree的每一個葉節點增長一個指向相鄰葉節點指針,造成帶有順序訪問指針的B+Tree
此優化的目的是提升區間訪問的性能,例如圖4中若是要查詢key爲從18到49的全部數據記錄,當找到18後,只需順着節點和指針順序遍歷就能夠一次性訪問到全部數據節點,極大提升了區間查詢效率
紅黑樹也可用來實現索引,可是文件系統及數據庫系統廣泛採用B/+Tree,何也?
通常來講,索引自己也很大,不可能全存內存,每每以索引文件的形式存在磁盤
索引查找過程當中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高几個數量級,因此評價一個數據結構做爲索引的優劣最重要的指標就是在查找過程當中磁盤I/O操做次數的漸進複雜度。
B樹在提升了IO性能的同時並無解決元素遍歷的我效率低下的問題,正是爲了解決這個問題,B+樹應用而生.B+樹只須要去遍歷葉子節點就能夠實現整棵樹的遍歷.並且在數據庫中基於範圍的查詢是很是頻繁的,而B樹不支持這樣的操做(或者說效率過低).
換句話說,索引的結構組織要儘可能減小查找過程當中磁盤I/O的存取次數
計算機使用的主存基本都是隨機讀寫存儲器(RAM),抽象出一個十分簡單的存取模型來講明RAM的工做原理
從抽象角度看,主存是一系列的存儲單元組成的矩陣,每一個存儲單元存儲固定大小的數據
每一個存儲單元有惟一的地址,現代主存的編址規則比較複雜,這裏將其簡化成一個二維地址:經過一個行地址和一個列地址能夠惟必定位到一個存儲單元
存取過程
當系統須要讀取主存時,將地址信號經過地址總線傳給主存,主存讀到地址信號後,解析信號並定位到指定存儲單元,而後將此存儲單元數據放到數據總線,供其它部件讀取
寫主存
過程相似,系統將要寫入單元地址和數據分別放在地址總線和數據總線上,主存讀取兩個總線的內容,作相應的寫操做
這裏能夠看出,主存存取的時間僅與存取次數呈線性關係,由於不存在機械操做,兩次存取的數據的「距離」不會對時間有任何影響,例如,先取A0再取A1和先取A0再取D3的時間消耗是同樣的
索引通常以文件形式存儲在磁盤上,索引檢索須要磁盤I/O
與主存不一樣,磁盤I/O存在機械消耗,所以磁盤I/O時間消耗巨大
磁盤由大小相同且同軸的圓形盤片組成,磁盤能夠轉動(各磁盤必須同步轉動)
在磁盤的一側有磁頭支架,磁頭支架固定了一組磁頭,每一個磁頭負責存取一個磁盤的內容。磁頭不能轉動,可是能夠沿磁盤半徑方向運動(實際是斜切向運動),每一個磁頭同一時刻也必須是同軸的,即從正上方向下看,全部磁頭任什麼時候候都是重疊的(不過目前已經有多磁頭獨立技術,可不受此限制)
當須要從磁盤讀取數據時,系統會將數據邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即肯定要讀的數據在哪一個磁道,哪一個扇區
爲了讀取這個扇區的數據,須要將磁頭放到這個扇區上方,爲了實現這一點,磁頭須要移動對準相應磁道,這個過程叫作尋道,所耗費時間叫作尋道時間,而後磁盤旋轉將目標扇區旋轉到磁頭下,這個過程耗費的時間叫作旋轉時間
因爲存儲介質特性,磁盤自己存取就比主存慢,再加上機械運動耗費,磁盤的存取速度每每是主存的幾百萬分之一,所以爲了提升效率,要儘可能減小磁盤I/O。
爲了達到這個目的,磁盤每每不是嚴格按需讀取,而是每次都會預讀,即便只須要一個字節,磁盤也會從這個位置開始,順序向後讀取必定長度的數據放入內存。這樣作的理論依據是計算機科學中著名的局部性原理:
當一個數據被用到時,其附近的數據也一般會立刻被使用
程序運行期間所須要的數據一般比較集中
因爲磁盤順序讀取的效率很高(不須要尋道時間,只需不多的旋轉時間),所以對於具備局部性的程序來講,預讀能夠提升I/O效率
預讀的長度通常爲頁(page)的整數倍
頁是存儲器的邏輯塊,操做系統每每將主存和磁盤存儲區分割爲連續的大小相等的塊,每一個存儲塊稱爲一頁(在許多操做系統中,頁大小一般爲4k),主存和磁盤以頁爲單位交換數據
當程序要讀取的數據不在主存中時,會觸發一個缺頁異常,此時系統會向磁盤發出讀盤信號,磁盤會找到數據的起始位置並向後連續讀取一頁或幾頁載入內存中,而後異常返回,程序繼續運行
通常使用磁盤I/O次數評價索引結構的優劣
檢索一次最多須要訪問h個節點
數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設爲等於一個頁,這樣每一個節點只須要一次I/O就能夠徹底載入
爲了達到這個目的,在實際實現B-Tree還須要使用以下技巧:
綜上所述,用B-Tree做爲索引結構效率是很是高的
h明顯要深的多。因爲邏輯上很近的節點(父子)物理上可能很遠,沒法利用局部性,因此紅黑樹的I/O漸進複雜度也爲O(h),效率明顯比B-Tree差不少
B+Tree更適合外存索引,緣由和內節點出度d有關
從上面分析能夠看到,d越大索引的性能越好
出度的上限取決於節點內key和data的大小
:
dmax=floor(pagesize/(keysize+datasize+pointsize))
floor表示向下取整。因爲B+Tree內節點去掉了data域,所以能夠擁有更大的出度,更好的性能
索引屬於存儲引擎級別的概念,不一樣存儲引擎對索引的實現方式是不一樣的,主要討論MyISAM和InnoDB兩個存儲引擎的索引實現方式
使用B+Tree做爲索引結構,葉節點data域存放數據記錄的地址
設Col1爲主鍵,則圖8是一個MyISAM表的主索引(Primary key)示例
能夠看出MyISAM的索引文件僅僅保存數據記錄的地址
在MyISAM中,主/輔索引在結構上沒有任何區別,只是主索引要求key惟一
,而輔索引key可重複
若是咱們在Col2上創建一個輔索引
一樣也是一顆B+Tree,data域保存數據記錄的地址。
所以,MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,則取出其data域的值,而後以data域的值爲地址,讀取相應數據記錄。
MyISAM的索引方式也叫作「非彙集」的,之因此這麼稱呼是爲了與InnoDB的彙集索引區分
雖然InnoDB也使用B+Tree做爲索引結構,但具體實現方式卻與MyISAM大相徑庭
第一個重大區別是
InnoDB的數據文件自己就是索引文件
彙集索引
第二個與MyISAM索引的不一樣是
InnoDB的輔索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的全部輔助索引都引用主鍵做爲data域
這裏以英文字符的ASCII碼做爲比較準則
彙集索引這種實現方式使得按主鍵的搜索十分高效,可是輔助索引搜索須要檢索兩遍索引:
知道了InnoDB的索引實現後,就很容易明白爲何不建議使用過長的字段做爲主鍵,由於全部輔索引都引用主索引,過長的主索引會令輔索引變得過大
再如,用非單調的字段做爲主鍵在InnoDB中不是個好主意,由於InnoDB數據文件自己是一顆B+Tree,非單調的主鍵會形成在插入新記錄時數據文件爲了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增字段做爲主鍵則是一個很好的選擇
聚簇索引並非一種單獨的索引類型,而是一種數據存儲方式
具體的細節依賴於其實現方式,但innoddb 的聚簇索引實際上在同一個結構中保存了B-Tree索引和數據行
是對磁盤上實際數據從新組織以按指定的一個或多個列的值排序的算法。特色是存儲數據的順序和索引順序一致。
通常狀況下主鍵會默認建立聚簇索引,且一張表只容許存在一個聚簇索引
當表有聚簇索引時,它的數據實際上存放在索引的葉子頁(leaf page)中
術語‘聚簇’
表示數據行和相鄰的鍵值進錯的存儲在一塊兒
由於沒法同時把數據行存放在兩個不一樣的地方,因此在一個表中只能有一個聚簇索引
(不過,覆蓋索引能夠模擬多個聚簇索引的狀況)。
InnoDb將經過主鍵彙集數據。
若是沒有定義主鍵,InnoDB 會選擇一個惟一的非空索引代替
若是沒有這樣的索引,InnoDB 會隱式定義一個主鍵來做爲聚簇索引
InnoDB值彙集在同一個頁面中的記錄,包含相鄰鍵值的頁面可能會相距很遠
聚簇索引和非聚簇索引的數據分佈有區別,以及對應的主鍵索引和二級索引的數據分佈也有區別
來看看InnoDB和MyISAM是如何存儲下面的這個表的
CREATE TABLE layout_test( col1 int not null, col2 int not null, primary key (col1), key(col2) );
假設該表的主鍵取值爲1-1w,按照隨機順序插入,並使用OPTIMIZE TABLE
命令優化
換句話說,數據在磁盤的存儲方式已經最優,但進行的順序是隨機的
列col2的值時從1-100之間隨機賦值,因此有不少重複的值
MyIsam按照數據插入的順序存儲在磁盤上
實際上,MyISAM 中主鍵索引和其餘索引在結構上沒有什麼不一樣
主鍵索引就是一個名爲PRIMARY的惟一非空索引
由於InnoDB支持聚簇索引,索引使用很是不一樣的方式存儲一樣的數據。在InnoDB中,聚簇索引「是」表,因此不像myISAM那樣須要獨立的行存儲
聚簇索引的缺點:
聚簇索引最大限度的提升了io密集型應用的性能,但若是數據所有存放在內存中,則訪問的順序就沒那麼重要了,聚簇索引也就沒有什麼優點了。
插入速度嚴重依賴插入順序。按照主鍵的順序插入是加載數據到innodb表中速度最快的方式。但若是不是按照主鍵順序加載數據,那麼加載完成後最好使用OPTIMIZE TABLE 命令來從新組織一下表。
更新聚簇索引的代價很高,由於會強制InooDB將每一個更新的數據移動到新的位置。
基於聚簇索引的表在插入行,或者主鍵被更新致使須要移動行的時候,可能面臨’頁分裂(page split)‘的問題。當行的主鍵值要求必須將這一行插入到某個已滿的頁中時。存儲引擎,存儲引擎會將該頁分裂成兩個頁面來容納該行,這就是一次頁分裂操做。頁分裂會致使表佔用更多的存儲空間。
聚簇索引可能致使全表掃描變慢,尤爲是行比較稀疏,或者因爲頁分裂致使數據存儲不連續的時候。
二級索引(非聚簇索引)可能比想象的要更大,由於在二級索引的子節點包含了最優一個幾點可能讓人有些疑惑,爲何二級索引須要兩次索引查找?答案在於二級索引中保存的「行指針」的實質。要記住,二級索引葉子節點保存的不是隻想物理位置的指針,而是行的主鍵值。
這意味着經過二級索引進行查找行,存儲引擎須要找到二級索引的子節點得到對應的主鍵值,而後根據這個值去聚簇索引總超找到對應的行。這裏作了重複的工做:兩次B-Tree查找,而不是一次。對於InnoDB,自適應哈希索引可以減小這樣重複工做。
在《數據庫原理》一書中是這麼解釋聚簇索引和非聚簇索引的區別的:
所以,MYSQL中不一樣的數據存儲引擎對聚簇索引的支持不一樣就很好解釋了。
下面,咱們能夠看一下MYISAM和INNODB兩種引擎的索引結構。
如原始數據爲:
MyISAM引擎的數據存儲方式,如圖
MYISAM引擎的索引文件(.MYI)和數據文件(.MYD)是相互獨立的。
而InnoDB按聚簇索引的形式存儲數據,因此它的數據佈局有着很大的不一樣。它存儲數據的結構大體以下:
注:聚簇索引中的每一個葉子節點包含主鍵值、事務ID、回滾指針(rollback pointer用於事務和MVCC)和餘下的列(如col2)。
INNODB的二級索引與主鍵索引有很大的不一樣。InnoDB的二級索引的葉子包含主鍵值,而不是行指針(row pointers),這減少了移動數據或者數據頁面分裂時維護二級索引的開銷,由於InnoDB不須要更新索引的行指針。其結構大體以下:
INNODB和MYISAM的主鍵索引與二級索引的對比:
InnoDB的的二級索引的葉子節點存放的是KEY字段加主鍵值。所以,經過二級索引查詢首先查到是主鍵值,而後InnoDB再根據查到的主鍵值經過主鍵索引找到相應的數據塊。而MyISAM的二級索引葉子節點存放的仍是列值與行號的組合,葉子節點中保存的是數據的物理地址。因此能夠看出MYISAM的主鍵索引和二級索引沒有任何區別,主鍵索引僅僅只是一個叫作PRIMARY的惟1、非空的索引,且MYISAM引擎中能夠不設主鍵。
(1) 定義有主鍵的列必定要創建索引 : 主鍵能夠加速定位到表中的某行
(2) 定義有外鍵的列必定要創建索引 : 外鍵列一般用於表與表之間的鏈接,在其上建立索引能夠加快表間的鏈接
(3) 對於常常查詢的數據列最好創建索引
① 對於須要在指定範圍內快速或頻繁查詢的數據列,由於索引已經排序,其指定的範圍是連續的,查詢能夠利用索引的排序,加快查詢的時間
② 常常用在 where
子句中的數據列,將索引創建在where
子句的集合過程當中,對於須要加速或頻繁檢索的數據列,可讓這些常常參與查詢的數據列按照索引的排序進行查詢,加快查詢的時間
MySQL的優化主要分爲
本章討論的高性能索引策略主要屬於結構優化範疇
爲了討論索引策略,須要一個數據量不算小的數據庫做爲示例
選用MySQL官方文檔中提供的示例數據庫之一:employees
這個數據庫關係複雜度適中,且數據量較大。下圖是這個數據庫的E-R關係圖(引用自MySQL官方手冊):
高效使用索引的首要條件是知道什麼樣的查詢會使用到索引,這個問題和B+Tree中的「最左前綴原理」有關,下面經過例子說明最左前綴原理
MySQL中的索引能夠以必定順序引用多列,這種索引叫作聯合索引
,通常的,一個聯合索引是一個有序元組<a1, a2, …, an>,其中各個元素均爲數據表的一列
包含知足查詢的全部列
只須要讀索引而不用讀數據,大大提升查詢性能。有如下優勢:
(1)索引項一般比記錄要小,使得MySQL訪問更少的數據
(2)索引都按值排序存儲,相對於隨機訪問記錄,須要更少的I/O
(3)大多數據引擎能更好的緩存索引。好比MyISAM只緩存索引
(4)覆蓋索引對於InnoDB表尤爲有用,由於InnoDB使用彙集索引
組織數據,若是二級索引中包含查詢所需的數據,就再也不須要在彙集索引中查找了
覆蓋索引只有B-TREE索引存儲相應的值
並非全部存儲引擎都支持覆蓋索引(Memory/Falcon)
對於索引覆蓋查詢(index-covered query),使用EXPLAIN
時,能夠在Extra
列中看到Using index
在大多數引擎中,只有當查詢語句所訪問的列是索引的一部分時,索引纔會覆蓋
可是,InnoDB
不限於此,InnoDB
的二級索引在葉節點中存儲了primary key的值
以employees.titles表爲例,下面先查看其上都有哪些索引:
從結果中能夠看到titles表的主索引爲<emp_no, title, from_date>,還有一個輔助索引<emp_no>
爲了不多個索引使事情變複雜(MySQL的SQL優化器在多索引時行爲比較複雜),咱們將輔助索引drop掉
ALTER TABLE employees.titles DROP INDEX emp_no;
這樣就能夠專心分析索引PRIMARY
索引對順序敏感
,可是因爲MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引
當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,因此能夠被用到,可是隻能用到一部分,即條件所組成的最左前綴
上面的查詢從分析結果看用到了PRIMARY索引,可是key_len爲4,說明只用到了索引的第一列前綴
from_date
雖然也在索引中,可是因爲title
不存在而沒法和左前綴鏈接,所以須要對結果進行過濾from_date
(這裏因爲emp_no
惟一,因此不存在掃描)
from_date
也使用索引而不是where過濾,能夠增長一個輔助索引<emp_no, from_date>
,此時上面的查詢會使用這個索引
emp_no
與from_date
之間的「坑」填上
首先咱們看下title一共有幾種不一樣的值
只有7種
在這種成爲「坑」的列值比較少的狀況下,能夠考慮用「IN」來填補這個「坑」從而造成最左前綴
「填坑」後性能提高了一點。若是通過emp_no篩選後餘下不少數據,則後者性能優點會更加明顯。固然,若是title的值不少,用填坑就不合適了,必須創建輔助索引
因爲不是最左前綴,這樣的查詢顯然用不到索引
此時能夠用到索引,通配符%不出如今開頭,則能夠用到索引,但根據具體狀況不一樣可能只會用其中一個前綴
索引最多用於一個範圍列,所以若是查詢條件中有兩個範圍列則沒法全用到索引
用了「between」並不意味着就是範圍查詢,例以下面的查詢:
看起來是用了兩個範圍查詢,但做用於emp_no上的「BETWEEN」實際上至關於「IN」,也就是說emp_no實際是多值精確匹配。能夠看到這個查詢用到了索引所有三個列。所以在MySQL中要謹慎地區分多值匹配和範圍匹配,不然會對MySQL的行爲產生困惑。
若是查詢條件中含有函數或表達式,則MySQL不會爲這列使用索引(雖然某些在數學意義上可使用)
雖然這個查詢和狀況五中功能相同,可是因爲使用了函數left,則沒法爲title列應用索引,而狀況五中用LIKE則能夠。再如:
① 以「%」開頭的LIKE語句,模糊匹配
② OR語句先後沒有同時使用索引
③ 數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換爲int型)
答案是否認的。由於索引雖然加快了查詢速度,但索引也是有代價的:索引文件自己要消耗存儲空間
Index Selectivity = Cardinality / #T
有一種與索引選擇性有關的索引優化策略叫作前綴索引,就是用列的前綴代替整個列做爲索引key,當前綴長度合適時,能夠作到既使得前綴索引的選擇性接近全列索引,同時由於索引key變短而減小了索引文件的大小和維護開銷。下面以employees.employees表爲例介紹前綴索引的選擇和使用。
從圖12能夠看到employees表只有一個索引<emp_no>
,那麼若是咱們 想按名字搜索一我的,就只能全表掃描了:
若是頻繁按名字搜索員工,這樣顯然效率很低,所以咱們能夠考慮建索引。有兩種選擇,建<first_name>或<first_name, last_name>,看下兩個索引的選擇性:
<first_name>顯然選擇性過低,<first_name, last_name>選擇性很好,可是first_name和last_name加起來長度爲30,有沒有兼顧長度和選擇性的辦法?能夠考慮用first_name和last_name的前幾個字符創建索引,例如<first_name, left(last_name, 3)>,看看其選擇性:
選擇性還不錯,但離0.9313仍是有點距離,那麼把last_name前綴加到4:
這時選擇性已經很理想了,而這個索引的長度只有18,比<first_name, last_name>短了接近一半,咱們把這個前綴索引 建上:
first_name_last_name4
(first_name, last_name(4));此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:
性能的提高是顯著的,查詢速度提升了120多倍。
前綴索引兼顧索引大小和查詢速度,可是其缺點是不能用於ORDER BY和GROUP BY操做,也不能用於Covering index(即當索引自己包含查詢所需所有數據時,再也不訪問數據文件自己)。
在使用InnoDB存儲引擎時,若是沒有特別的須要,請永遠使用一個與業務無關的自增字段
做爲主鍵
常常看到有帖子或博客討論主鍵選擇問題,有人建議使用業務無關的自增主鍵,有人以爲沒有必要,徹底可使用如學號或身份證號這種惟一字段做爲主鍵。不論支持哪一種論點,大多數論據都是業務層面
的。
若是從數據庫索引優化
角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意
上文討論過InnoDB的索引實現,InnoDB使用匯集索引,數據記錄自己被存於主索引(一顆B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小爲一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,所以每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB默認爲15/16),則開闢一個新的頁(節點)。
若是表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。以下圖所示:
這樣就會造成一個緊湊的索引結構,近似順序填滿
因爲每次插入時也不須要移動已有數據,所以效率很高,也不會增長不少開銷在維護索引上。
若是使用非自增主鍵(若是身份證號或學號等),因爲每次插入主鍵的值近似於隨機,所以每次新紀錄都要被插到現有索引頁得中間某個位置:
此時MySQL不得不爲了將新記錄插到合適位置而移動數據,甚至目標頁面可能已經被回寫到磁盤上而從緩存中清掉,此時又要從磁盤上讀回來,這增長了不少開銷,同時頻繁的移動、分頁操做形成了大量的碎片,獲得了不夠緊湊的索引結構,後續不得不經過OPTIMIZE TABLE來重建表並優化填充頁面。
所以,只要能夠,請儘可能在InnoDB上採用自增字段作主鍵。
與排序(ORDER BY)相關的索引優化及覆蓋索引(Covering index)的話題本文並未涉及,
全文索引等等本文也並未涉及
MySQL提供四種索引
MySql目前不支持函數索引,可是能對列的前面某一部分進行索引,例如標題title字段,能夠只取title的前10個字符索引,這樣的特性大大縮小了索引文件的大小,但前綴索引也有缺點,在排序order by和分組group by操做的時候沒法使用
create index idx_title on film(title(10));
索引 | MyISAM引擎 | InnoDB引擎 | Memory引擎 |
---|---|---|---|
B-Tree索引 | 支持 | 支持 | 支持 |
HASH索引 | 不支持 | 不支持 | 支持 |
R-Tree索引 | 支持 | 不支持 | 不支持 |
Full-text索引 | 支持 | 暫不支持 | 不支持 |
經常使用的索引就是B-tree索引和hash索引,資只有memory引擎支持HASH索引,hash索引適用於key-value查詢,經過hash索引比B-tree索引查詢更加迅速,可是hash索引不支持範圍查找例如<><==,>==等操做,若是使用memory引擎而且where不使用=進行 索引列,就不會用的索引。Memory只有在"="的條件下才會使用索引
Memory
,NDB
兩種引擎支持,Memory引擎默認支持哈希索引,若是多個hash值相同,出現哈希碰撞,那麼索引以鏈表方式存儲
可是,Memory引擎表只對可以適合機器的內存切實有限的數據集。
要使InnoDB或MyISAM支持哈希索引,能夠經過僞哈希索引來實現,叫自適應哈希索引。
主要經過增長一個字段,存儲hash值,將hash值創建索引,在插入和更新的時候,創建觸發器,自動添加計算後的hash到表裏。
假若有一個很是很是大的表,以下:
CREATE TABLE IF NOT EXISTS `User` ( `id` int(10) NOT NULL COMMENT '自增id', `name` varchar(128) NOT NULL DEFAULT '' COMMENT '用戶名', `email` varchar(128) NOT NULL DEFAULT '' COMMENT '用戶郵箱', `pass` varchar(64) NOT NULL DEFAULT '' COMMENT '用戶密碼', `last` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最後登陸時間', ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
這個時候,好比說,用戶登錄,我須要經過email檢索出用戶,經過explain獲得以下:
mysql> explain SELECT
id
FROMUser
WHERE email = ‘ooxx@gmail.com’ LIMIT 1;
+----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | User | ALL | NULL | NULL | NULL | NULL | 384742 | Using where | +----+-------------+-------+------+---------------+------+---------+------+--------+-------------+
發現 rows = 384742
也就是要在384742裏面進行比對email這個字段的字符串。
這條記錄運行的時間是:Query took 0.1744 seconds,數據庫的大小是40萬。
從上面能夠說明,若是直接在email上面創建索引,除了索引區間匹配,還要進行字符串匹配比對,email短還好,若是長的話這個查詢代價就比較大。
若是這個時候,在email上創建哈希索引,查詢以int查詢,性能就比字符串比對查詢快多了。
創建哈希索引,先選定哈希算法,這裏選用CRC32。
《高性能MySQL》說到的方法CRC32算法,創建SHA或MD5算法是划算的,自己位數都有可能比email段長了。
在表中添加hash值的字段:
mysql> ALTER TABLE
User
ADD COLUMN email_hash int unsigned NOT NULL DEFAULT 0;
接下來就是在UPDATE和INSERT的時候,自動更新 email_hash
字段,經過MySQL觸發器實現:
DELIMITER | CREATE TRIGGER user_hash_insert BEFORE INSERT ON `User` FOR EACH ROW BEGIN SET NEW.email_hash=crc32(NEW.email); END; | CREATE TRIGGER user_hash_update BEFORE UPDATE ON `User` FOR EACH ROW BEGIN SET NEW.email_hash=crc32(NEW.email); END; | DELIMITER ;
這樣的話,咱們的SELECT請求就會變成這樣:
mysql> SELECT
email_hash
FROMUser
WHERE email_hash = CRC32(「F2dgTSWRBXSZ1d3O@gmail.com」) AND
+----------------------------+------------+ | email | email_hash | +----------------------------+------------+ | F2dgTSWRBXSZ1d3O@gmail.com | 2765311122 | +----------------------------+------------+
在沒創建hash索引時候,請求時間是 0.2374 seconds,創建完索引後,請求時間直接變成 0.0003 seconds。
AND email = "F2dgTSWRBXSZ1d3O@gmail.com"
是爲了防止哈希碰撞致使數據不許確。
哈希索引也有幾個缺點: