MySQL之索引原理與慢查詢優化

一 介紹

  爲什麼要有索引?mysql

    通常的應用系統,讀寫比例在10:1左右,並且插入操做和通常的更新操做不多出現性能問題,在生產環境中,咱們遇到最多的,也是最容易出問題的,仍是一些複雜的查詢操做,所以對查詢語句的優化顯然是重中之重。提及加速查詢,就不得不提到索引了。ios

  什麼是索引?算法

    索引在MySQL中也叫作「鍵」或者"key"(primary key,unique key,還有一個index key),是存儲引擎用於快速找到記錄的一種數據結構。索引對於良好的性能很是關鍵,尤爲是當表中的數據量愈來愈大時,索引對於性能的影響愈發重要,減小io次數,加速查詢。(其中primary key和unique key,除了有加速查詢的效果以外,還有約束的效果,primary key 不爲空且惟一,unique key 惟一,而index key只有加速查詢的效果,沒有約束效果)
    索引優化應該是對查詢性能優化最有效的手段了。索引可以輕易將查詢性能提升好幾個數量級。
    索引至關於字典的音序表,若是要查某個字,若是不使用音序表,則須要從幾百頁中逐頁去查。sql

    強調:一旦爲表建立了索引,之後的查詢最好先查索引,再根據索引定位的結果去找數據數據庫

 

   

 
                      30

        10                          40

   5         15               35          66

1    6    11   19          21   39     55    100
 

  你是否對索引存在誤解?vim

    索引是應用程序設計和開發的一個重要方面。若索引太多,應用程序的性能可能會受到影響。而索引太少,對查詢性能又會產生影響,要找到一個平衡點,這對應用程序的性能相當重要。一些開發人員老是在過後纔想起添加索引----我一直認爲,這源於一種錯誤的開發模式。若是知道數據的使用,從一開始就應該在須要處添加索引。開發人員每每對數據庫的使用停留在應用的層面,好比編寫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轉/min,表示每分鐘能轉7200次,也就是說1秒鐘能轉120次,旋轉延遲就是1/120/2 = 4.17ms,也就是半圈的時間(這裏有兩個時間:平均尋道時間,受限於目前的物理水平,大概是5ms的時間,找到磁道了,還須要找到你數據存在的那個點,尋點時間,這尋點時間的一個平均值就是半圈的時間,這個半圈時間叫作平均延遲時間,那麼平均延遲時間加上平均尋道時間就是你找到一個數據所消耗的平均時間,大概9ms,其實機械硬盤慢主要是慢在這兩個時間上了,當找到數據而後把數據拷貝到內存的時間是很是短暫的,和光速差很少了);傳輸時間指的是從磁盤讀出或將數據寫入磁盤的時間,通常在零點幾毫秒,相對於前兩個時間能夠忽略不計。那麼訪問一次磁盤的時間,即一次磁盤IO的時間約等於5+4.17 = 9ms左右,聽起來還挺不錯的,但要知道一臺500 -MIPS(Million Instructions Per Second)的機器每秒能夠執行5億條指令,由於指令依靠的是電的性質,換句話說執行一次IO的消耗的時間段下cpu能夠執行約450萬條指令,數據庫動輒十萬百萬乃至千萬級數據,每次9毫秒的時間,顯然是個災難,因此咱們要想辦法下降IO次數。下圖是計算機硬件延遲的對比圖,供你們參考:

     

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

三 索引的數據結構

  前面講了索引的基本原理,數據庫的複雜性,又講了操做系統的相關知識,目的就是讓你們瞭解,如今咱們來看看索引怎麼作到減小IO,加速查詢的。任何一種數據結構都不是憑空產生的,必定會有它的背景和使用場景,咱們如今總結一下,咱們須要這種數據結構可以作些什麼,其實很簡單,那就是:每次查找數據時把磁盤IO次數控制在一個很小的數量級,最好是常數數量級。那麼咱們就想到若是一個高度可控的多路搜索樹是否能知足需求呢?就這樣,b+樹應運而生(B+樹是經過二叉查找樹,再由平衡二叉樹,B樹演化而來,等到後面講算法的時候再將,如今這個階段,你大概瞭解一下就好了,別深究~~)。

    

  如上圖,是一顆b+樹,最上層是樹根,中間的是樹枝,最下面是葉子節點,關於b+樹的定義能夠參見B+樹,這裏只說一些重點,淺藍色的塊咱們稱之爲一個磁盤塊或者叫作一個block塊,這是操做系統一次IO往內存中讀的內容,一個塊對應四個扇區,能夠看到每一個磁盤塊包含幾個數據項(深藍色所示,一個磁盤塊裏面包含多少數據,一個深藍色的塊表示一個數據,其實不是數據,後面有解釋)和指針(黃色所示,看最上面一個,p1表示比上面深藍色的那個17小的數據的位置在哪,看它指針指向的左邊那個塊,裏面的數據都比17小,p2指向的是比17大比35小的磁盤塊),如磁盤塊1包含數據項17和35,包含指針P一、P二、P3,P1表示小於17的磁盤塊,P2表示在17和35之間的磁盤塊,P3表示大於35的磁盤塊。真實的數據存在於葉子節點即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非葉子節點只不存儲真實的數據,只存儲指引搜索方向的數據項,如1七、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或者說層級,這個高度或者層級就是你每次查詢數據的IO次數,假設當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則有h=㏒(m+1)N,當數據量N必定的狀況下,m越大,h越小;而m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個數據頁的大小,是固定的,若是數據項佔的空間越小,數據項的數量越多,樹的高度越低。這就是爲何每一個數據項,即索引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。這也是爲何b+樹要求把真實的數據放到葉子節點而不是內層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低,致使樹增高。當數據項等於1時將會退化成線性表。

      好比:你每一個葉子節點只存兩個數據的狀況下,你若是想多加兩個數據,你怎麼辦

        

        因此咱們須要將樹建的越低越好,由於每一個磁盤塊的大小是必定的,那麼意味着咱們單個數據庫裏面的單個數據的大小越大越好仍是越小越好,你想啊,你如今葉子節點的磁盤塊,兩個數據就沾滿了,你數據要是更大的話,你這一個磁盤塊就只能放一個數據了親,這樣隨着你數據量的增大,你的樹就越高啊,咱們應該想辦法讓樹的層數低下來,效率才高啊,因此咱們應該讓每一個數據的大小盡量的小,那就意味着,你每一個磁盤塊存的數據就越多,你樹的層級就越少啊,樹就越低啊,對不對。而且數據的數量越大,你須要的磁盤塊越多,磁盤塊越多,你須要的樹的層級就越高,因此咱們應該儘量的用更少的磁盤塊來裝更多的數據項,這樣樹的高度才能降下來,怎麼才能裝更多的數據項啊,固然是你的數據項越小,你的磁盤塊盛放的數據量就越多了,因此若是一張表中有不少的字段,咱們應該用什麼字段來創建索引啊,若是你有id字段、name字段、描述信息字段等等的,你應該用哪一個來創建索引啊,固然是id字段了,你想一想對不對,由於id是個數字,佔用空間最少啊。


    2.索引的最左匹配特性:簡單來講就是你的數據來了之後,從數據塊的左邊開始匹配,在匹配右邊的,知道這句話就行啦~~~~,咱們繼續學下面的內容。當b+樹的數據項是複合的數據結構,好比(name,age,sex)的時候,b+數是按照從左到右的順序來創建搜索樹的,好比當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性別是F的數據了, 這個是很是重要的性質,即索引的最左匹配特性。

四 彙集索引與輔助索引

  彙集索引是什麼呢,其實就是咱們說的那個主鍵,以前咱們說Innodb存儲引擎的表,必須有一個主鍵,還記得爲何嗎,咱們說過的...不記得了吧,看下面

    還記得MyISAM存儲引擎在建立表的時候會在硬盤上生成哪些文件嗎,是否是有三個.frm\.MYD\.MYI結尾的三個文件,frm結尾的是表結構,MYD結尾的是數據文件,MYI結尾的就是索引文件,也就是說索引也是存在硬盤上的,那InnoDB引擎呢,建立一個表,在硬盤上會生成.frm\.idb結尾的兩個文件,那索引的呢,難道InnoDB就用不了索引嗎?怎麼可能?以前我們有沒有創建過索引啊,primary key、unique key是否是都叫作索引啊,可是索引那個文件去哪了呢,索引是不可能在表結構.frm(存什麼字段什麼類型這些東西)的文件中,那就只剩下.idb結尾的數據文件了,索引就在這裏面,InnoDB引擎的表,它的索引和數據都在同一個文件裏面,因此我一直強調,使用InnoDB存儲引擎的時候,每建一個表,就須要給一個主鍵,是由於這個主鍵是InnoDB存儲引擎的.idb文件來組織存儲數據的依據或者說方式,也就是說InnoDB存儲引擎在存儲數據的時候默認就按照索引的那種樹形結構來幫你存。這種索引,咱們就稱爲彙集索引,也就是在彙集數據組織數據的時候,就用這種索引。InnoDB這麼作就是爲了加速查詢效率,由於你常常會遇到基於主鍵來查詢數據的狀況,而且一般咱們把id字段做爲主鍵,第一點是由於id佔用的數據空間不大,第二點是你常常會用到id來查數據。若是你的表有兩個字段,一個id一個name,id爲主鍵,當你查詢的時候若是where後面的條件是name=多少多少,那麼你就沒有用到主鍵給你帶來的加速查詢的效果(須要主鍵以外的輔助索引),若是你用where id=多少多少,就會按照咱們剛纔上面說的哪一種樹形結構來給你找尋數據了(固然不只僅有這種樹形結構的數據結構類型),可以快速的幫你定位到數據塊。這種彙集索引的特色是它會以id字段做爲依據,去創建樹形結構,可是葉子節點存的是你表中的一條完整記錄,一條完整的數據。記住這一點昂,一會將輔助索引的時候,和這個內容有關係,會講到一個回表的概念。

  在數據庫中,B+樹的高度通常都在2~4層,這也就是說查找某一個鍵值的行記錄時最多隻須要2到4次IO,這倒不錯。由於當前通常的機械硬盤每秒至少能夠作100次IO,2~4次的IO意味着查詢時間只須要0.02~0.04秒。

  數據庫中的B+樹索引能夠分爲彙集索引(clustered index)和輔助索引(secondary index),

  彙集索引與輔助索引相同的是:無論是彙集索引仍是輔助索引,其內部都是B+樹的形式,即高度是平衡的,葉子結點存放着全部的數據。

  彙集索引與輔助索引不一樣的是:葉子結點存放的是不是一整行的信息

  一、彙集索引

 
#InnoDB存儲引擎表示索引組織表,即表中數據按照主鍵順序存放。而彙集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子結點存放的即爲整張表的行記錄數據,也將彙集索引的葉子結點稱爲數據頁。彙集索引的這個特性決定了索引組織表中數據也是索引的一部分。同B+樹數據結構同樣,每一個數據頁都經過一個雙向鏈表來進行連接。
    
#若是未定義主鍵,MySQL取第一個惟一索引(unique)並且只含非空列(NOT NULL)做爲主鍵,InnoDB使用它做爲聚簇索引。
    
#若是沒有這樣的列,InnoDB就本身產生一個這樣的ID值,它有六個字節,並且是隱藏的,使其做爲聚簇索引。

#因爲實際的數據頁只能按照一棵B+樹進行排序,所以每張表只能擁有一個彙集索引。在多少狀況下,查詢優化器傾向於採用彙集索引。由於彙集索引可以在B+樹索引的葉子節點上直接找到數據。此外因爲定義了數據的邏輯順序,彙集索引可以特別快地訪問針對範圍值得查詢。
 

    

    彙集索引的好處之一:它對主鍵的排序查找和範圍查找速度很是快,葉子節點的數據就是用戶所要查詢的數據。如用戶須要查找一張表,查詢最後的10位用戶信息,因爲B+樹索引是雙向鏈表,因此用戶能夠快速找到最後一個數據頁,並取出10條記錄  

 
 
#參照第六小結測試索引的準備階段來建立出表s1
mysql> desc s1; #最開始沒有主鍵
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> explain select * from s1 order by id desc limit 10; #Using filesort,須要二次排序
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2633472 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+----------------+
1 row in set, 1 warning (0.11 sec)

mysql> alter table s1 add primary key(id); #添加主鍵
Query OK, 0 rows affected (13.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 order by id desc limit 10; #基於主鍵的彙集索引在建立完畢後就已經完成了排序,無需二次排序
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
|  1 | SIMPLE      | s1    | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.04 sec)
 
 

    

    彙集索引的好處之二:範圍查詢(range query),即若是要查找主鍵某一範圍內的數據,經過葉子節點的上層中間節點就能夠獲得頁的範圍,以後直接讀取數據頁便可

 
 
mysql> alter table s1 drop primary key;
Query OK, 2699998 rows affected (24.23 sec)
Records: 2699998  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | NO   |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(6)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.12 sec)

mysql> explain select * from s1 where id > 1 and id < 1000000; #沒有彙集索引,預估須要檢索的rows數以下,explain就是預估一下你的sql的執行效率
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2690100 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> alter table s1 add primary key(id);
Query OK, 0 rows affected (16.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain select * from s1 where id > 1 and id < 1000000; #有彙集索引,預估須要檢索的rows數以下
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | s1    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 1343355 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.09 sec)
 

  二、輔助索引

    就是咱們在查詢的時候,where後面須要寫id以外的其餘字段名稱來進行查詢,好比說是where name=xx,無法用到主鍵索引的效率,怎麼辦,就須要咱們添加輔助索引了,給name添加一個輔助索引。

    表中除了彙集索引外其餘索引都是輔助索引(Secondary Index,也稱爲非彙集索引)(unique key啊、index key啊),與彙集索引的區別是:輔助索引的葉子節點不包含行記錄的所有數據。

    葉子節點存放的是對應的那條數據的主鍵字段的值,除了包含鍵值之外,每一個葉子節點中的索引行中還包含一個書籤(bookmark),其實這個書籤你能夠理解爲是一個{'name字段',name的值,主鍵id值}的這麼一個數據。該書籤用來告訴InnoDB存儲引擎去哪裏能夠找到與索引相對應的行數據。若是咱們select 後面要的是name,咱們直接就能夠在輔助索引的葉子節點找到對應的name值,好比:select name from tb1 where name='xx';這個xx值你直接就在輔助索引的葉子節點就能找到,這種咱們也能夠稱爲覆蓋索引。若是你select後面的字段不是name,例如:select age from tb1 where name='xx';也就是說,我經過輔助索引的葉子節點不能直接拿到age的值,須要經過輔助索引的葉子節點中保存的主鍵id的值再去經過彙集索引來找到完整的一條記錄,而後從這個記錄裏面拿出age的值,這種操做有時候也成爲回表操做,就是從頭再回去查一遍,這種的查詢效率也很高,可是比覆蓋索引低一些,再說一下昂,再輔助索引的葉子節點就能找到你想找的數據可稱爲覆蓋索引。再看看下面的解釋:

    因爲InnoDB存儲引擎是索引組織表,所以InnoDB存儲引擎的輔助索引的書籤就是相應行數據的彙集索引鍵或者稱爲主鍵的值。以下圖

      

    輔助索引的存在並不影響數據在彙集索引中的組織,所以每張表上能夠有多個輔助索引,但只能有一個彙集索引。當經過輔助索引來尋找數據時,InnoDB存儲引擎會遍歷輔助索引並經過葉子級別的指針得到只想主鍵索引的主鍵,而後再經過主鍵索引來找到一個完整的行記錄,這種查找的效率也是很是高。

    舉例來講,若是在一棵高度爲3的輔助索引樹種查找數據,那須要對這個輔助索引樹遍歷3次找到指定主鍵,若是彙集索引樹的高度一樣爲3,那麼還須要對彙集索引樹進行3次查找,最終找到一個完整的行數據所在的頁,所以一共須要6次邏輯IO訪問才能獲得最終的一個數據頁。

    

  概念基本就說完了,下面咱們來點實際操做吧,看下面的內容~~~~

五 MySQL索引管理

  一 功能

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

 

  二 MySQL經常使用的索引

 
普通索引INDEX:加速查找

惟一索引:
    -主鍵索引PRIMARY KEY:加速查找+約束(不爲空、不能重複)
    -惟一索引UNIQUE:加速查找+約束(不能重複)

聯合索引:
    -PRIMARY KEY(id,name):聯合主鍵索引
    -UNIQUE(id,name):聯合惟一索引
    -INDEX(id,name):聯合普通索引
 

     索引操做:

 
添加主鍵索引:
建立的時候添加:  添加索引的時候要注意,給字段裏面數據大小比較小的字段添加,給字段裏面的數據區分度高的字段添加.
彙集索引的添加方式
建立的是添加
Create table t1(
Id int primary key,
)
Create table t1(
Id int,
Primary key(id)
)

表建立完了以後添加
Alter table 表名 add primary key(id)
刪除主鍵索引:
Alter table 表名 drop primary key;


惟一索引:
Create table t1(
Id int unique,
)

Create table t1(
Id int,
Unique key uni_name (id)
)

表建立好以後添加惟一索引:
alter table s1 add unique key  u_name(id);
刪除:
Alter table s1 drop index u_name;

普通索引:
建立:
Create table t1(
Id int,
Index index_name(id)
)
Alter table s1 add index index_name(id);
Create index index_name on s1(id);

刪除:
Alter table s1 drop index u_name;
DROP INDEX 索引名 ON 表名字;
 

 

    各類索引的應用場景:

 
舉個例子來講,好比你在爲某商場作一個會員卡的系統。

這個系統有一個會員表
有下列字段:
會員編號 INT
會員姓名 VARCHAR(10)
會員身份證號碼 VARCHAR(18)
會員電話 VARCHAR(10)
會員住址 VARCHAR(50)
會員備註信息 TEXT

那麼這個 會員編號,做爲主鍵,使用 PRIMARY
會員姓名 若是要建索引的話,那麼就是普通的 INDEX
會員身份證號碼 若是要建索引的話,那麼能夠選擇 UNIQUE (惟一的,不容許重複)

#除此以外還有全文索引,即FULLTEXT
會員備註信息 , 若是須要建索引的話,能夠選擇全文搜索。
用於搜索很長一篇文章的時候,效果最好。
用在比較短的文本,若是就一兩行字的,普通的 INDEX 也能夠。
但其實對於全文搜索,咱們並不會使用MySQL自帶的該索引,而是會選擇第三方軟件如Sphinx,專門來作全文搜索。

#其餘的如空間索引SPATIAL,瞭解便可,幾乎不用
 

 

  三 索引的兩大類型hash與btree

 

 
#咱們能夠在建立上述索引的時候,爲其指定索引類型,分兩類
hash類型的索引:查詢單條快,範圍查詢慢
btree類型的索引:b+樹,層數越多,數據量指數級增加(咱們就用它,由於innodb默認支持它)

#不一樣的存儲引擎支持的索引類型也不同
InnoDB 支持事務,支持行級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事務,支持表級別鎖定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事務,支持表級別鎖定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事務,支持行級別鎖定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事務,支持表級別鎖定,不支持 B-tree、Hash、Full-text 等索引;
 

 

  四 建立/刪除索引的語法

 
#方法一:建立表時
      CREATE TABLE 表名 (
                字段名1  數據類型 [完整性約束條件…],
                字段名2  數據類型 [完整性約束條件…],
                [UNIQUE | FULLTEXT | SPATIAL ]   INDEX | KEY
                [索引名]  (字段名[(長度)]  [ASC |DESC]) 
                );


#方法二:CREATE在已存在的表上建立索引
        CREATE  [UNIQUE | FULLTEXT | SPATIAL ]  INDEX  索引名 
                     ON 表名 (字段名[(長度)]  [ASC |DESC]) ;


#方法三:ALTER TABLE在已存在的表上建立索引
        ALTER TABLE 表名 ADD  [UNIQUE | FULLTEXT | SPATIAL ] INDEX
                             索引名 (字段名[(長度)]  [ASC |DESC]) ;
                             
#刪除索引:DROP INDEX 索引名 ON 表名字;
 

 

    看下面的示範:

 
#方式一
create table t1(
    id int,
    name char,
    age int,
    sex enum('male','female'),
    unique key uni_id(id),
    index ix_name(name) #index沒有key
);


#方式二
create index ix_age on t1(age);

#方式三
alter table t1 add index ix_sex(sex);

#查看
mysql> show create table t1;
| t1    | CREATE TABLE `t1` (
  `id` int(11) DEFAULT NULL,
  `name` char(1) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `sex` enum('male','female') DEFAULT NULL,
  UNIQUE KEY `uni_id` (`id`),
  KEY `ix_name` (`name`),
  KEY `ix_age` (`age`),
  KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
 

 

六 測試索引

  一 準備

 
#1. 準備表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 建立存儲過程,實現批量插入記錄
delimiter $$ #聲明存儲過程的結束符號爲$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'egon','male',concat('egon',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$結束
delimiter ; #從新聲明分號爲結束符號

#3. 查看存儲過程
show create procedure auto_insert1\G 

#4. 調用存儲過程
call auto_insert1();
 

 

  二 在沒有索引的前提下測試查詢速度

#無索引:mysql根本就不知道究竟是否存在id等於333333333的記錄,也不知道存在幾條id=333333333的記錄,只能把數據表從頭至尾掃描一遍,此時有多少個磁盤塊就須要進行多少IO操做,因此查詢速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)

 

  三 在表中已經存在大量數據的前提下,爲某個字段段創建索引,創建速度會很慢

    

    或者用alter table s1 add primary key(id);加主鍵,建索引很慢的。

  四 在索引創建完畢後,以該字段爲查詢條件時,查詢速度提高明顯

    

    PS:

    1. mysql先去索引表裏根據b+樹的搜索原理很快搜索到id等於333333333的記錄不存在,IO大大下降,於是速度明顯提高

    2. 咱們能夠去mysql的data目錄下找到該表,能夠看到佔用的硬盤空間多了

    3. 須要注意,以下圖

    

  五 總結

 
#1. 必定是爲搜索條件的字段建立索引,好比select * from s1 where id = 333;就須要爲id加上索引

#2. 在表中已經有大量數據的狀況下,建索引會很慢,且佔用硬盤空間,建完後查詢速度加快
好比create index idx on s1(id);會掃描表中全部的數據,而後以id爲數據項,建立索引結構,存放於硬盤的表中。
建完之後,再查詢就會很快了。

#3. 須要注意的是:innodb表的索引會存放於s1.ibd文件中,而myisam表的索引則會有單獨的索引文件table1.MYI

MySAM索引文件和數據文件是分離的,索引文件僅保存數據記錄的地址。而在innodb中,表數據文件自己就是按照B+Tree(BTree即Balance True)組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,所以innodb表數據文件自己就是主索引。
由於inndob的數據文件要按照主鍵彙集,因此innodb要求表必需要有主鍵(Myisam能夠沒有),若是沒有顯式定義,則mysql系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則mysql會自動爲innodb表生成一個隱含字段做爲主鍵,這字段的長度爲6個字節,類型爲長整型.
 

   你想一下,是否是所有加索引就必定好的呢,首先,咱們加上索引,查詢時快了,可是寫入的時候就慢了,還記得嗎,每次插入新的記錄,你的整個索引結構都會跟着改,因此若是你亂加索引,你會發現,即使是你的網站沒有幾我的在註冊,或者說沒有幾個寫入數據的操做,你的磁盤IO會居高不下,磁盤在瘋狂的轉,由於你每插入一條數據,咱們的索引都須要從新建,重建的索引要寫入硬盤裏面的,還記得咱們給那三百萬條數據建索引的時候的速度嗎?每次都要把全部的數據取出來,作好一個數據結構,而後再寫回硬盤,也就是要經歷不少的IO才能實現這個事兒,因此亂加索引的一個弊端就是,你不多的寫入都會致使你的磁盤IO很是的高,致使效率不好,因此咱們要學一下怎麼正確的加索引。

七 正確使用索引

  一 索引未命中

    並非說咱們建立了索引就必定會加快查詢速度,若想利用索引達到預想的提升查詢速度的效果,咱們在添加索引時,必須遵循如下問題

    1 範圍問題,或者說條件不明確,條件中出現這些符號或關鍵字:>、>=、<、<=、!= 、between...and...、like、

    大於號、小於號

    

    若是你寫where id >1 and id <1000000;你會發現,隨着你範圍的增大,速度會愈來愈慢,會成倍的體現出來。

    不等於!=

    

    between ...and...

    

    like  #測like的時候,你能夠先把id的主鍵索引去掉,而後測一下like,而後加上index key,再測一下。就知道你忘了,看語句:create index email_index  on s1(email) ,email_index是索引名、on、 s1是表名(字段名),你會發現建索引的時間也是很慢的,desc s1;查看一下索引是否是建立成功了,看那個key字段是否是有個mul,有這個說明建立成功了。

    like=後面若是沒有那些特殊字符,通配符之類的,就跟等因而一個效果,精確匹配。

    

    #使用like的時候,通配符寫在最前面,也是須要全匹配一遍,而後在比較字符串的第二個字符,最左匹配的規則,還記得嗎。

    2 儘可能選擇區分度高的列做爲索引,區分度的公式是count(distinct col)/count(*),表示字段不重複的比例,比例越大咱們掃描的記錄數越少,惟一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就是0,那可能有人會問,這個比例有什麼經驗值嗎?使用場景不一樣,這個值也很難肯定,通常須要join的字段咱們都要求是0.1以上,即平均1條掃描10條記錄

 

 
#先把表中的索引都刪除,讓咱們專心研究區分度的問題
mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  | MUL | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  | MUL | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> drop index a on s1;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index d on s1;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id     | int(11)     | YES  |     | NULL    |       |
| name   | varchar(20) | YES  |     | NULL    |       |
| gender | char(5)     | YES  |     | NULL    |       |
| email  | varchar(50) | YES  |     | NULL    |       |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
 

 

    

 
咱們編寫存儲過程爲表s1批量添加記錄,name字段的值均爲egon,也就是說name這個字段的區分度很低(gender字段也是同樣的,咱們稍後再搭理它)

回憶b+樹的結構,查詢的速度與樹的高度成反比,要想將樹的高低控制的很低,須要保證:在某一層內數據項均是按照從左到右,從小到大的順序依次排開,即左1<左2<左3<...

而對於區分度低的字段,沒法找到大小關係,由於值都是相等的,毫無疑問,還想要用b+樹存放這些等值的數據,只能增長樹的高度,字段的區分度越低,則樹的高度越高。極端的狀況,索引字段的值都同樣,那麼b+樹幾乎成了一根棍。本例中就是這種極端的狀況,name字段全部的值均爲'egon'

#如今咱們得出一個結論:爲區分度低的字段創建索引,索引樹的高度會很高,然而這具體會帶來什麼影響呢???

#1:若是條件是name='xxxx',那麼確定是能夠第一時間判斷出'xxxx'是不在索引樹中的(由於樹中全部的值均爲'egon’,看第一條的時候就知道你不在索引樹裏面了),因此查詢速度很快

#2:若是條件正好是name='egon',查詢時,咱們永遠沒法從樹的某個位置獲得一個明確的範圍,只能往下找,往下找,往下找。。。這與全表掃描的IO次數沒有多大區別,因此速度很慢
 

 

    3 =和in能夠亂序,好比a = 1 and b = 2 and c = 3 創建(a,b,c)索引能夠任意順序,mysql的查詢優化器會幫你優化成索引能夠識別的形式

    4 索引列不能參與計算,保持列「乾淨」,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,緣由很簡單,b+樹中存的都是數據表中的字段值,但進行檢索時,須要把全部元素都應用函數才能比較,顯然成本太大。因此語句應該寫成create_time = unix_timestamp(’2014-05-29’)

    

 

    把上面的條件寫成 where id = 3000/3;你會發現速度變得很快,由於等於號後面的數字,是在比較以前就計算出來了,不須要每次都計算一次每次都計算一次了,跟直接等於一個常數是同樣的,因此很快。結論是不要讓你的索引字段參與到計算中。

    5 and/or

 
#一、and與or的邏輯
    條件1 and 條件2:全部條件都成立纔算成立,但凡要有一個條件不成立則最終結果不成立
    條件1 or 條件2:只要有一個條件成立則最終結果就成立

#二、and的工做原理
    條件:
        a = 10 and b = 'xxx' and c > 3 and d =4
    索引:
        製做聯合索引(d,a,b,c)
    工做原理:  #若是是你找的話,你會怎麼找,是否是從左到右一個一個的比較啊,首先你不能肯定a這個字段是否是有索引,即使是有索引,也不必定能確保命中索引了(所謂命中索引,就是應用上了索引),mysql不會這麼笨的,看下面mysql是怎麼找的:
        索引的本質原理就是先不斷的把查找範圍縮小下來,而後再進行處理,對於連續多個and:mysql會按照聯合索引,從左到右的順序找一個區分度高的索引字段(這樣即可以快速鎖定很小的範圍),加速查詢,即按照d—>a->b->c的順序

#三、or的工做原理
    條件:
        a = 10 or b = 'xxx' or c > 3 or d =4
    索引:
        製做聯合索引(d,a,b,c)
        
    工做原理:
        只要一個匹配成功就行,因此對於連續多個or:mysql會按照條件的順序,從左到右依次判斷,即a->b->c->d
 

    索引要加在數據區分度高的字段上

    

    在左邊條件成立可是索引字段的區分度低的狀況下(name與gender均屬於這種狀況),會依次往右找到一個區分度高的索引字段,加速查詢

    

    

    通過分析,在條件爲name='egon' and gender='male' and id>333 and email='xxx'的狀況下,咱們徹底不必爲前三個條件的字段加索引,由於只能用上email字段的索引,前三個字段的索引反而會下降咱們的查詢效率

    

       

    你們還有興趣的話,能夠去研究一下mysql的慢sql查詢的日誌,學一學若是篩選慢sql等操做和配置等,優化慢sql的方式是能夠修改你查詢語句的邏輯,還有更有效的方式就是加索引或者聯合索引。

    6 最左前綴匹配原則(詳見第八小節),很是重要的原則,對於組合索引mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配(指的是範圍大了,有索引速度也慢),好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。

    

    7 其餘狀況

 
- 使用函數
    select * from tb1 where reverse(email) = 'egon';
            
- 類型不一致
    若是列是字符串類型,傳入條件是必須用引號引發來,否則...
    select * from tb1 where email = 999;
    
#排序條件爲索引,則select字段必須也是索引字段,不然沒法命中
- order by
    select name from s1 order by email desc;
    當根據索引排序時候,select查詢的字段若是不是索引,則速度仍然很慢
    select email from s1 order by email desc;
    特別的:若是對主鍵排序,則仍是速度很快:
        select * from tb1 order by nid desc;
 
- 組合索引最左前綴
    若是組合索引爲:(name,email)
    name and email       -- 命中索引
    name                 -- 命中索引
    email                -- 未命中索引


- count(1)或count(列)代替count(*)在mysql中沒有差異了

- create index xxxx  on tb(title(19)) #text類型,必須制定長度
 

 

  二 其餘注意事項

 
- 避免使用select *
- count(1)或count(列) 代替 count(*)
- 建立表時儘可能時 char 代替 varchar
- 表的字段順序固定長度的字段優先
- 組合索引代替多個單列索引(常用多個條件查詢時)
- 儘可能使用短索引
- 使用鏈接(JOIN)來代替子查詢(Sub-Queries)
- 連表時注意條件類型需一致
- 索引散列值(重複少)不適合建索引,例:性別不適合
 

 

八 聯合索引與覆蓋索引

  一 聯合索引

    聯合索引時指對錶上的多個列合起來作一個索引,省的你查詢的時候,where後面的條件字段一直再變,你就想給每一個字段加索引的尷尬問題。聯合索引的建立方法與單個索引的建立方法同樣,不一樣之處在僅在於有多個索引列,以下

 
mysql> create table t(
    -> a int,
    -> b int,
    -> primary key(a),
    -> key idx_a_b(a,b)
    -> );
Query OK, 0 rows affected (0.11 sec)
 

 

    

 

    那麼什麼時候須要使用聯合索引呢?在討論這個問題以前,先來看一下聯合索引內部的結果。從本質上來講,聯合索引就是一棵B+樹,不一樣的是聯合索引的鍵值得數量不是1,而是>=2。接着來討論兩個整型列組成的聯合索引,假定兩個鍵值得名稱分別爲a、b如圖

     

    能夠看到這與咱們以前看到的單個鍵的B+樹並無什麼不一樣,鍵值都是排序的,經過葉子結點能夠邏輯上順序地讀出全部數據,就上面的例子來講,即(1,1),(1,2),(2,1),(2,4),(3,1),(3,2),數據按(a,b)的順序進行了存放。

    所以,對於查詢select * from table where a=xxx and b=xxx, 顯然是能夠使用(a,b) 這個聯合索引的,對於單個列a的查詢select * from table where a=xxx,也是能夠使用(a,b)這個索引的。

    但對於b列的查詢select * from table where b=xxx,則不能夠使用(a,b) 索引,其實你不難發現緣由,葉子節點上b的值爲一、二、一、四、一、2顯然不是排序的,所以對於b列的查詢使用不到(a,b) 索引

    注意創建聯合索引的一個原則:索引是有個最左匹配的原則的,因此建聯合索引的時候,將區分度高的放在最左邊,依次排下來,範圍查詢的條件儘量的日後邊放。

    聯合索引的第二個好處是在第一個鍵相同的狀況下,已經對第二個鍵進行了排序處理,例如在不少狀況下應用程序都須要查詢某個用戶的購物狀況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引能夠幫咱們避免多一次的排序操做,由於索引自己在葉子節點已經排序了,以下

 
#===========準備表==============
create table buy_log(
    userid int unsigned not null,
    buy_date date
);

insert into buy_log values
(1,'2009-01-01'),
(2,'2009-01-01'),
(3,'2009-01-01'),
(1,'2009-02-01'),
(3,'2009-02-01'),
(1,'2009-03-01'),
(1,'2009-04-01');

alter table buy_log add key(userid);
alter table buy_log add key(userid,buy_date);

#===========驗證==============
mysql> show create table buy_log;
| buy_log | CREATE TABLE `buy_log` (
  `userid` int(10) unsigned NOT NULL,
  `buy_date` date DEFAULT NULL,
  KEY `userid` (`userid`),
  KEY `userid_2` (`userid`,`buy_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

#能夠看到possible_keys在這裏有兩個索引能夠用,分別是單個索引userid與聯合索引userid_2,可是優化器最終選擇了使用的key是userid由於該索引的葉子節點包含單個鍵值,因此理論上一個頁能存放的記錄應該更多
mysql> explain select * from buy_log where userid=2;
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+
| id | select_type | table   | type | possible_keys   | key    | key_len | ref   | rows | Extra |
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+
|  1 | SIMPLE      | buy_log | ref  | userid,userid_2 | userid | 4       | const |    1 |       |
+----+-------------+---------+------+-----------------+--------+---------+-------+------+-------+
1 row in set (0.00 sec)

#接着假定要取出userid爲1的最近3次的購買記錄,用的就是聯合索引userid_2了,由於在這個索引中,在userid=1的狀況下,buy_date都已經排序好了
mysql> explain select * from buy_log where userid=1 order by buy_date desc limit 3;
+----+-------------+---------+------+-----------------+----------+---------+-------+------+--------------------------+
| id | select_type | table   | type | possible_keys   | key      | key_len | ref   | rows | Extra                    |
+----+-------------+---------+------+-----------------+----------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | buy_log | ref  | userid,userid_2 | userid_2 | 4       | const |    4 | Using where; Using index |
+----+-------------+---------+------+-----------------+----------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

#ps:若是extra的排序顯示是Using filesort,則意味着在查出數據後須要二次排序(以下查詢語句,沒有先用where userid=3先定位範圍,因而即使命中索引也沒用,須要二次排序)
mysql> explain select * from buy_log order by buy_date desc limit 3;
+----+-------------+---------+-------+---------------+----------+---------+------+------+-----------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows | Extra                       |
+----+-------------+---------+-------+---------------+----------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | buy_log | index | NULL          | userid_2 | 8       | NULL |    7 | Using index; Using filesort |
+----+-------------+---------+-------+---------------+----------+---------+------+------+-----------------------------+


#對於聯合索引(a,b),下述語句能夠直接使用該索引,無需二次排序
select ... from table where a=xxx order by b;

#而後對於聯合索引(a,b,c)來首,下列語句一樣能夠直接經過索引獲得結果
select ... from table where a=xxx order by b;
select ... from table where a=xxx and b=xxx order by c;

#可是對於聯合索引(a,b,c),下列語句不能經過索引直接獲得結果,還須要本身執行一次filesort操做,由於索引(a,c)並未排序
select ... from table where a=xxx order by c;
 

 

  二 覆蓋索引

    InnoDB存儲引擎支持覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就能夠獲得查詢記錄,而不須要查詢彙集索引中的記錄。

    使用覆蓋索引的一個好處是:輔助索引不包含整行記錄的全部信息,故其大小要遠小於彙集索引,所以能夠減小大量的IO操做


     注意:覆蓋索引技術最先是在InnoDB Plugin中完成並實現,這意味着對於InnoDB版本小於1.0的,或者MySQL數據庫版本爲5.0如下的,InnoDB存儲引擎不支持覆蓋索引特性


    對於InnoDB存儲引擎的輔助索引而言,因爲其包含了主鍵信息,所以其葉子節點存放的數據爲(primary key1,priamey key2,...,key1,key2,...)。例如

 
select age from s1 where id=123 and name = 'egon'; #id字段有索引,可是name字段沒有索引,該sql命中了索引,但未覆蓋,須要去彙集索引中再查找詳細信息。
最牛逼的狀況是,索引字段覆蓋了全部,那全程經過索引來加速查詢以及獲取結果就ok了
mysql> desc s1;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | char(6) | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+--------+-------------+------+-----+---------+-------+
4 rows in set (0.21 sec)

mysql> explain select name from s1 where id=1000; #沒有任何索引
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ALL | NULL | NULL | NULL | NULL | 2688336 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> create index idx_id on s1(id); #建立索引
Query OK, 0 rows affected (4.16 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> explain select name from s1 where id=1000; #命中輔助索引,可是未覆蓋索引,還須要從彙集索引中查找name
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| 1 | SIMPLE | s1 | NULL | ref | idx_id | idx_id | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.08 sec)

mysql> explain select id from s1 where id=1000; #在輔助索引中就找到了所有信息,Using index表明覆蓋索引
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | s1 | NULL | ref | idx_id | idx_id | 4 | const | 1 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.03 sec)
 

 

    覆蓋索引的另一個好處是對某些統計問題而言的。基於上一小結建立的表buy_log,查詢計劃以下

 
mysql> explain select count(*) from buy_log;
+----+-------------+---------+-------+---------------+--------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key    | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+--------+---------+------+------+-------------+
|  1 | SIMPLE      | buy_log | index | NULL          | userid | 4       | NULL |    7 | Using index |
+----+-------------+---------+-------+---------------+--------+---------+------+------+-------------+
1 row in set (0.00 sec)
 

 

    innodb存儲引擎並不會選擇經過查詢彙集索引來進行統計。因爲buy_log表有輔助索引,而輔助索引遠小於彙集索引,選擇輔助索引能夠減小IO操做,故優化器的選擇如上key爲userid輔助索引

    對於(a,b)形式的聯合索引,通常是不能夠選擇b中所謂的查詢條件。但若是是統計操做,而且是覆蓋索引,則優化器仍是會選擇使用該索引,以下

 

 
#聯合索引userid_2(userid,buy_date),通常狀況,咱們按照buy_date是沒法使用該索引的,但特殊狀況下:查詢語句是統計操做,且是覆蓋索引,則按照buy_date當作查詢條件時,也能夠使用該聯合索引
mysql> explain select count(*) from buy_log where buy_date >= '2011-01-01' and buy_date < '2011-02-01';
+----+-------------+---------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table   | type  | possible_keys | key      | key_len | ref  | rows | Extra                    |
+----+-------------+---------+-------+---------------+----------+---------+------+------+--------------------------+
|  1 | SIMPLE      | buy_log | index | NULL          | userid_2 | 8       | NULL |    7 | Using where; Using index |
+----+-------------+---------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
 

 

九 查詢優化神器-explain

  關於explain命令相信你們並不陌生,具體用法和字段含義能夠參考官網explain-output,這裏須要強調rows是核心指標,絕大部分rows小的語句執行必定很快(有例外,下面會講到)。因此優化語句基本上都是在優化rows。

  關於explain,若是你們有興趣,能夠看看這篇博客,他總結的挺好的:http://www.cnblogs.com/yycc/p/7338894.html

 
執行計劃:讓mysql預估執行操做(通常正確)
    all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
    id,email
    
    慢:
        select * from userinfo3 where name='alex'
        
        explain select * from userinfo3 where name='alex'
        type: ALL(全表掃描)
            select * from userinfo3 limit 1;
    快:
        select * from userinfo3 where email='alex'
        type: const(走索引)
 

 

  http://blog.itpub.net/29773961/viewspace-1767044/

十 慢查詢優化的基本步驟

 
0.先運行看看是否真的很慢,注意設置SQL_NO_CACHE
1.where條件單表查,鎖定最小返回記錄表。這句話的意思是把查詢語句的where都應用到表中返回的記錄數最小的表開始查起,單表每一個字段分別查詢,看哪一個字段的區分度最高
2.explain查看執行計劃,是否與1預期一致(從鎖定記錄較少的表開始查詢)
3.order by limit 形式的sql語句讓排序的表優先查
4.瞭解業務方使用場景
5.加索引時參照建索引的幾大原則
6.觀察結果,不符合預期繼續從0分析
 

 

十一 慢日誌管理

 
慢日誌
            - 執行時間 > 10
            - 未命中索引
            - 日誌文件路徑
            
        配置:
            - 內存
                show variables like '%query%';
                show variables like '%queries%';
                set global 變量名 =- 配置文件
                mysqld --defaults-file='E:\wupeiqi\mysql-5.7.16-winx64\mysql-5.7.16-winx64\my-default.ini'
                
                my.conf內容:
                    slow_query_log = ON
                    slow_query_log_file = D:/....
                    
                注意:修改配置文件以後,須要重啓服務
 

 

 
MySQL日誌管理
========================================================
錯誤日誌: 記錄 MySQL 服務器啓動、關閉及運行錯誤等信息
二進制日誌: 又稱binlog日誌,以二進制文件的方式記錄數據庫中除 SELECT 之外的操做
查詢日誌: 記錄查詢的信息
慢查詢日誌: 記錄執行時間超過指定時間的操做
中繼日誌: 備庫將主庫的二進制日誌複製到本身的中繼日誌中,從而在本地進行重放
通用日誌: 審計哪一個帳號、在哪一個時段、作了哪些事件
事務日誌或稱redo日誌: 記錄Innodb事務相關的如事務執行時間、檢查點等
========================================================
1、bin-log
1. 啓用
# vim /etc/my.cnf
[mysqld]
log-bin[=dir\[filename]]
# service mysqld restart
2. 暫停
//僅當前會話
SET SQL_LOG_BIN=0;
SET SQL_LOG_BIN=1;
3. 查看
查看所有:
# mysqlbinlog mysql.000002
按時間:
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56"
# mysqlbinlog mysql.000002 --stop-datetime="2012-12-05 11:02:54"
# mysqlbinlog mysql.000002 --start-datetime="2012-12-05 10:02:56" --stop-datetime="2012-12-05 11:02:54" 

按字節數:
# mysqlbinlog mysql.000002 --start-position=260
# mysqlbinlog mysql.000002 --stop-position=260
# mysqlbinlog mysql.000002 --start-position=260 --stop-position=930
4. 截斷bin-log(產生新的bin-log文件)
a. 重啓mysql服務器
b. # mysql -uroot -p123 -e 'flush logs'
5. 刪除bin-log文件
# mysql -uroot -p123 -e 'reset master' 


2、查詢日誌
啓用通用查詢日誌
# vim /etc/my.cnf
[mysqld]
log[=dir\[filename]]
# service mysqld restart

3、慢查詢日誌
啓用慢查詢日誌
# vim /etc/my.cnf
[mysqld]
log-slow-queries[=dir\[filename]]
long_query_time=n
# service mysqld restart
MySQL 5.6:
slow-query-log=1
slow-query-log-file=slow.log
long_query_time=3
查看慢查詢日誌
測試:BENCHMARK(count,expr)
SELECT BENCHMARK(50000000,2*3);
 

 

回到頂部 

相關文章
相關標籤/搜索