搞懂MySQL中的SQL優化,就靠這篇文章了

搞懂MySQL中的SQL優化,就靠這篇文章了

在說優化以前須要先GET到如下知識點,這樣便於後續的分析。看完這篇文章不只要會如何優化,還要搞懂爲何這樣優化。程序員

半雙工通訊:MySQL的數據傳輸採用的是半雙工通訊,同一時間要麼是客戶端向服務端發送數據,要麼是服務端向客戶端發送數據,這兩個動做不能同時發生。MySQL對客戶端發送數據也有要求,一次發送全部數據,等服務端響應後才能發送下次數據。算法

順序讀寫與隨機讀寫:數據庫數據都是要落盤的,因爲磁盤物理結構,尋道時間過長,故順序讀寫比隨機讀寫效率高不少。若是不太懂,能夠想一想平時坐車,你是坐一趟車直達(順序讀寫)好呢?仍是各類換乘(隨機讀寫)好呢?sql

結果緩存:MySQL對查詢的結果是支持緩存的,默認關閉。(提示一下,對於頻繁更新的數據儘可能不要使用MySQL自己的緩存,緩存失效形成更多性能浪費)數據庫

SQL查詢流程:客戶端發送查詢SQL,經過數據傳輸到服務端,優先查詢結果緩存,若是未命中則前後經過解析器、預處理器、優化器、執行計劃、執行引擎、存儲引擎後獲得結果放入內存中並返回給客戶端。(後續專門寫一篇文章介紹下)緩存

索引(Index):幫助MySQL高效獲取數據的數據結構,MySQL中大部分索引都使用多路平衡查找樹。數據結構

在對索引優化以前,須要知道索引的具體結構。根據不一樣的存儲引擎數據的存儲結構也不同,存儲引擎主要使用的有InnoDB、MyISAM。本文主要講InnoDB的索引優化。函數

 

InnoDB引擎索引說明

聚簇索引

每一個表都有一個聚簇索引性能

  1. 主鍵存在時以主鍵爲聚簇索引,
  2. 主鍵不存在時,以第一個不含有null值的惟一索引做爲聚簇索引
  3. 以上索引都不存在時,MySQL會建立一個隱藏字段rowid的聚簇索引。

每一個表的數據按照聚簇索引而彙集在一塊兒造成B+樹。其中在最後的葉子節點掛載非索引數據,葉子節點之間存在有序的指針優化

 

聚簇索引圖示1ui

輔助索引

表中除了聚簇索引外其餘非聚簇索引成爲二級索引或者輔助索引,輔助索引中的葉子節點再也不掛載非索引數據,而是存儲聚簇索引的索引值


輔助索引圖示2

聯合索引

特殊的輔助索引:聯合索引,B+樹的節點存儲的不是一個列數據,而是多個列數據,按照定義的順序構成一個節點。


聯合索引圖示3

 

在對B+樹存儲結構有必定了解下,從實用角度來分析如何優化SQL。這也是SQL優化器要作的功能。

 

索引優化

主鍵的選擇

首先了解B+樹是有序多路平衡查找樹,也就是插入以前須要排序的,爲了平衡還須要拆頁、旋轉等操做。

先說順序自己,順序是比較以後的結果,如何比較?MySQL在創建數據的時候必須指定編碼格式和排序方式,這時便有了比較順序的方式。不管主鍵是何種類型,數字、字符串都會轉換編碼,而後排序。主鍵的可比較性決定了主鍵的效率

再說順序意義,仔細觀察聚簇索引圖示1的葉子節點,也就是最後一層,這是一個有序的頁(圖示中放在一塊的數據稱爲一頁)列表。每次插入都是先肯定主鍵的位置,而後才記錄數據的,葉子節點的是否有序插入決定了主鍵的效率。主鍵的有序性決定的磁盤讀寫的有序性(順序寫比隨機寫效率高不少)。

以上兩點足以說明MySQL中主鍵的有序性的重要性。因此選擇主鍵優先選擇有序主鍵,自增主鍵就是有序主鍵。固然也不要這麼絕對,當數據量太小時這點效率差距是基本看不出來的。

順便說下常常被問的UUID主鍵和自增主鍵的選擇,在數據量太小或者業務剛性需求時,兩者皆可。在數據量過大時,推薦自增主鍵,不只僅由於有序性,還由於字符串的存儲空間是大於整型的存儲空間的。

排序的選擇

前面說順序,這裏就使用下順序,索引樹的葉子節點自己就是有序的,在查詢時order by越匹配該順序則查詢效率越高。所以在排序時,儘可能按照所使用的索引進行排序,也所以全表查詢時默認是主鍵排序。若是查詢條件中涉及到了其餘索引則默認以首個索引的順序爲主。若是不肯定使用了什麼索引,則應該主動指定排序列

一樣基於以上,推薦在頻繁排序或者分組的列上創建索引

索引樹中數據如何獲取

首先先明確一點,索引樹中數據分爲2種,1:索引樹非葉子節點存儲的是索引數據,2:索引葉子節點存儲的是索引數據和表非索引數據。

其次也要明確:聚簇索引是一顆存有全表數據的索引樹,每一個表都是必有的。其餘輔助索引每創建一個就會多一顆索引樹,只是和圖示同樣葉子節點不存儲數據

所以獲取SQL查詢數據應該從2個角度分析

  • 從不一樣索引樹角度
    • 查詢聚簇索引樹
    • 查詢非聚簇索引樹
  • 從查詢數據所在位置角度
    • 查詢索引樹中非葉子節點數據(即索引數據),不查其餘數據
    • 查詢葉子節點中的數據(包含索引和非索引數據)。

SQL索引優化注重點之一在數據所處位置

若是查詢的數據所有在索引樹非葉子節點(即查詢索引列)時,此時效率是最高的,由於節點的有序性,經過高效算法能很快找到數據完成查詢,這種查詢稱爲覆蓋索引查詢。這點告訴使用者:儘可能不要使用select *,同時也應該知道,若是一個表列全是索引,那必定會走索引。(別再說什麼 not null 、!= 必定不走索引的問題了)

若是查詢的數據不在索引樹非葉子節點(即查詢非索引列)時,注意此時SQL優化器頗有可能會優化書寫的SQL,致使最終執行的SQL和客戶端傳輸的SQL不一致。

先說下此時正規的數據查找流程:

  • 若是查詢條件存在索引,則使用第一個索引條件列(優化後的)去首次加載數據行
    • 索引爲聚簇索引,則在聚簇索引樹上,根據算法查詢到索引所處的葉子節點位置,把該位置的對應數據獲取便可
    • 索引爲非聚簇索引,則在非聚簇索引樹上,根據算法查詢引所處的葉子節點位置,獲取到該位置上的聚簇索引值,而後拿到該值在聚簇索引樹上定位其位置,再把聚簇索引樹葉子節點上對應的數據獲取便可。從非聚簇索引樹再到聚簇索引樹的過程稱爲回表。
  • 若是查詢條件不存在索引
    • 因爲沒有索引,因此會去聚簇索引樹的非葉子節點數據處進行全表掃描,逐個匹配,直至掃描完畢獲取到數據返回
 

從聚簇索引中獲取到的數據行,會加載到內存中,而後在進行

where其餘條件的過濾,最後才返回過濾後的數據,

這點告訴使用者:where條件中首個條件應儘可能精確匹配(例如主鍵、高離散度索引列)數據。

索引樹的分裂、節點移除

索引樹中每一個頁存儲的數據個數是固定的,例如4個,當該頁新增數據時,若是數據已滿4個,則須要分裂爲2個頁,每頁仍是4個來保證。

節點移除時,索引樹會進行旋轉來達到平衡。具體流程可自行查詢平衡樹。這裏只須要知道:索引樹調整很浪費時間,開銷很大。

所以頻繁更新的列,不適合做爲主鍵或者索引

 

最左匹配原則

問個索引優化,都說最左匹配原則,但是否知道爲何是最左匹配,如何匹配?

在上面說順序時提到了如何排序,這裏如何匹配也是相似,例如abcabd如何匹配,這裏說下通俗理解(不必定是實現),把這兩個字符逐個經過編碼、排序獲取排序值,假設a編碼後排序值爲 32b 編碼後排序值爲33c 編碼後排序值爲 34,設d編碼後排序值爲35;匹配時先對a比較==,若是不等則沒必要再進行匹配,若是相等則比較b、而後c,最終發現35>34因而結果就是不匹配。第一步的a的匹配就是最左開始匹配原則。

最左匹配的應用:

  • like匹配,只有左邊字符肯定才能支持最左匹配原則,即不支持%xxx匹配。

  • 聯合索引匹配,聯合索引中非葉子節點中數據存儲是安裝聯合索引定義的順序組合成一個節點的,例如

    index0,index1,index2一旦順序不對則不能進行匹配。可是記住一點:組合後的索引節點是按照一個節點在索引中排序的,也就是哪怕匹配了一個索引也是能提升效率的。例如:聚合索引a,b,c查詢條件where a=1 and c=1,此時a=1是能走聚合索引的,可是c就不行了,此時等同於%c。這裏也有個坑,會問這個查詢是否走索引,回答是走索引(部分走也是走)。還有查詢條件中遇到範圍查詢(like != > < 等)則會停止後續匹配。直接理解爲聯合索引就是一個拼接後的字符列索引,遇到範圍查詢則會致使開銷指數級變大。

 

索引條件下推ICP

在索聚簇索引樹查詢數據行以前,匹配的數據行越少,越精確則查詢效率越高。ICP(index_condition_pushdown)技術就是優化的這部分,旨在儘可能減小數據行加載到內存中。在InnoDB引擎中ICP只支持聯合索引,由於聚簇索引能直接鎖定要查詢的數據行,沒法繼續再篩選(聚簇索引只有一個索引),而聯合索引則是至少2個索引,在第一個索引匹配的行數和後續其餘聯合索引匹配的行數處理後,再回表到聚簇索引樹中查詢數據,這樣聚簇索引樹中的數據行就會縮減,從而提升效率。ICP技術是默認開啓的。explain提示信息爲:Using index condition,設置參數爲:index_condition_pushdown

ICP應用:

  • 儘可能創建聚合索引而不是多個單索引where條件後面按照聚合索引列做爲條件

函數對索引條件的影響

內置函數

MySQL函數的contract,date_format,count等

函數區分爲2種,1:該函數能夠獲得肯定的結果,這種稱爲肯定性函數,2:該函數不能獲得肯定的結果,具體的結果由參數決定,這種稱爲不肯定性函數

表達式

計算表達式,1+一、2*3等

函數和表達式位置分爲條件左側和右側,條件左側即條件列,右側爲查詢條件。

  • 對於右側:
    • 肯定性函數大部分可使用索引,例如: contract、pow
    • 不肯定性函數基本不能使用索引,例如: rand,uuid
  • 對於左側:
    • 必定致使索引失效,並且任何對左側索引列的處理都會致使索引失效,包含編碼格式、函數、表達式計算等。 例如:where age + 10 = 30 應寫爲where age = 30 + 10這種寫法沒問題,MySQL會自動優化爲where age = 40

NULL的優化

MySQL支持索引列的null查詢,且支持is not nullis null,屬於範圍查詢。出現索引失效的通常都是由於回表開銷過大致使的,畢竟數據爲null爲少數或者多數。

非空約束列的is null查詢不會走索引,由於有比索引更高效的查詢方式。

開銷優化

MySQL的優化器是基於開銷的,它對客戶端的SQL會解析出多條一樣效果的SQL,最終選擇的是開銷最小的SQL。基本全部的優化都基於此。

離散度體現的開銷

例如:在性別sex列表創建索引,然而sex值只有0和1。若是表中數據全是男或者全是女,優化器會以爲全表掃描會因爲索引查詢,畢竟不用從索引樹的根節點逐個比較。

開銷大小對索引而已外觀表現爲索引列數據的離散度,離散度至關於count(distinct(column_name))/count(*)。對於這種離散度低的列不建議創建索引

 

全表掃描開銷

例如:聚合索引a,b,c,在查詢條件中使用where a=1 or d=1,這裏d爲非索引列,此時會致使匹配d時必須全表掃描,既然都全表掃描了說明索引樹中的數據行都加載到了內存,所以不必經過索引去過濾,定位聚簇索引樹的位置了,因而最終採用的是全表掃描而不會走索引。注:若是表全部列都是索引則全表掃描也是走索引樹掃描。覆蓋索引優先級比全表掃描優先級高

聯合索引順序開銷

例如:聚合索引a,b,c,在查詢條件中書寫順序where a=1 and b=1 and c=1和書寫順序where c=1 and a=1 and b=1不影響索引使用,SQL優化器會分析出最小的開銷,就是按照索引定義順序來糾正查詢條件。符合最左匹配原則纔有意義。

其餘索引優化

MySQL優化點不少,只是列一些常見的優化

隱式轉換

字符串類型的列必定要加單引號'',不然會隱式轉換爲數字,致使索引失效

負向索引

負向索引(<> 、!= 、not in)有可能使用索引,可是大部分不會使用索引,這要基於SQL優化器優化了。例如對於索引列a,若是值全是1(離散度太低),此時<>1 、!=、not in(1) 都是會走索引的。注意不走索引便意味着全表掃描。

對於負向索引(not like) 必定不走索引。

強制索引

當SQL優化器優化後不是想要的SQL時,能夠指定強制索引(force index(idx_name))來讓SQL使用指定的索引查詢,不必定會採用,只有多個執行計劃中有這個索引的執行計劃時纔有效(畢竟強制一個不查詢的索引也沒意義)。

 

其餘優化

查詢結果越少越好

前面提到MySQL是半雙工通訊,客戶端須要等待服務端處理好結果且返回以後才能繼續。若是查詢結果很大,會致使後續請求阻塞。故善用limit,不要select *,也注意insert into xxx select xxx這個select結果也是越少越好

子查詢越少越好,最好不存在

子查詢會致使屢次查詢數據行,浪費IO。我的建議即便屢次請求也比子查詢好。不只能看懂,效率也不必定下降。

查詢SQL越精確越好

SQL越精確,在進行查找時讀取的數據行越少,查詢效率越高。

儘可能不要隨機讀取

基於磁盤性能,隨機讀取效率差,索引樹查詢開銷大,不建議

常量查詢效率比索引查詢高

能使用常量查詢的儘可能使用常量查詢

例如:只是確認是否存在,不必查詢其餘字段

select 1 from user where name='xx' limit 1

例如 非空約束列查詢is null

時間字段儘可能使用數據庫函數

雖說大部分數據庫和線上庫都會統一時間,可是防止埋坑,並且數據庫自身的效率會高點,固然這點性能沒什麼影響。若是不必仍是建議使用數據庫自身的時間函數來填充時間字段。

update user set modify_time=now()

使用IN代替OR

針對同列的IN 和 OR 若是查詢字段是索引列,則兩者性能基本一致,不然In的效率隨着數據量增大會比OR愈來愈高,

針對IN,MySQL會估算in範圍的條數開銷,in的範圍越大開銷越大,特別是否是惟一列的開銷更大,此時能夠考慮join等方式是否能夠試下,畢竟in其實也是等值比較,join鏈接條件也是等值比較。固然也能夠考慮exists

針對不一樣列的OR,例如where a=1 or b=1,會被優化爲union,儘可能主動書寫union

select a,b from  source where a=0  or  b=2

推薦寫法

select a,b from  source where a=0  
union
select a,b from  source where b=2

In和Exists

使用IN時要保證IN中的總數據量小且in以後的數據量也很小才能操做其效率高。Exists則是exists語句中的數據量大,可是匹配後小則效率高。

在考慮in和exists時,思考下哪一個遍歷的少,哪一個效率就高。

平時常見的索引優化暫時就羅列這些,一旦想起來再來補充吧!

 

補充

Like優化

通過數據驗證,like在千萬級數據時效率不好,反而沒有instr函數效率高。

select xxx  from xxx  where xxx like '%abc%'

不如走索引的如下語句好

select xxx  from xxx  where xxx like 'abc%'

走索引的like不如如下語句好

select xxx  from xxx  where instr( xxx, 'abc' ) > 0

 

原文地址:程序員微錄 搞懂Mysql中的SQL優化,就靠這篇文章了

相關文章
相關標籤/搜索