索引的目的在於提高查詢效率,可以模擬字典,如果要查「mysql」這個單詞,我們肯定需要定位到m字母,然後從下往下找到y字母,再找到剩下的sql。如果沒有索引,那麼你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢?或者w開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?
除了詞典,生活中隨處可見索引的例子,如火車站的車次表、圖書的目錄等。它們的原理都是一樣的,通過不斷的縮小想要獲得資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是我們總是通過同一種查詢方式來鎖定資料。資料庫也是一樣,但顯然要複雜許多,因為不僅面臨著等值查詢,還有範圍查詢(>、
磁碟io與預讀
考慮到磁碟io是非常高昂的操作,計算機作業系統做了一些優化,當一次io時,不光把當前磁碟位址的資料,而是把相鄰的資料也都讀取到記憶體緩衝區內,因為區域性預讀性原理告訴我們,當計算機訪問乙個位址的資料的時候,與其相鄰的資料也會很快被訪問到。每一次io讀取的資料我們稱之為一頁(page)。具體一頁有多大資料跟作業系統有關,一般為4k或8k,也就是我們讀取一頁內的資料時候,實際上才發生了一次io,這個理論對於索引的資料結構設計非常有幫助。
索引的資料結構
前面講了生活中索引的例子,索引的基本原理,資料庫的複雜性,又講了作業系統的相關知識,目的就是讓大家了解,任何一種資料結構都不是憑空產生的,一定會有它的背景和使用場景,我們現在總結一下,我們需要這種資料結構能夠做些什麼,其實很簡單,那就是:每次查詢資料時把磁碟io次數控制在乙個很小的數量級,最好是常數數量級。那麼我們就想到如果乙個高度可控的多路搜尋樹是否能滿足需求呢?就這樣,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,***)的時候,b+數是按照從左到右的順序來建立搜尋樹的,比如當(張三,20,f)這樣的資料來檢索的時候,b+樹會優先比較name來確定下一步的所搜方向,如果name相同再依次比較age和***,最後得到檢索的資料;但當(20,f)這樣的沒有name的資料來的時候,b+樹就不知道下一步該查哪個節點,因為建立搜尋樹的時候name就是第乙個比較因子,必須要先根據name來搜尋才能知道下一步去**查詢。比如當(張三,f)這樣的資料來檢索時,b+樹可以用name來指定搜尋方向,但下乙個欄位age的缺失,所以只能把名字等於張三的資料都找到,然後再匹配性別是f的資料了, 這個是非常重要的性質,即索引的最左匹配特性。
關於mysql索引原理是比較枯燥的東西,大家只需要有乙個感性的認識,並不需要理解得非常透徹和深入。我們回頭來看看一開始我們說的慢查詢,了解完索引原理之後,大家是不是有什麼想法呢?先總結一下索引的幾大基本原則
建索引的幾大原則
1.最左字首匹配原則,非常重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調整。
2.=和in可以亂序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
3.盡量選擇區分度高的列作為索引,區分度的公式是count(distinct col)/count(*),表示欄位不重複的比例,比例越大我們掃瞄的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大資料面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃瞄10條記錄
4.索引列不能參與計算,保持列「乾淨」,比如from_unixtime(create_time) = 』2014-05-29』就不能使用到索引,原因很簡單,b+樹中存的都是資料表中的字段值,但進行檢索時,需要把所有元素都應用函式才能比較,顯然成本太大。所以語句應該寫成create_time = unix_timestamp(』2014-05-29』);
5.盡量的擴充套件索引,不要新建索引。比如表中已經有a的索引,現在要加(a,b)的索引,那麼只需要修改原來的索引即可
一條簡單sql的查詢優化
select count(*) from task where status=2 and operator_id=20839 and operate_time>1371169729 and operate_time<1371174603 and type=2;
根據最左匹配原則,該sql語句的索引應該是status、operator_id、type、operate_time的聯合索引;其中status、operator_id、type的順序可以顛倒;比如還有如下查詢
select * from task where status = 0 and type = 12 limit 10;
select count(*) from task where status = 0 ;
那麼索引建立成(status,type,operator_id,operate_time)就是非常正確的,因為可以覆蓋到所有情況。這個就是利用了索引的最左匹配的原則
查詢優化神器 – explain命令
關於explain命令相信大家並不陌生,具體用法和字段含義可以參考官網explain-output,這裡需要強調rows是核心指標,絕大部分rows小的語句執行一定很快(有例外,下面會講到)。所以優化語句基本上都是在優化rows。
慢查詢優化基本步驟
0.先執行看看是否真的很慢,注意設定sql_no_cache
1.where條件單錶查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單錶每個字段分別查詢,看哪個欄位的區分度最高
2.explain檢視執行計畫,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3.order by limit 形式的sql語句讓排序的表優先查
4.了解業務方使用場景
5.加索引時參照建索引的幾大原則
6.觀察結果,不符合預期繼續從0分析
MySQL索引原理與慢查詢優化
索引的目的在於提高查詢效率,可以模擬字典,如果要查 mysql 這個單詞,我們肯定需要定位到m字母,然後從下往下找到y字母,再找到剩下的sql。如果沒有索引,那麼你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢?或者w開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?...
MySQL索引原理及慢查詢優化
通過不斷的縮小想要獲得資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是我們總是通過同一種查詢方式來鎖定資料。資料庫索引就是通過演算法提高查詢效率。磁碟io與預讀 考慮到磁碟io是非常高昂的操作,計算機作業系統做了一些優化,當一次io時,不光把當前磁碟位址的資料,而是把相鄰的資...
MySQL索引原理及慢查詢優化
2.和in可以亂序,比如a 1 and b 2 and c 3 建立 a,b,c 索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式。3.盡量選擇區分度高的列作為索引,區分度的公式是count distinct col count 表示欄位不重複的比例,比例越大我們掃瞄的記錄數越...