1. 程式人生 > >讀《MySQL效能調優與架構設計》筆記之充分利用 Explain和Profiling

讀《MySQL效能調優與架構設計》筆記之充分利用 Explain和Profiling

1.1. Explain 的使用

    我們先看一下在MySQL Explain 功能中給我們展示的各種資訊的解釋:

    ◆ ID:MySQL Query Optimizer選定的執行計劃中查詢的序列號。表示查詢中執行select子句或操作表的順序,id值越大優先順序越高,越先被執行。id相同,執行順序由上至下。

    ◆ Select_type:所使用的查詢型別,主要有以下這幾種查詢型別

        ◇DEPENDENT SUBQUERY:子查詢中內層的第一個SELECT,依賴於外部查詢的結果集;

Dependent Subquery意味著什麼


它的執行計劃如下,請注意看關鍵詞“DEPENDENT SUBQUERY”:


官方含義為:

SUBQUERY:子查詢中的第一個SELECT;

DEPENDENT SUBQUERY:子查詢中的第一個SELECT,取決於外面的查詢 。

換句話說,就是 子查詢對 g2 的查詢方式依賴於外層 g1 的查詢。

什麼意思呢?它意味著兩步:

第一步,MySQL 根據 select gid,count(id) from shop_goods where status=0 group by gid; 得到一個大結果集 t1,其資料量就是上圖中的 rows=850672 了。

第二步,上面的大結果集 t1 中的每一條記錄,都將與子查詢 SQL 組成新的查詢語句:

select gid from

shop_goods where sid in (15...blabla..29) and gid=%t1.gid%。

等於說,子查詢要執行85萬次……即使這兩步查詢都用到了索引,但不慢才怪。

如此一來,子查詢的執行效率居然受制於外層查詢的記錄數,那還不如拆成兩個獨立查詢順序執行呢。

你不想拆成兩個獨立查詢的話,也可以與臨時表聯表查詢,如下所示:


也能得到同樣的結果,且是毫秒級。

它的執行計劃為:


DERIVED 的官方含義為:

DERIVED:用於 from 子句裡有子查詢的情況。MySQL 會遞迴執行這些子查詢,把結果放在臨時表裡。

mysql 在處理子查詢時,會改寫子查詢。

通常情況下,我們希望由內到外,先完成子查詢的結果,然後再用子查詢來驅動外查詢的表,完成查詢。

例如:

select * from test where tid in(select fk_tid from sub_test where gid=10)

通常我們會感性地認為該 sql 的執行順序是:

sub_test 表中根據 gid 取得 fk_tid(2,3,4,5,6)記錄,

然後再到 test 中,帶入 tid=2,3,4,5,6,取得查詢資料。

但是實際mysql的處理方式為:

select * from test where exists (

select * from sub_test where gid=10 and sub_test.fk_tid=test.tid

)

mysql 將會掃描 test 中所有資料,每條資料都將會傳到子查詢中與 sub_test 關聯,子查詢不會先被執行,所以如果 test 表很大的話,那麼效能上將會出現問題。

        ◇ DEPENDENT UNION:子查詢中的UNION,且為UNION中從第二個                   SELECT 開始的後面所有SELECT,同樣依賴於外部查詢的結果集;

        ◇ PRIMARY:子查詢中的最外層查詢,注意並不是主鍵查詢;

        ◇ SIMPLE:除子查詢或者UNION之外的其他查詢;

        ◇ SUBQUERY:子查詢內層查詢的第一個SELECT,結果不依賴於外部查詢結果集;

        ◇ UNCACHEABLE SUBQUERY:結果集無法快取的子查詢;

        ◇ UNION:UNION語句中第二個SELECT 開始的後面所有SELECT,第一個SELECT 為PRIMARY

        ◇ UNION RESULT:UNION中的合併結果;

    ◆ Table:顯示這一步所訪問的資料庫中的表的名稱;

    ◆ Type:告訴我們對錶所使用的訪問方式,主要包含如下集中型別;

        ◇ all:全表掃描

        ◇ const:讀常量,且最多隻會有一條記錄匹配,由於是常量,所以實際上只需要讀一次;

        ◇ eq_ref:最多隻會有一條匹配結果,一般是通過主鍵或者唯一鍵索引來訪問;

        ◇ fulltext:

        ◇ index:全索引掃描;

        ◇ index_merge:查詢中同時使用兩個(或更多)索引,然後對索引結果進行merge 之後再讀取表資料;

        ◇ index_subquery:子查詢中的返回結果欄位組合是一個索引(或索引組合),但不是一個主鍵或者唯一索引;

        ◇ rang:索引範圍掃描;

        ◇ ref:Join語句中被驅動表索引引用查詢;

        ◇ ref_or_null:與ref的唯一區別就是在使用索引引用查詢之外再增加一個空值的查詢;

        ◇ system:系統表,表中只有一行資料;

        ◇ unique_subquery:子查詢中的返回結果欄位組合是主鍵或者唯一約束;

    ◆ Possible_keys:該查詢可以利用的索引. 如果沒有任何索引可以使用,就會顯示成null,這一項內容對於優化時候索引的調整非常重要;

    ◆ Key:MySQLQuery Optimizer 從possible_keys 中所選擇使用的索引;

    ◆ Key_len:被選中使用索引的索引鍵長度;

    ◆ Ref:列出是通過常量(const),還是某個表的某個欄位(如果是join)來過濾(通過key)的;

    ◆ Rows:MySQLQuery Optimizer 通過系統收集到的統計資訊估算出來的結果集記錄條數;

    ◆ Extra:查詢中每一步實現的額外細節資訊,主要可能會是以下內容:

        ◇ Distinct:查詢distinct值,所以當mysql 找到了第一條匹配的結果後,將停止該值的查詢而轉為後面其他值的查詢;

        ◇ Full scan on NULL key:子查詢中的一種優化方式,主要在遇到無法通過索引訪問null值的使用使用;

        ◇ Impossible WHERE noticedafter reading const tables:MySQL Query Optimizer 通過收集到的統計資訊判斷出不可能存在結果;

        ◇ No tables:Query語句中使用FROM DUAL 或者不包含任何FROM 子句;

        ◇ Not exists:在某些左連線中MySQLQuery Optimizer 所通過改變原有Query 的組成而使用的優化方法,可以部分減少資料訪問次數;

        ◇ Range checked for eachrecord (index map: N):通過MySQL 官方手冊的描述,當MySQL Query Optimizer 沒有發現好的可以使用的索引的時候,如果發現如果來自前面的表的列值已知,可能部分索引可以使用。對前面的表的每個行組合,MySQL 檢查是否可以使用range 或index_merge 訪問方法來索取行。

        ◇ Select tables optimizedaway:當我們使用某些聚合函式來訪問存在索引的某個欄位的時候,MySQL Query Optimizer 會通過索引而直接一次定位到所需的資料行完成整個查詢。當然,前提是在Query 中不能有GROUP BY 操作。如使用MIN()或者MAX()的時候;

        ◇ Using filesort:當我們的Query中包含ORDER BY 操作,而且無法利用索引完成排序操作的時候,MySQL Query Optimizer 不得不選擇相應的排序演算法來實現。

        ◇ Using index:所需要的資料只需要在Index即可全部獲得而不需要再到表中取資料;

        ◇ Using index for group-by:資料訪問和Usingindex 一樣,所需資料只需要讀取索引即可,而當Query 中使用了GROUP BY 或者DISTINCT 子句的時候,如果分組欄位也在索引中,Extra 中的資訊就會是Using index for group-by;

        ◇ Using temporary:當MySQL在某些操作中必須使用臨時表的時候,在Extra 資訊中就會出現Using temporary 。主要常見於GROUP BY 和ORDER BY 等操作中。

        ◇ Using where:如果我們不是讀取表的所有資料,或者不是僅僅通過索引就可以獲取所有需要的資料,則會出現Using where 資訊;

        ◇ Using where with pushedcondition:這是一個僅僅在NDBCluster 儲存引擎中才會出現的資訊,而且還需要通過開啟Condition Pushdown 優化功能才可能會被使用。控制引數為engine_condition_pushdown 。

    這裡我們通過分析示例來看一下不同的Query 語句通過Explain 所顯示的不同資訊:

    我們先看一個簡單的單表Query:

    > explain select count(*),max(id),min(id) from user\G

********************1. row ***************************

id:              1

select_type:   SIMPLE

table:          NULL

type:            NULL

possible_keys:NULL

key:             NULL

key_len:        NULL

ref:             NULL

rows:           NULL

Extra:          Select tables optimized away

    對user 表的單表查詢,查詢型別為SIMPLE,因為既沒有UNION 也不是子查詢。聚合函式MAX MIN以及COUNT 三者所需要的資料都可以通過索引就能夠直接定位得到資料,所以整個實現的Extra 資訊為Select tables optimized away。

    再來看一個稍微複雜一點的Query,一個子查詢:

> explain select name from groups where id in

( select group_id from user_group whereuser_id = 1)\G

*********************** 1.row *************************

id:               1

select_type:    PRIMARY

table:           groups

type:            ALL

possible_keys:NULL

key:             NULL

key_len:        NULL

ref:             NULL

rows:            50000

Extra:           Using where

************************2. row *************************

id:              2

select_type:   DEPENDENT SUBQUERY

table:          user_group

type:           ref

possible_keys:user_group_gid_ind,user_group_uid_ind

key:            user_group_uid_ind

key_len:       4

ref:            const

rows:           1

Extra:          Using where

    通過id 資訊我們可以得知MySQL Query Optimizer 給出的執行計劃是首先對groups進行全表掃描,然後第二步才訪問user_group 表,所使用的查詢方式是DEPENDENT SUBQUERY,對所需資料的訪問方式是索引掃描,由於過濾條件是一個整數,所以索引掃描的型別為ref,過濾條件是const。可以使

用的索引有兩個,一個是基於user_id,另一個則是基於group_id 的。為什麼基於group_id 的索引user_group_gid_ind 也被列為可選索引了呢?是因為與子查詢的外層查詢所關聯的條件是基於group_id 的。當然,最後MySQL Query Optimizer 還是選擇了使用基於user_id的索引user_group_uid_ind。

1.2. Profiling 的使用

    要想優化一條Query,我們就需要清楚的知道這條Query 的效能瓶頸到底在哪裡,是消耗的CPU計算太多,還是需要的的IO 操作太多?要想能夠清楚的瞭解這些資訊,通過Query Profiler 功能。

    MySQL 的Query Profiler 是一個使用非常方便的Query 診斷分析工具,通過該工具可以獲取一條Query 在整個執行過程中多種資源的消耗情況,如CPU,IO,IPC,SWAP 等,以及發生的PAGE FAULTS,CONTEXT SWITCHE 等等,同時還能得到該Query 執行過程中MySQL 所呼叫的各個函式在原始檔中的位置。下面我們看看Query Profiler 的具體用法。

    1、開啟profiling 引數

> set profiling=1;

Query OK, 0 rows affected (0.00 sec)

    通過執行“set profiling”命令,可以開啟關閉Query Profiler 功能。

    2、執行Query

      ... ...

    > select status,count(*) from test_profiling groupbystatus;


5 rows in set (1.11 sec)

... ...

    在開啟Query Profiler 功能之後,MySQL 就會自動記錄所有執行的Query 的profile 資訊了。

    3、獲取系統中儲存的所有Query 的profile 概要資訊

    >show profiles;


3 rows in set(0.00 sec)

    通過執行“SHOW PROFILE” 命令獲取當前系統中儲存的多個Query 的profile 的概要資訊。

    4、針對單個Query 獲取詳細的profile 資訊。

    在獲取到概要資訊之後,我們就可以根據概要資訊中的Query_ID 來獲取某個Query 在執行過程中

    詳細的profile 資訊了,具體操作如下:

    > show profile cpu, block io for query6;


    上面的例子中是獲取CPU 和Block IO 的消耗,非常清晰,對於定位效能瓶頸非常適用。希望得到取其他的資訊,都可以通過執行“SHOW PROFILE *** FOR QUERY n” 來獲取。