1. 程式人生 > 實用技巧 >索引原理與慢查詢優化

索引原理與慢查詢優化

1、為何要用索引?
建立索引的目的就是為了優化查詢速度
注意一張表一旦建立了索引,就會降低寫速度

2、什麼是索引?
索引是mysql資料庫的一種資料結構,在mysql裡稱之為key

你是否對索引存在誤解?

索引是應用程式設計和開發的一個重要方面。若索引太多,應用程式的效能可能會受到影響。而索引太少,對查詢效能又會產生影響,要找到一個平衡點,這對應用程式的效能至關重要。一些開發人員總是在事後才想起新增索引----我一直認為,這源於一種錯誤的開發模式。如果知道資料的使用,從一開始就應該在需要處新增索引。開發人員往往對資料庫的使用停留在應用的層面,比如編寫SQL語句、儲存過程之類,他們甚至可能不知道索引的存在,或認為事後讓相關DBA加上即可。DBA往往不夠了解業務的資料流,而新增索引需要通過監控大量的SQL語句進而從中找到問題,這個步驟所需的時間肯定是遠大於初始新增索引所需的時間,並且可能會遺漏一部分的索引。當然索引也並不是越多越好,我曾經遇到過這樣一個問題:某臺MySQL伺服器iostat顯示磁碟使用率一直處於100%,經過分析後發現是由於開發人員添加了太多的索引,在刪除一些不必要的索引之後,磁碟使用率馬上下降為20%。可見索引的新增也是非常有技術含量的。

二、索引原理:

索引的目的在於提高查詢效率,與我們查閱圖書所用的目錄是一個道理:先定位到章,然後定位到該章下的一個小節,然後找到頁數。相似的例子還有:查字典,查火車車次,飛機航班等

本質都是:通過不斷地縮小想要獲取資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,有了這種索引機制,我們可以總是用同一種查詢方式來鎖定資料。

資料庫也是一樣,但顯然要複雜的多,因為不僅面臨著等值查詢,還有範圍查詢(>、<、between、in)、模糊查詢(like)、並集查詢(or)等等。資料庫應該選擇怎麼樣的方式來應對所有的問題呢?我們回想字典的例子,能不能把資料分成段,然後分段查詢呢?最簡單的如果1000條資料,1到100分成第一段,101到200分成第二段,201到300分成第三段......這樣查第250條資料,只要找第三段就可以了,一下子去除了90%的無效資料。但如果是1千萬的記錄呢,分成幾段比較好?稍有演算法基礎的同學會想到搜尋樹,其平均複雜度是lgN,具有不錯的查詢效能。但這裡我們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操作成本來考慮的。而資料庫實現比較複雜,一方面資料是儲存在磁碟上的,另外一方面為了提高效能,每次又可以把部分資料讀入記憶體來計算,因為我們知道訪問磁碟的成本大概是訪問記憶體的十萬倍左右,所以簡單的搜尋樹難以滿足複雜的應用場景。

二 磁碟IO與預讀

前面提到了訪問磁碟,那麼這裡先簡單介紹一下磁碟IO和預讀,磁碟讀取資料靠的是機械運動,每次讀取資料花費的時間可以分為尋道時間、旋轉延遲、傳輸時間三個部分,尋道時間指的是磁臂移動到指定磁軌所需要的時間,主流磁碟一般在5ms以下;旋轉延遲就是我們經常聽說的磁碟轉速,比如一個磁碟7200轉,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms;傳輸時間指的是從磁碟讀出或將資料寫入磁碟的時間,一般在零點幾毫秒,相對於前兩個時間可以忽略不計。那麼訪問一次磁碟的時間,即一次磁碟IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行約450萬條指令,資料庫動輒十萬百萬乃至千萬級資料,每次9毫秒的時間,顯然是個災難。下圖是計算機硬體延遲的對比圖,供大家參考:

考慮到磁碟IO是非常高昂的操作,計算機作業系統做了一些優化,當一次IO時,不光把當前磁碟地址的資料,而是把相鄰的資料也都讀取到記憶體緩衝區內,因為區域性預讀性原理告訴我們,當計算機訪問一個地址的資料的時候,與其相鄰的資料也會很快被訪問到。每一次IO讀取的資料我們稱之為一頁(page)。具體一頁有多大資料跟作業系統有關,一般為4k或8k,也就是我們讀取一頁內的資料時候,實際上才發生了一次IO,這個理論對於索引的資料結構設計非常有幫助。

三、索引的資料結構

前面講了索引的基本原理,資料庫的複雜性,又講了作業系統的相關知識,目的就是讓大家瞭解,任何一種資料結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種資料結構能夠做些什麼,其實很簡單,那就是:每次查詢資料時把磁碟IO次數控制在一個很小的數量級,最好是常數數量級。那麼我們就想到如果一個高度可控的多路搜尋樹是否能滿足需求呢?就這樣,b+樹應運而生(B+樹是通過二叉查詢樹,再由平衡二叉樹,B樹演化而來)。

如上圖,是一顆b+樹,關於b+樹的定義可以參見B+樹,這裡只說一些重點,淺藍色的塊我們稱之為一個磁碟塊,可以看到每個磁碟塊包含幾個資料項(深藍色所示)和指標(黃色所示),如磁碟塊1包含資料項17和35,包含指標P1、P2、P3,P1表示小於17的磁碟塊,P2表示在17和35之間的磁碟塊,P3表示大於35的磁碟塊。真實的資料存在於葉子節點即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非葉子節點只不儲存真實的資料,只儲存指引搜尋方向的資料項,如17、35並不真實存在於資料表中。

###b+樹的查詢過程
如圖所示,如果要查詢資料項29,那麼首先會把磁碟塊1由磁碟載入到記憶體,此時發生一次IO,在記憶體中用二分查詢確定29在17和35之間,鎖定磁碟塊1的P2指標,記憶體時間因為非常短(相比磁碟的IO)可以忽略不計,通過磁碟塊1的P2指標的磁碟地址把磁碟塊3由磁碟載入到記憶體,發生第二次IO,29在26和30之間,鎖定磁碟塊3的P2指標,通過指標載入磁碟塊8到記憶體,發生第三次IO,同時記憶體中做二分查詢找到29,結束查詢,總計三次IO。真實的情況是,3層的b+樹可以表示上百萬的資料,如果上百萬的資料查詢只需要三次IO,效能提高將是巨大的,如果沒有索引,每個資料項都要發生一次IO,那麼總共需要百萬次的IO,顯然成本非常非常高。

###b+樹性質
1.索引欄位要儘量的小:通過上面的分析,我們知道IO次數取決於b+數的高度h,假設當前資料表的資料為N,每個磁碟塊的資料項的數量是m,則有h=㏒(m+1)N,當資料量N一定的情況下,m越大,h越小;而m = 磁碟塊的大小 / 資料項的大小,磁碟塊的大小也就是一個數據頁的大小,是固定的,如果資料項佔的空間越小,資料項的數量越多,樹的高度越低。這就是為什麼每個資料項,即索引欄位要儘量的小,比如int佔4位元組,要比bigint8位元組少一半。這也是為什麼b+樹要求把真實的資料放到葉子節點而不是內層節點,一旦放到內層節點,磁碟塊的資料項會大幅度下降,導致樹增高。當資料項等於1時將會退化成線性表。
2.索引的最左匹配特性:當b+樹的資料項是複合的資料結構,比如(name,age,sex)的時候,b+數是按照從左到右的順序來建立搜尋樹的,比如當(張三,20,F)這樣的資料來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和sex,最後得到檢索的資料;但當(20,F)這樣的沒有name的資料來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜尋樹的時候name就是第一個比較因子,必須要先根據name來搜尋才能知道下一步去哪裡查詢。比如當(張三,F)這樣的資料來檢索時,b+樹可以用name來指定搜尋方向,但下一個欄位age的缺失,所以只能把名字等於張三的資料都找到,然後再匹配性別是F的資料了, 這個是非常重要的性質,即索引的最左匹配特性。

四、聚集索引與輔助索引

1、聚集索引

1 InnoDB儲存引擎表示索引組織表,即表中資料按照主鍵順序存放。而聚集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即為整張表的行記錄資料,也將聚集索引的葉子結點稱為資料頁。聚集索引的這個特性決定了索引組織表中資料也是索引的一部分。同B+樹資料結構一樣,每個資料頁都通過一個雙向連結串列來進行連結。
2     
3 如果未定義主鍵,MySQL取第一個唯一索引(unique)而且只含非空列(NOT NULL)作為主鍵,InnoDB使用它作為聚簇索引。
4     
5 如果沒有這樣的列,InnoDB就自己產生一個這樣的ID值,它有六個位元組,而且是隱藏的,使其作為聚簇索引。
6 
7 由於實際的資料頁只能按照一棵B+樹進行排序,因此每張表只能擁有一個聚集索引。在多少情況下,查詢優化器傾向於採用聚集索引。因為聚集索引能夠在B+樹索引的葉子節點上直接找到資料。此外由於定義了資料的邏輯順序,聚集索引能夠特別快地訪問針對範圍值得查詢。

2、輔助索引

除了聚集索引外其他索引都是輔助索引(Secondary Index,也稱為非聚集索引),與聚集索引的區別是:輔助索引的葉子節點不包含行記錄的全部資料。

葉子節點除了包含鍵值以外(還有指標且指向聚集索引),每個葉子節點中的索引行中還包含一個書籤(bookmark)。該書籤用來告訴InnoDB儲存引擎去哪裡可以找到與索引相對應的行資料。

五、Mysql索引管理

1 #1. 索引的功能就是加速查詢
2 #2. mysql中的primary key,unique,聯合唯一也都是索引,這些索引除了加速查詢以外,還有約束的功能

1、Mysql常用索引

 1 普通索引index:加速查詢
 2 
 3 唯一索引:
 4     -主鍵索引PRIMARY KEY:加速查詢+約束(不為空、不能重複)
 5     -唯一索引UNIQUE:加速查詢+約束(不能重複)
 6 
 7 聯合索引:
 8     -PRIMARY KEY(id,name):聯合主鍵索引
 9     -UNIQUE(id,name):聯合唯一索引
10     -INDEX(id,name):聯合普通索引

六、索引的兩大型別'hash'與'btree'

1 #我們可以在建立上述索引的時候,為其指定索引型別,分兩類
2 hash型別的索引:查詢單條快,範圍查詢慢
3 btree型別的索引:b+樹,層數越多,資料量指數級增長(我們就用它,因為innodb預設支援它)

七、建立和刪除索引的語法

 1 # 方式一:
 2 create table t5(
 3     id int primary key auto_increment,
 4     name varchar(4),
 5     email varchar(10),
 6     unique key uni_name(name),
 7     index xxx(email)
 8 );
 9 
10 # 方式二:
11 alter table t5 add index qqq(email);
12 
13 # 方式三:
14 create index yyy on t5(name);
15 
16 
17 
18 alter table t5 drop primary key;
19 alter table t5 drop index qqq;

八、測試索引

1、在表中已經存在大量資料的前提下,為某個欄位段建立索引,建立速度會很慢

2、在索引建立完畢後,以該欄位為查詢條件時,查詢速度提升明顯