索引優化的這把絕世好劍,你真的會用嗎?

前言

對於互聯網公司來說,隨着用戶量和數據量的不斷增加,慢查詢是無法避免的問題。一般情況下如果出現慢查詢,意味着接口響應慢、接口超時等問題。如果是高併發的場景,可能會出現數據庫連接被佔滿的情況,直接導致服務不可用。

慢查詢的確會導致很多問題,我們要如何優化慢查詢呢?

主要解決辦法有:

  • 監控sql執行情況,發郵件、短信報警,便於快速識別慢查詢sql

  • 打開數據庫慢查詢日誌功能

  • 簡化業務邏輯

  • 代碼重構、優化

  • 異步處理

  • sql優化

  • 索引優化

其他的辦法先不說,後面有機會再單獨介紹。今天我重點說說索引優化,因爲它是解決慢查詢sql問題最有效的手段。

如何查看某條sql的索引執行情況呢?

沒錯,在sql前面加上 explain 關鍵字,就能夠看到它的執行計劃,通過執行計劃,我們可以清楚的看到表和索引執行的情況,索引有沒有執行、索引執行順序和索引的類型等。

索引優化的步驟是:

  1. 使用 explain 查看sql執行計劃

  2. 判斷哪些索引使用不當

  3. 優化sql,sql可能需要多次優化才能達到索引使用的最優值

既然索引優化的第一步是使用 explain ,我們先全面的瞭解一下它。

explain介紹

先看看mysql的官方文檔是怎麼描述explain的:

  • EXPLAIN可以使用於 SELECT, DELETE, INSERT, REPLACE,和 UPDATE語句。

  • 當EXPLAIN與可解釋的語句一起使用時,MySQL將顯示來自優化器的有關語句執行計劃的信息。也就是說,MySQL解釋了它將如何處理該語句,包括有關如何連接表以及以何種順序連接表的信息。

  • 當EXPLAIN與非可解釋的語句一起使用時,它將顯示在命名連接中執行的語句的執行計劃。

  • 對於SELECT語句, EXPLAIN可以顯示的其他執行計劃的警告信息。

explain詳解

explain的語法:

{EXPLAIN | DESCRIBE | DESC}
    tbl_name [col_name | wild]

{EXPLAIN | DESCRIBE | DESC}
    [explain_type]
    {explainable_stmt | FOR CONNECTION connection_id}

explain_type: {
    EXTENDED
  | PARTITIONS
  | FORMAT = format_name
}

format_name: {
    TRADITIONAL
  | JSON
}

explainable_stmt: {
    SELECT statement
  | DELETE statement
  | INSERT statement
  | REPLACE statement
  | UPDATE statement
}

用一條簡單的sql看看使用 explain 關鍵字的效果:

explain  select * from test1;

執行結果:從上圖中看到執行結果中會顯示12列信息,每列具體信息如下:

說白了,我們要搞懂這些列的具體含義才能正常判斷索引的使用情況。

話不多說,直接開始介紹吧。

id列

該列的值是select查詢中的序號,比如:1、2、3、4等,它決定了表的執行順序。

某條sql的執行計劃中一般會出現三種情況:

  1. id相同

  2. id不同

  3. id相同和不同都有

那麼這三種情況表的執行順序是怎麼樣的呢?

1.id相同

執行sql如下:

explain  select * from test1 t1 inner  join test1 t2 on t1.id=t2.id

結果:我們看到執行結果中的兩條數據id都是1,是相同的。

這種情況表的執行順序是怎麼樣的呢?

答案:從上到下執行,先執行表t1,再執行表t2。

執行的表要怎麼看呢?

答案:看table字段,這個字段後面會詳細解釋。

2.id不同

執行sql如下:

explain  select * from test1 t1 where t1.id = (select  id  from  test1 t2 where  t2.id=2);

結果:我們看到執行結果中兩條數據的id不同,第一條數據是1,第二條數據是2。

這種情況表的執行順序是怎麼樣的呢?

答案:序號大的先執行,這裏會從下到上執行,先執行表t2,再執行表t1。

3.id相同和不同都有

執行sql如下:

explain 
select t1.* from test1 t1
inner  join (select  max(id) mid  from test1 group  by  id) t2
on t1.id=t2.mid

結果:

我們看到執行結果中三條數據,前面兩條數據的的id相同,第三條數據的id跟前面的不同。

這種情況表的執行順序又是怎麼樣的呢?

答案:先執行序號大的,先從下而上執行。遇到序號相同時,再從上而下執行。所以這個列子中表的順序順序是:test1、t1、

也許你會在這裏心生疑問: < derived2> 是什麼鬼?

它表示派生表,別急後面會講的。

還有一個問題:id列的值允許爲空嗎?

答案在後面揭曉。

select_type列

該列表示select的類型。具體包含了如下11種類型:

但是常用的其實就是下面幾個:

類型 含義
SIMPLE 簡單SELECT查詢,不包含子查詢和UNION
PRIMARY 複雜查詢中的最外層查詢,表示主要的查詢
SUBQUERY SELECT或WHERE列表中包含了子查詢
DERIVED FROM列表中包含的子查詢,即衍生
UNION UNION關鍵字之後的查詢
UNION RESULT 從UNION 後 的表獲取結果集

下面看看這些SELECT類型具體是怎麼出現的:

  1. SIMPLE

    執行sql如下:

explain select * from test1;

結果:它只在簡單SELECT查詢中出現,不包含子查詢和UNION ,這種類型比較直觀就不多說了。

  1. PRIMARY 和 SUBQUERY

    執行sql如下:

結果:我們看到這條嵌套查詢的sql中,最外層的t1表是 PRIMARY 類型,而最裏面的子查詢t2表是SUBQUERY類型。

explain select * from test1 t1 where t1.id = (select id from  test1 t2 where  t2.id=2);
  1. DERIVED

    執行sql如下:

explain
select t1.* from test1 t1
inner join (select max(id) mid from test1 group by id) t2
on t1.id=t2.mid

結果:

最後一條記錄就是衍生表,它一般是 FROM列表中包含的子查詢 ,這裏是sql中的分組子查詢。

  1. UNION 和 UNION RESULT

    執行sql如下:

explain
select * from test1
union
select* from test2

結果:

test2表是UNION關鍵字之後的查詢,所以被標記爲 U N ION ,test1是最主要的表,被標記爲PRIMARY。而<union1,2>表示id=1和id=2的表union,其結果被標記爲UNION RESULT。

UNION 和 UNION RESULT 一般會成對出現。

此外,回答上面的問題: id列的值允許爲空嗎?

如果仔細看上面那張圖,會發現id列是可以允許爲空的,並且是在SELECT 類型 爲: UNION RESULT 的時候。

table列

該列的值表示輸出行所引用的表的名稱,比如前面的:test1、test2等。

但也可以是以下值之一:

  • <unionM,N> :具有和id值的行的M並集N。

  • <derivedN> :用於與該行的派生表結果id的值N。派生表可能來自(例如)FROM子句中的子查詢 。

  • <subqueryN> :子查詢的結果,其id值爲N

partitions列

該列的值表示查詢將從中匹配記錄的分區

type列

該列的值表示連接類型,是查看索引執行情況的一個重要指標。包含如下類型:

執行結果從最好到最壞的的順序是從上到下。

我們需要重點掌握的是下面幾種類型:

system > const > eq_ref > ref > range > index > ALL

在演示之前,先說明一下test2表中只有一條數據:

並且code字段上面建了一個普通索引:

下面逐一看看常見的幾個連接類型是怎麼出現的:

  1. system

    這種類型要求數據庫表中只有一條數據,是const類型的一個特例,一般情況下是不會出現的。

  2. const

    通過一次索引就能找到數據,一般用於主鍵或唯一索引作爲條件的查詢sql中,執行sql如下:

explain select * from test2 where id=1;

結果:

  1. eq_ref

    常用於主鍵或唯一索引掃描。執行sql如下:

explain select * from test2 t1 inner join test2 t2 on t1.id=t2.id;

結果:
此時,有人可能感到不解,const和eq_ref都是對主鍵或唯一索引的掃描,有什麼區別?

答:const只索引一次,而eq_ref主鍵和主鍵匹配,由於表中有多條數據,一般情況下要索引多次,才能全部匹配上。

  1. ref

    常用於非主鍵和唯一索引掃描。 執行sql如下:

explain select * from test2 where code = '001';

結果:

  1. range

    常用於範圍查詢,比如:between ... and 或 In 等操作, 執行sql如下:

explain select * from test2 where id between 1 and 2;

結果:

  1. index

    全索引掃描。執行sql如下 :

explain select code from test2;

結果:

  1. ALL

    全表掃描。執行sql如下 :

explain select *  from test2;

結果:

possible_keys列

該列表示可能的索引選擇。

請注意,此列完全獨立於表的順序,這就意味着possible_keys在實踐中,某些鍵可能無法與生成的表順序一起使用。

如果此列是NULL,則沒有相關的索引。在這種情況下,您可以通過檢查該WHERE 子句以檢查它是否引用了某些適合索引的列,從而提高查詢性能。

key列

該列表示實際用到的索引。

可能會出現 possible_keys列爲NULL,但是key不爲NULL的情況。

演示之前,先看看test1表結構:

test1表中數據:

使用的索引:

code和name字段使用了聯合索引。

執行sql如下 :

explain select code  from test1;

結果:

這條sql預計沒有使用索引,但是實際上使用了全索引掃描方式的索引。

key_len列

該列表示使用索引的長度。上面的key列可以看出有沒有使用索引,key_len列則可以更進一步看出索引使用是否充分。不出意外的話,它是最重要的列。

有個關鍵的問題浮出水面:key_len是如何計算的?

決定 key_len值的 三個因素 :

1.字符集

2.長度

3.是否爲空

常用的字符編碼佔用字節數量如下:

目前我的數據庫字符編碼格式用的:UTF8佔3個字節。

mysql常用字段佔用字節數:

字段類型 佔用字節數
char(n) n
varchar(n) n + 2
tinyint 1
smallint 2
int 4
bigint 8
date 3
timestamp 4
datetime 8

此外,如果字段類型允許爲空則加1個字節。

上圖中的 184是怎麼算的?

184 = 30 * 3 + 2 + 30 * 3 + 2

再把test1表的code字段類型改成char,並且改成允許爲空:

執行sql如下 :

explain select code  from test1;

怎麼算的?

183 = 30 * 3 + 1 + 30 * 3 + 2

還有一個問題:爲什麼這列表示索引使用是否充分呢,還有使用不充分的情況?

執行sql如下 :

explain select code  from test1 where code='001';

結果:

上圖中使用了聯合索引:idx_code_name,如果索引全匹配key_len應該是183,但實際上卻是92,這就說明沒有使用所有的索引,索引使用不充分。

ref列

該列表示索引命中的列或者常量。

執行sql如下 :

explain select *  from test1 t1 inner join test1 t2 on t1.id=t2.id where t1.code='001';

結果:

我們看到表t1命中的索引是const(常量),而t2命中的索引是列sue庫的t1表的id字段。

rows列

該列表示MySQL認爲執行查詢必須檢查的行數。

對於InnoDB表,此數字是估計值,可能並不總是準確的。

filtered列

該列表示按表條件過濾的錶行的估計百分比。最大值爲100,這表示未過濾行。值從100減小表示過濾量增加。

rows顯示了檢查的估計行數,rows× filtered顯示了與下表連接的行數。例如,如果 rows爲1000且 filtered爲50.00(50%),則與下表連接的行數爲1000×50%= 500。

Extra列

該字段包含有關MySQL如何解析查詢的其他信息,這列還是挺重要的,但是裏面包含的值太多,就不一一介紹了,只列舉幾個常見的。

  1. Impossible WHERE

    表示WHERE後面的條件一直都是false,

    執行sql如下 :

explain select code  from test1 where 'a' = 'b';

結果:

  1. Using filesort

    表示按文件排序,一般是在指定的排序和索引排序不一致的情況纔會出現。

    執行sql如下 :

explain select code  from test1 order by name desc;

結果:

這裏建立的是code和name的聯合索引,順序是code在前,name在後,這裏直接按name降序,跟之前聯合索引的順序不一樣。

  1. Using index

    表示是否用了覆蓋索引,說白了它表示是否所有獲取的列都走了索引。

    上面那個例子中其實就用到了:Using index,因爲只返回一列code,它字段走了索引。

  2. Using temporary

    表示是否使用了臨時表,一般多見於order by 和 group by語句。

    執行sql如下 :

explain select name  from test1 group by name;

結果:

  1. Using where

    表示使用了where條件過濾。

  2. Using join buffer

表示是否使用連接緩衝。 來自較早聯接的表被部分讀取到聯接緩衝區中,然後從緩衝區中使用它們的行來與當前表執行聯接。

索引優化的過程

1.先用慢查詢日誌定位具體需要優化的sql

2.使用explain執行計劃查看索引使用情況

3.重點關注:

key(查看有沒有使用索引)

key_len(查看索引使用是否充分)

type(查看索引類型)

Extra(查看附加信息:排序、臨時表、where條件爲false等)

一般情況下根據這4列就能找到索引問題。

4.根據上1步找出的索引問題優化sql

5.再回到第2步

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