Mysql調優-explain的使用

explain簡介

explain和SQL語句一起使用的時候,MySQL將顯示來自優化器的相關語句執行計劃的信息,包括表的讀取順序、數據讀取操作的操作類型、那些索引可以被使用、那些索引會被實際使用、表之間的引用、每張表大概需要讀取多少行記錄等等。通過這些我們能分析SQL語句的結構和性能瓶頸,從而優化我們的SQL語句。

example:

explain輸出列解釋

字段 字段說明 字段值描述
id select查詢序列號,包含一組數字,表示查詢中執行select子句或者操作表的順序 id相同 執行順序由上至下
id不相同 如果是子查詢,id的序號會遞增,id的值越大被執行的優先級越高
id相同和不相同都存在 如果id相同可以認爲是一組,同一組id執行順序由上至下,不同組之間,id值越大被執行的優先級越高
select_type 查詢的類型,主要用來區別普通查詢,聯合查詢,子查詢等複雜查詢 SIMPLE 簡單的select查詢,查詢中不包含子查詢或者UNION
PRIMARY 查詢中若包含任何複雜的子部分,最外層的查詢則被標記爲PRIMARY
SUBQUERY 在select或者where列表中包含了子查詢
DERIVED 在from列表中包含的子查詢會被標記爲DERICED(衍生),Mysql會遞歸地執行這些子查詢,然後把結果放到臨時表
UNION 若第二個select語句出現在UNION之後,則被標記爲UNION。若UNION包含在from子句的子查詢中,外層select則被標記爲DERIVED
UNION RESULT 從union表獲取結果的select
type 訪問類型排序,顯示查詢使用了何種類型,從最好到最差依次是:system>const>eq_ref>ref>range>index>ALL system 表只有一行記錄,這是const類型的特例,這個平時很少出現
const 表最多有一個匹配行,它將在查詢開始時被讀取。因爲僅有一行記錄匹配,所以這行的列值可被優化器剩餘部分認爲是常數。const表很快,因爲它們只讀取一次!
eq_ref 驅動表和關聯表中的每行進行組合並且僅有一行記錄。這是除了system 和 const 類型之外, 這是最好的聯接類型。當連接使用索引的所有部分時, 索引是主鍵或唯一非 NULL 索引時, 將使用該值
ref 非唯一性索引掃描,返回符合某個索引值的所有記錄,可能會有多條記錄匹配
range 使用一個索引來檢索給定範圍的行,這種範圍索引掃描比全表掃描效率要高
index 使用覆蓋索引
all 全表掃描(full table scan)
possiable_keys 顯示可能應用在這張表中的索引,一個或者多個,查詢的涉及的字段若存在索引,則該索引被列出,但是不一定被查詢實際用到
key 實際使用的索引,如果爲NULL,則沒有使用索引;若查詢中使用了覆蓋索引,則該索引只會出現在key列表中
key_len 表示索引中使用的字節數,可通過該列計算查詢中所使用索引的長度,在損失精度的情況下,索引長度越短越好;key_len顯示的值爲索引字段最大的可能長度,並非實際使用的長度,即key_len是根據表的定義計算而得,並非通過表內檢索出的
ref 哪些列或者常量被用做索引列上的值
rows 根據表的統計信息和索引的使用情況,大致估算查詢結果所需要讀取記錄的行數
extra 包含其explain字段不適合顯示但又十分重要的額外信息

explain輸出列-id

  • id相同:執行順序由上至下

    Id列的結果值全部是1,表示由上到下,先查詢game_company表,再查詢game表,最後查詢game_type表

  • id不同:如果是子查詢,id的序號會遞增,id的值越大被執行的優先級越高

    id的結果值分別是123,最裏面的子查詢country值最大優先級最高,最先被執行,接着是子查詢game_company,最後纔是主查詢game。其中select_type的值PRIMARY、SUBQUERY分別表示主查詢和子查詢。

  • id同時存在相同和不相同:id相同爲同一組,組內執行順序由上至下,組之間id值越大被執行的優先級越高

    上面例子中id=1的表<derived2>,game爲一組,id=2的表game_type爲一組,所以回優先查詢表game_type,接着到id=1的組內,由上至下先查詢表<derived>再查詢表game。

    tips:

    • <derived2>是派生表2的意思,後面select_type會講到。
    • form子查詢語句中加LIMIT 2的原因是在MYSQL 5.7後,引入了derived_merge,一種查詢優化技術,作用就是把派生表合併到外部的查詢中,提高數據檢索的效率。舉例來說:“select * from (select * from a) aa where aa.id =1;”,在derived_merge下會被優化成"select * from a where a.id =1;",但是當派生子查詢存在以下操作時該特性無法生效:UNION 、GROUP BY、DISTINCT、LIMIT/OFFSET以及聚合操作。由於上面做實驗的例子比較簡單在derived_merge打開的情況下from子查詢會合併到外層查詢,無法演示效果,因此在子查詢語句中加了limit,當然也可以通過設置屬性"optimizer_switch='derived_merge=off"來關閉derived_merge。

explain輸出列-select_type

  • SIMPLE:簡單的select查詢,查詢中不包含子查詢或者UNION

  • PRIMARY:查詢中若包含任何複雜的子部分,最外層的查詢則被標記爲PRIMARY

    上圖表示,select * game 是主查詢。

  • SUBQUERY:在select或者where列表中包含了子查詢

    where列表中的子查詢select id from game_type where type_name = “RTS”。

    select列表中的子查詢select game_type.type_name from game_type where game_type.id = 1

  • DERIVED:在from列表中包含的子查詢會被標記爲DERICED(衍生),Mysql會遞歸地執行這些子查詢,然後把結果放到臨時表

    上圖第三行結果select_type=DERIVED,表示from列表中的子查詢select * from game_type where game_type.type_name in (“RPG”,“MMORPG”) LIMIT 2會產生臨時表,這張臨時表名稱就是<derived2>,第一行結果所示,其中derived後面的2就是上面第三行結果的id值,表示這是表game_type查詢產生的臨時表。

  • UNION:若第二個select語句出現在UNION之後,則被標記爲UNION。若UNION包含在from子句的子查詢中,外層select則被標記爲DERIVED

    上圖中SQL語句中,union後面的select語句,特意在right join game_company後面加上as company,所以在explain結果中能明顯看出select_type=UNION的table分別是company和game,說明了union後面的select語句會被標記爲UNION。

  • UNION RESULT:從union表獲取結果的select

    union上下的查詢語句會把結果合併到臨時表<union1,2>中,結果會從這張表中返回。

explain輸出列-type

  • system:表只有一行記錄,這是const類型的特例

    上圖所示from列表子查詢只有一行記錄,其派生出的臨時表<derived2>只有一行記錄,因此外層臨時表的select查詢的type值就是system

  • const:表最多有一個匹配行,它將在查詢開始時被讀取

    還是上圖,from列表子查詢的where條件是主鍵=常量,所以肯定只有一行記錄被匹配,所以type的值爲const

    上圖所示第一條SQL語句查詢結果只有一條記錄,但是下面explain的結果顯示type值卻是ref。Mysql優化器發現查詢條件裏面的主鍵或者唯一索引是個常量值,所以判斷肯定最多隻有一行記錄匹配,因此採用type=const的查詢方式,即只需要讀取到匹配的第一行記錄即可返回;而並不是系統已經返回結果了發現只有一行記錄,因而才顯示type=const。所以例子1是const而例子2是ref。在這一點上Mysql是兵馬未動而糧草先行,而並非事後諸葛亮。

  • eq_ref:驅動表和關聯表中的每行進行組合並且僅有一行記錄

    以表game爲驅動表關聯表game_type的時候,表game和表game_type是一對一關係(game_type.id是主鍵),一個遊戲只能是一種遊戲類型,所以表game的每一行記錄都只能和關聯表game_type組合成最多一條記錄,所以在關聯查詢表game_type時,type是eq_ref,表示驅動表每行記錄只需要和關聯表組合到第一行記錄即可。

  • ref:非唯一性索引掃描,返回符合某個索引值的所有記錄,可能會有多條記錄匹配

    單表查詢,表game的type_id字段是普通索引,所以優化器判斷其可能會有多條匹配的記錄,所以type=ref,會讀取所有匹配的記錄,而不會像const一樣碰到第一行記錄匹配便返回。

    聯表查詢,以表game_type爲驅動表的時候,因爲game.type_id不是主鍵也不是唯一索引,所以系統判斷表game_type和表game存在多對一的關係,因此表game_type的每一行記錄都可能和關聯表game組合成多行記錄,所以在關聯查詢表game時,type=ref,表示驅動表每行都會組合匹配到最後。

  • range:使用一個索引來檢索給定範圍的行,這種範圍索引掃描比全表掃描效率要高。

  • index:索引樹掃描

    select列表只包含id,name兩個字段,而所以索引Uk_name就包含這兩列,所以Mysql只需要掃描索引樹既可以找到所需要的所有值,不需要去掃描表。

    tips: 按照Mysql索引最左原則,"name like %爭霸%"是無法使用索引列name的,但這是模糊搜索常常出現的需求,所以我們可以像上面語句一樣通過掃描索引樹找出所需要的Id(最好配合分頁),再通過這些id去查詢相應的行記錄。索引樹存在內存裏面,就是全索引樹掃描也比掃描全表要快的多得多。

  • all:全表掃描

explain輸出列-possiable_keys、key

  • possiable_keys:可能會使用到的索引;key:實際會使用到的索引。

    possiable_keys顯示可能會使用的索引有兩個:uk_name,ik_name_typeId,而key顯示實際用到的索引是uk_name。

    如果查詢用到的是覆蓋索引,並且沒有where查詢條件,key會顯示實際用到的索引,但是possiable_keys不會顯示。

explain輸出列-key_len

  • key_len:實際使用的索引字節長度,可以分爲變長和定長數據類型兩種。當索引字段爲定長數據類型時,如char,int,datetime,需要有是否爲空的標記,這個標記佔用1個字節(對於not null的字段來說,則不需要這1字節);對於變長數據類型,比如varchar,除了是否爲空的標記外,還需要有長度信息,需要佔用兩個字節。

    索引uk_name只有一個索引列name,其中name類型是varchar(255),字節編碼utf-8mb4,因此key_len=255 * 4 + 2 +1=1023。

    索引ik_name_typeId包含兩個索引列type_id(int)和name(varchar(255)),字節編碼utf-8mb4,所以key_len=(255 * 4 + 2 + 1) + (4 + 1) =1028。

    索引同樣使用ik_name_type,但是因爲like條件的值變成了 %魔獸%,因此索引只用了type_id列並且key_len=5。因此key_len的值表示的是索引實際使用的長度,並不是索引本身所有索引列的長度總和。我們使用explain分析語句的時候,就可以通過這個分析實際上用到索引長度是多少。

explain輸出列-ref

  • ref:哪些列或者常量被用做索引列上的值

    type_id=1,1是常量,ref=const

    表game被關聯查詢的時候,使用了ik_typeId索引,索引列是game.type_id,而值則使用了驅動表的game_type.id列。

explain輸出列-rows

  • rows:根據表的統計信息和索引的使用情況,大致估算查詢結果所需要讀取記錄的行數

    全表掃描,返回結果大概需要掃描21行記錄。

    索引範圍掃描,返回結果大概需要讀取2行記錄。

explain輸出列-extra

  • using filesort:使用文件內排序
    在這裏插入圖片描述
    因爲查詢使用了索引ik_typeId,該索引只有type_id一列,因此無法使用索引來完成order by name的排序,需要額外進行外部的排序動作,這一般是不能容忍的,需要優化。
    上圖就是使用了ik_typeId_name索引,能直接利用索引完成order by type_id,name的排序,因此extra輸出列不會出using filesort。

    tips:這裏簡單提一下第一個例子的優化問題
    一、例子1不是索引不完整造成,而是索引順序問題,所以首先可以從程序實現上或者需求上考慮,能不能像例子2一樣去調整sql語句,讓where和order by都用上索引,例子2是調整了order by;同樣也可以調整where,比如type=1,讓type等於一個常量值也可以讓現有的索引同時滿足where和order by。
    二、但是說如果你的程序實現無發調整而且需求上就是要type_id是範圍查詢,order by的第一字段就是name,那你可以考慮一下使用覆蓋索引,先查詢到刷選並且排序後的Id列表(最好配合分頁),再通過id列表去讀取完整的行數據,雖然也是需要額外對索引樹的掃描結果進行排序,因此using filesort還是會存在,但是這些全部都是在內存中完成,還是比讀取源表數據後再排序要快上很多的,尤其是數據量大的時候,主要減少了IO消耗。

  • using tmporary:使用臨時表保存中間結果,常見於排序order by和分組group by。

    從possiable_keys列結果中,我們能發現並沒有包含company_id列的索引,所以where條件使用ik_typeId索引,但是group by並沒有索引可以使用,因此使用了臨時表進行分組。

    這是優化後的結果,從possiable_keys列結果中可以發現可用索引多了一個ik_companyId_type_id,而key列結果中也顯示了實際上也是使用了這個索引,因此group by可以依靠索引進行分組,不再需要使用臨時表。

  • Using index:表示覆蓋索引即可滿足查詢要求,因而無需再回表查詢

  • Using where:表示MySQL將對存儲引擎層提取的結果進行過濾,它表示的是Server層對存儲引擎層返回的數據所做的過濾。

    上面查詢使用了索引ik_companyId_typeId,索引在引擎層只能通過索引完成對company_id=1的刷選,而name like '魔獸%'只能在讀取源表後,在Mysql Server層進行第二次刷選。

  • Using index;Using where:使用了覆蓋索引讀取數據,並且利用索引進行查找。

    這種還是直接使用了覆蓋索引的數據,只是對結果進行了一次過濾,不需要讀取源表因此查詢效率還是非常高的!

  • Using index condition:使用了Index Condition Pushdown (IPC) ,這是Mysql 5.6開始支持的一種根據索引進行查詢的優化方式。其優化支持range、ref、eq_ref、ref_or_null類型的查詢,當前支持MyISAM和InnoDB存儲引擎。

    key列的結果顯示,實際使用的索引是ik_companyId_typeId,Mysql可以根據索引來獲取company_id between 1 and 3的記錄,但是對於type_id < 4卻無能爲力。若不支持IPC,則數據庫需要先通過索引列company_id獲取區間[1,3]中的所有記錄,然後再過濾type_id<4這個條件,如果滿足第二個條件會過濾掉非常多的數據,那樣讀取源表記錄時包含非常多無用數據,影響查詢效率。若支持IPC,滿足第一個where條件的索引在取出的時候,會利用該索引樹,直接進行其他where條件的過濾,最後再利用過濾後的索引去讀取源表的完整記錄。IPC的情形是無法直接通過讀取索引滿足所有where條件,但是索引又包含where的所有條件列,Ik_companyId_typeId有包含了company_id和type_id兩列,所以符合IPC的情形。

    上面圖中SQL語句強行使用了索引Ik_companyId,發現extra的值時Using where。IPC的使用條件情形是,索引掃描無法直接滿足where的所有條件,但是該索引又包含所有where條件列。但是索引ik_companyId缺少type_id列,所以無法滿足IPC的情形,因此會讀取滿足第一個條件的所有記錄,再在Sql層進行where過濾。

  • Using index for group by:數據訪問和 Using index 一樣,所需數據只須要讀取索引,當查詢 中使用GROUP BY或DISTINCT 子句時,如果分組字段也在索引中,Extra中的信息就會是Using index for group-by

  • Using join buffer:

    Block Nested-Loop Join算法:將外層循環的行/結果集存入join buffer, 內層循環的每一行與整個buffer中的記錄做比較,從而減少內層循環的次數。優化器管理參數optimizer_switch中中的block_nested_loop參數控制着BNL是否被用於優化器。默認條件下是開啓,若果設置爲off,優化器在選擇 join方式的時候會選擇NLJ(Nested Loop Join)算法。
    Batched Key Access原理:對於多表join語句,當MySQL使用索引訪問第二個join表的時候,使用一個join buffer來收集第一個操作對象生成的相關列值。BKA構建好key後,批量傳給引擎層做索引查找。key是通過MRR接口提交給引擎的(mrr目的是較正順序)MRR使得查詢更有效率,要使用BKA,必須調整系統參數optimizer_switch的值,batched_key_access設置爲on,因爲BKA使用了MRR,因此也要打開MRR。

  • impossiable where:where的值總fasle,不能獲取任何記錄

  • Select tables optimized away:在沒有group by子句的情況下,基於索引優化的MAX/MIN操作,或者基於MyISAM存儲引擎優化的COUNT(*)操作,不必等到執行階段再進行計算,在查詢計劃生成階段既可以完成優化。

  • Distinct:優化Distinct操作,在找到匹配的第一行記錄後,立馬停止查找同樣的值。

    上面語句查找所有開發過type_id=2的類型遊戲的遊戲公司id,如果表game裏面有兩條company_id和type_id組合一致的記錄,比如暴雪公司開發過兩款RTS遊戲星際爭霸和魔獸爭霸,那對於內層關聯查詢,只需要匹配到第一條記錄便可以停止查詢這個組合。

參考博客

MySQL中explain執行計劃中額外信息字段(Extra)詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章