1. 程式人生 > >重新學習Mysql資料庫5:根據MySQL索引原理進行分析與優化

重新學習Mysql資料庫5:根據MySQL索引原理進行分析與優化

本文出自我的公眾號:程式設計師江湖。

滿滿乾貨,關注就送。

一:Mysql原理與慢查詢

MySQL憑藉著出色的效能、低廉的成本、豐富的資源,已經成為絕大多數網際網路公司的首選關係型資料庫。雖然效能出色,但所謂“好馬配好鞍”,如何能夠更好的使用它,已經成為開發工程師的必修課,我們經常會從職位描述上看到諸如“精通MySQL”、“SQL語句優化”、“瞭解資料庫原理”等要求。我們知道一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現效能問題,遇到最多的,也是最容易出問題的,還是一些複雜的查詢操作,所以查詢語句的優化顯然是重中之重。

本人從13年7月份起,一直在美團核心業務系統部做慢查詢的優化工作,共計十餘個系統,累計解決和積累了上百個慢查詢案例。隨著業務的複雜性提升,遇到的問題千奇百怪,五花八門,匪夷所思。本文旨在以開發工程師的角度來解釋資料庫索引的原理和如何優化慢查詢。

一個慢查詢引發的思考

select
   count(*) 
from
   task 
where
   status=2 
   and operator_id=20839 
   and operate_time>1371169729 
   and operate_time<1371174603 
   and type=2;

系統使用者反應有一個功能越來越慢,於是工程師找到了上面的SQL。
並且興致沖沖的找到了我,“這個SQL需要優化,給我把每個欄位都加上索引”
我很驚訝,問道“為什麼需要每個欄位都加上索引?”
“把查詢的欄位都加上索引會更快”工程師信心滿滿
“這種情況完全可以建一個聯合索引,因為是最左字首匹配,所以operate_time需要放到最後,而且還需要把其他相關的查詢都拿來,需要做一個綜合評估。”
“聯合索引?最左字首匹配?綜合評估?”工程師不禁陷入了沉思。
多數情況下,我們知道索引能夠提高查詢效率,但應該如何建立索引?索引的順序如何?許多人卻只知道大概。其實理解這些概念並不難,而且索引的原理遠沒有想象的那麼複雜。

MySQL索引原理

索引目的
索引的目的在於提高查詢效率,可以類比字典,如果要查“mysql”這個單詞,我們肯定需要定位到m字母,然後從下往下找到y字母,再找到剩下的sql。如果沒有索引,那麼你可能需要把所有單詞看一遍才能找到你想要的,如果我想找到m開頭的單詞呢?或者ze開頭的單詞呢?是不是覺得如果沒有索引,這個事情根本無法完成?

索引原理
除了詞典,生活中隨處可見索引的例子,如火車站的車次表、圖書的目錄等。它們的原理都是一樣的,通過不斷的縮小想要獲得資料的範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是我們總是通過同一種查詢方式來鎖定資料。
資料庫也是一樣,但顯然要複雜許多,因為不僅面臨著等值查詢,還有範圍查詢(>、<、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的機器每秒可以執行5億條指令,因為指令依靠的是電的性質,換句話說執行一次IO的時間可以執行40萬條指令,資料庫動輒十萬百萬乃至千萬級資料,每次9毫秒的時間,顯然是個災難。下圖是計算機硬體延遲的對比圖,供大家參考:
various-system-software-hardware-latencies
考慮到磁碟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. 主鍵索引

primary key() 要求關鍵字不能重複,也不能為null,同時增加主鍵約束 
主鍵索引定義時,不能命名

2. 唯一索引

unique index() 要求關鍵字不能重複,同時增加唯一約束

3. 普通索引

index() 對關鍵字沒有要求

4. 全文索引

fulltext key() 關鍵字的來源不是所有欄位的資料,而是欄位中提取的特別關鍵字

關鍵字:可以是某個欄位或多個欄位,多個欄位稱為複合索引

建表:
creat table student(
    stu_id int unsigned not null auto_increment,
    name varchar(32) not null default '',
    phone char(11) not null default '',
    stu_code varchar(32) not null default '',
    stu_desc text,
    primary key ('stu_id'),     //主鍵索引
    unique index 'stu_code' ('stu_code'), //唯一索引
    index 'name_phone' ('name','phone'),  //普通索引,複合索引
    fulltext index 'stu_desc' ('stu_desc'), //全文索引
) engine=myisam charset=utf8;

更新:
alert table student
    add primary key ('stu_id'),     //主鍵索引
    add unique index 'stu_code' ('stu_code'), //唯一索引
    add index 'name_phone' ('name','phone'),  //普通索引,複合索引
    add fulltext index 'stu_desc' ('stu_desc'); //全文索引

刪除:
alert table sutdent
    drop primary key,
    drop index 'stu_code',
    drop index 'name_phone',
    drop index 'stu_desc';

三:淺析explain用法

有什麼用?

在MySQL中,當資料量增長的特別大的時候就需要用到索引來優化SQL語句,而如何才能判斷我們辛辛苦苦寫出的SQL語句是否優良?這時候explain就派上了用場。

怎麼使用?

explain + SQL語句即可 如:explain select * from table;

如下

explain引數

相信第一次使用explain引數的朋友一定會疑惑這一大堆引數究竟有什麼用呢?筆者蒐集了一些資料,在這兒做一個總結希望能夠幫助大家理解。

引數介紹

id

如果是子查詢,id的序號會遞增,id的值越大優先順序越高,越先被執行

select_type

查詢的型別,主要用於區別普通查詢、聯合查詢、子查詢等的複雜查詢 SIMPLE:簡單的select查詢,查詢中不包含子查詢或者UNION PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記為PRIMARY(最後載入的那一個 ) SUBQUERY:在SELECT或WHERE列表中包含了子查詢 DERIVED:在FROM列表中包含的子查詢被標記為DERIVED(衍生)Mysql會遞迴執行這些子查詢,把結果放在臨時表裡。 UNION:若第二個SELECT出現在UNION之後,則被標記為UNION;若UNION包含在FROM字句的查詢中,外層SELECT將被標記為:DERIVED UNION RESULT:從UNION表獲取結果的SELECT type

	顯示查詢使用了何種型別
	從最好到最差依次是
System>const>eq_ref>range>index>All(**全表掃描**)
	一般來說**至少達到range級別,最好達到ref**
System:表只有一行記錄,這是const型別的特例,平時不會出現(忽略不計)
const:表示通過索引一次就找到了,const用於比較primary key或者unique索引,因為只匹配一行資料,所以很快。如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量。
eq_ref:唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描。
ref:非唯一索引掃描,返回匹配某個單獨值的行,本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而它可能會找到多個符合條件的行,所以它應該屬於查詢和掃描的混合體
range:只檢索給定範圍的行,使用一個索引來選擇行。key列顯示使用了哪個索引,一般就是在你的where語句中出現了between、<、>、in等的查詢。這種範圍掃描索引比全表掃描要好,因為它只需要開始於索引的某一點,而結束於另一點,不用掃描全部索引。
index:FULL INDEX SCAN,index與all區別為index型別只遍歷索引樹。這通常比all快,因為索引檔案通常比資料檔案小。

extra

包含不適合在其他列中顯示但十分重要的額外資訊 包含的資訊: **(危險!)**Using filesort:說明mysql會對資料使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,MYSQL中無法利用索引完成的排序操作稱為“檔案排序” **(特別危險!)**Using temporary:使用了臨時表儲存中間結果,MYSQL在對查詢結果排序時使用臨時表。常見於排序order by 和分組查詢 group by Using index:表示相應的select操作中使用了覆蓋索引,避免訪問了表的資料行,效率不錯。如果同時出現using where,表明索引被用來執行索引鍵值的查詢;如果沒有同時出現using where,表明索引用來讀取資料而非執行查詢操作。

possible_keys

顯示可能應用在這張表中的索引,一個或多個。查詢涉及到的欄位上若存在索引,則該索引將被列出, 但不一定被查詢實際使用

key

實際使用的索引,如果為NULL,則沒有使用索引。查詢中若使用了覆蓋索引,則該索引僅出現在key列表中,key引數可以作為使用了索引的判斷標準

key_len

:表示索引中使用的位元組數,可通過該列計算查詢中索引的長度,在不損失精確性的情況下,長度越短越好,key_len顯示的值為索引欄位的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的。

ref

顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查詢索引上的值。

rows

根據表統計資訊及索引選用情況,大致估算出找到所需記錄所需要讀取的行數

四:慢查詢優化

關於MySQL索引原理是比較枯燥的東西,大家只需要有一個感性的認識,並不需要理解得非常透徹和深入。我們回頭來看看一開始我們說的慢查詢,瞭解完索引原理之後,大家是不是有什麼想法呢?先總結一下索引的幾大基本原則

建索引的幾大原則

1.最左字首匹配原則,非常重要的原則,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的順序可以任意調整。
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語句的索引應該是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分析

五:最左字首原理與相關優化

高效使用索引的首要條件是知道什麼樣的查詢會使用到索引,這個問題和B+Tree中的“最左字首原理”有關,下面通過例子說明最左字首原理。

這裡先說一下聯合索引的概念。在上文中,我們都是假設索引只引用了單個的列,實際上,MySQL中的索引可以以一定順序引用多個列,這種索引叫做聯合索引,一般的,一個聯合索引是一個有序元組<a1, a2, …, an>,其中各個元素均為資料表的一列,實際上要嚴格定義索引需要用到關係代數,但是這裡我不想討論太多關係代數的話題,因為那樣會顯得很枯燥,所以這裡就不再做嚴格定義。另外,單列索引可以看成聯合索引元素數為1的特例。

以employees.titles表為例,下面先檢視其上都有哪些索引:

  1. SHOW INDEX FROM employees.titles;
  2. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  3. |Table|Non_unique|Key_name|Seq_in_index|Column_name|Collation|Cardinality|Null|Index_type|
  4. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  5. | titles |0| PRIMARY |1| emp_no | A | NULL || BTREE |
  6. | titles |0| PRIMARY |2| title | A | NULL || BTREE |
  7. | titles |0| PRIMARY |3| from_date | A |443308|| BTREE |
  8. | titles |1| emp_no |1| emp_no | A |443308|| BTREE |
  9. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+

從結果中可以到titles表的主索引為<emp_no, title, from_date>,還有一個輔助索引<emp_no>。為了避免多個索引使事情變複雜(MySQL的SQL優化器在多索引時行為比較複雜),這裡我們將輔助索引drop掉:

  1. ALTER TABLE employees.titles DROP INDEX emp_no;

這樣就可以專心分析索引PRIMARY的行為了。

情況一:全列匹配。

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. |1| SIMPLE | titles |const| PRIMARY | PRIMARY |59|const,const,const|1||
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

很明顯,當按照索引中所有列進行精確匹配(這裡精確匹配指“=”或“IN”匹配)時,索引可以被用到。這裡有一點需要注意,理論上索引對順序是敏感的,但是由於MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引,例如我們將where中的條件順序顛倒:

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. |1| SIMPLE | titles |const| PRIMARY | PRIMARY |59|const,const,const|1||
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

效果是一樣的。

情況二:最左字首匹配。

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  5. |1| SIMPLE | titles |ref| PRIMARY | PRIMARY |4|const|1||
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+

當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,所以可以被用到,但是隻能用到一部分,即條件所組成的最左字首。上面的查詢從分析結果看用到了PRIMARY索引,但是key_len為4,說明只用到了索引的第一列字首。

情況三:查詢條件用到了索引中列的精確匹配,但是中間某個條件未提供。

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  5. |1| SIMPLE | titles |ref| PRIMARY | PRIMARY |4|const|1|Usingwhere|
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+

此時索引使用情況和情況二相同,因為title未提供,所以查詢只用到了索引的第一列,而後面的from_date雖然也在索引中,但是由於title不存在而無法和左字首連線,因此需要對結果進行掃描過濾from_date(這裡由於emp_no唯一,所以不存在掃描)。如果想讓from_date也使用索引而不是where過濾,可以增加一個輔助索引<emp_no, from_date>,此時上面的查詢會使用這個索引。除此之外,還可以使用一種稱之為“隔離列”的優化方法,將emp_no與from_date之間的“坑”填上。

首先我們看下title一共有幾種不同的值:

  1. SELECT DISTINCT(title) FROM employees.titles;
  2. +--------------------+
  3. | title |
  4. +--------------------+
  5. |SeniorEngineer|
  6. |Staff|
  7. |Engineer|
  8. |SeniorStaff|
  9. |AssistantEngineer|
  10. |TechniqueLeader|
  11. |Manager|
  12. +--------------------+

只有7種。在這種成為“坑”的列值比較少的情況下,可以考慮用“IN”來填補這個“坑”從而形成最左字首:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no='10001'
  3. AND title IN ('Senior Engineer','Staff','Engineer','Senior Staff','Assistant Engineer','Technique Leader','Manager')
  4. AND from_date='1986-06-26';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. |1| SIMPLE | titles | range | PRIMARY | PRIMARY |59| NULL |7|Usingwhere|
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

這次key_len為59,說明索引被用全了,但是從type和rows看出IN實際上執行了一個range查詢,這裡檢查了7個key。看下兩種查詢的效能比較:

  1. SHOW PROFILES;
  2. +----------+------------+-------------------------------------------------------------------------------+
  3. |Query_ID|Duration|Query|
  4. +----------+------------+-------------------------------------------------------------------------------+
  5. |10|0.00058000| SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'|
  6. |11|0.00052500| SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ...|
  7. +----------+------------+-------------------------------------------------------------------------------+

“填坑”後效能提升了一點。如果經過emp_no篩選後餘下很多資料,則後者效能優勢會更加明顯。當然,如果title的值很多,用填坑就不合適了,必須建立輔助索引。

情況四:查詢條件沒有指定索引第一列。

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  5. |1| SIMPLE | titles | ALL | NULL | NULL | NULL | NULL |443308|Usingwhere|
  6. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

由於不是最左字首,索引這樣的查詢顯然用不到索引。

情況五:匹配某列的字首字串。

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. |1| SIMPLE | titles | range | PRIMARY | PRIMARY |56| NULL |1|Usingwhere|
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

此時可以用到索引,但是如果萬用字元不是隻出現在末尾,則無法使用索引。(原文表述有誤,如果萬用字元%不出現在開頭,則可以用到索引,但根據具體情況不同可能只會用其中一個字首)

情況六:範圍查詢。

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no <'10010'and title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. |1| SIMPLE | titles | range | PRIMARY | PRIMARY |4| NULL |16|Usingwhere|
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

範圍列可以用到索引(必須是最左字首),但是範圍列後面的列無法用到索引。同時,索引最多用於一個範圍列,因此如果查詢條件中有兩個範圍列則無法全用到索引。

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no <'10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. |1| SIMPLE | titles | range | PRIMARY | PRIMARY |4| NULL |16|Usingwhere|
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

可以看到索引對第二個範圍索引無能為力。這裡特別要說明MySQL一個有意思的地方,那就是僅用explain可能無法區分範圍索引和多值匹配,因為在type中這兩者都顯示為range。同時,用了“between”並不意味著就是範圍查詢,例如下面的查詢:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no BETWEEN '10001' AND '10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. |1| SIMPLE | titles | range | PRIMARY | PRIMARY |59| NULL |16|Usingwhere|
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

看起來是用了兩個範圍查詢,但作用於emp_no上的“BETWEEN”實際上相當於“IN”,也就是說emp_no實際是多值精確匹配。可以看到這個查詢用到了索引全部三個列。因此在MySQL中要謹慎地區分多值匹配和範圍匹配,否則會對MySQL的行為產生困惑。

情況七:查詢條件中含有函式或表示式。

很不幸,如果查詢條件中含有函式或表示式,則MySQL不會為這列使用索引(雖然某些在數學意義上可以使用)。例如:

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title,6)='Senior';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len

    相關推薦

    重新學習Mysql資料庫5根據MySQL索引原理進行分析優化

    本文出自我的公眾號:程式設計師江湖。 滿滿乾貨,關注就送。 一:Mysql原理與慢查詢 MySQL憑藉著出色的效能、低廉的成本、豐富的資源,已經成為絕大多數網際網路公司的首選關係型資料庫。雖然效能出色,但所謂“好馬配好鞍”,如何能夠更好的使用它,已經成為開發工程

    頂級架構師學習——第三階段深入JVM核心——原理、診斷優化

    1、JVM簡介 JVM是Java Virtual Machine的簡稱,意為Java虛擬機器,使用軟體模擬Java 位元組碼的指令集。 2、JVM執行機制 JVM啟動流程 JVM基本結構   1.PC暫存器 每個執行緒擁有一個PC暫存器,線上程建立時建立,指

    MySQL資料庫5GoMySQL的互動

    下載第三方依賴 go get github.com/jmoiron/sqlx go get github.com/go-sql-driver/mysql 引入依賴 import ( "github

    【搞定MySQL資料庫深入淺出 MySQL 和 InnoDB

    轉發自:https://blog.csdn.net/a724888/article/details/78765898 本文目錄: 1、資料庫的定義 1.1、資料庫和例項 1.2、MySQL 的架構 1.3、資料的儲存 1.4、如何儲存表 1.5、如何儲存記錄 1.6、

    實踐App記憶體優化如何有序地做記憶體分析優化

    由於專案裡之前線上版本出現過一定比例的OOM,雖然比例並不大,但是還是暴露了一定的問題,所以打算對我們App分為幾個步驟進行記憶體分析和優化,當然記憶體的優化是個長期的過程,不是一兩個版本的事,每個版本都需要收集線上記憶體資料進行監控以及分析。 版本迭代

    重新學習MySQL資料庫9Innodb中的事務隔離級別和鎖的關係

    本文出自我的公眾號:程式設計師江湖。 滿滿乾貨,關注就送。 轉自https://tech.meituan.com/innodb-lock.html Innodb中的事務隔離級別和鎖的關係 前言: 我們都知道事務的幾種性質,資料庫為了維護這些性質,尤其是一致性和

    重新學習MySQL資料庫開篇資料庫的前世今生

    本文內容出自劉欣的“碼農翻身”公眾號,強烈推薦劉欣大大的文章。 資料庫的前世今生 小李的資料庫之旅 無紙化辦公 小李是這個大學電腦科學與技術系的知名學生,他的程式設計能力了得,使用Pascal 爐火純青,這都是高中期間參加全國青少年資訊學奧林匹克競

    MySQL資料庫實驗任務一 建立資料庫和表

    目錄 任務一 建立資料庫和表 【實訓目的與要求】 【實訓原理】 【實訓步驟】 一、熟悉MySQL環境 二、利用MySQL命令列視窗建立資料庫及表 三、利用介面工具建立資料庫及表 任務一 建立資料庫和表

    MySQL資料庫實驗任務二 表資料的插入、修改及刪除

    目錄 任務二 表資料的插入、修改及刪除 一、利用介面工具插入資料 二、資料更新 (一)利用MySQL命令列視窗更新資料 (二)利用Navicat for MySQL客戶端工具更新資料 三、資料庫的備份與還原

    navicate 連結 mysql資料庫 錯誤2059

    在網上查的是,出現這個原因是mysql8 之前的版本中加密規則是mysql_native_password,而在mysql8之後,加密規則是caching_sha2_password,這裡的解決方法是把mysql使用者登入密碼加密規則還原成mysql_native_p

    python----mysql資料庫連線以pymysql替代mysqlclient和MySQLdb

    在pycharm中,以pymysql替代mysqlclient和MySQLdb 在windows10,終端pip install mysqlclient 時,是安裝不了mysqlclient這個庫的,需要在https://www.lfd.uci.edu/~gohlke/py

    MySQL資料庫5.5安裝版安裝

    1.MySQL資料庫下載 2.MySQL資料庫安裝 2.1MySQL的安裝 選擇安裝型別,有“Typical(預設)”、“Complete(完全)”、“Custom(使用者自定義)”三個選項,選擇“Custom” 點選“Browse”,手動指定安裝目錄

    mysql資料庫5.7.*版本zip檔案安裝方法

    MySQL安裝分為兩種: 1、msi格式安裝,直接連續next就好。 2、zip格式安裝,詳續。 mysql官網下載地址:https://dev.mysql.com/downloads/mysql/ zip格式安裝步驟如下: 1、mysql官網下載與自己電腦符合的版本

    Shell指令碼使用匯總整理——mysql資料庫5.7.8以後備份指令碼

    Shell指令碼使用匯總整理——mysql資料庫5.7.8以後備份指令碼 Shell指令碼使用的基本知識點彙總詳情見連線: https://www.cnblogs.com/lsy-blogs/p/9223477.html 指令碼分為三部分配置資訊、指令碼檔案、定時任務; 1、配置資訊: use

    Python3操作MySQL資料庫(驅動pymysql)

    建庫建表 create database wuSir default character set utf8 collate utf8_general_ci; use wuSir; create table auth_info( aid int

    【搞定MySQL資料庫MySQL索引實現原理

    本文轉發自:https://blog.csdn.net/a724888/article/details/78366383 本文主要轉載自幾篇關於MySQL資料庫索引相關的文章。可以相互參考著看。 目錄 1、MySQL索引型別 1.1、簡介 1.2、語句 1.3、索引型別

    四十八、mysql資料庫7Mysqlpython的互動、引數化(重點pythonmysql互動傳參)

    一、使用python命令連線資料庫流程 二、python3 安裝pymysql包 建立py檔案,進行插入資料:通過python檔案來連線資料庫實現互動(前提需要安裝pymysql包) 1、Li

    操作MySQL資料庫報出Parameter index out of range (1 > number of parameters, which is

    對MySQL進行insert操作,控制檯丟擲以下錯誤:Parameter index out of range (1 > number of parameter

    MySQL資料庫提示Communications link failure,The last packet succe

    Last modified:2013-10-08 14:16:47      **********************************************       web網站使用MySQL資料庫,今天突然報以下錯誤:       Communication

    MySQL資料庫檢視檢視定義、建立檢視、修改檢視

    檢視是指計算機資料庫中的檢視,是一個虛擬表,其內容由查詢定義。同真實的表一樣,檢視包含一系列帶有名稱的列和行資料。但是,檢視並不在資料庫中以儲存的資料值集形式存在。行和列資料來自由定義檢視的查詢所引用的表,並且在引用檢視時動態生成。——百度百科 關係型