1. 程式人生 > 其它 >慢 SQL

慢 SQL

1. 慢 SQL 的危害

2. SQL 語句的執行過程

3. 儲存引擎和索引的那些事兒

  • 3.1 儲存引擎
  • 3.2 索引

4. 慢 SQL 解決之道

  • 4.1 索引設計策略
  • 4.2 SQL 優化
  • 4.3 慢 SQL 的分析

1. 慢 SQL 的危害

慢 SQL,就是跑得很慢的 SQL 語句,你可能會問慢 SQL 會有啥問題嗎?

試想一個場景:

大白和小黑端午出去玩,機票太貴於是買了高鐵,火車站的人真是烏央烏央的。

馬上檢票了,大白和小黑準備去廁所清理下庫存,坑位不多,排隊的人還真不少。

小黑髮現其中有 3 個坑的乘客賊慢,其他 2 個坑位換了好幾波人,這 3 位坑主就是不出來。

等在外面的大夥,心裡很是不爽,長期佔用公共資源,後面的人沒法用。

小黑苦笑道:這不就是廁所版的慢 SQL 嘛!

這是實際生活中的例子,換到 MySQL 伺服器也是一樣的,畢竟科技源自生活嘛。

MySQL 伺服器的資源(CPU、IO、記憶體等)是有限的,尤其在高併發場景下需要快速處理掉請求,否則一旦出現慢 SQL 就會阻塞掉很多正常的請求,造成大面積的失敗/超時等。

2. SQL 語句的執行過程

客戶端和 MySQL 服務端的互動過程簡介:

  1. 客戶端傳送一條 SQL 語句給服務端,服務端的聯結器先進行賬號/密碼、許可權等環節驗證,若有異常則直接拒絕請求。
  2. 服務端查詢快取,如果 SQL 語句命中了快取,則返回快取中的結果,否則繼續處理。
  3. 服務端對 SQL 語句進行詞法解析、語法解析、預處理來檢查 SQL 語句的合法性。
  4. 服務端通過優化器對之前生成的解析樹進行優化處理,生成最優的物理執行計劃。
  5. 將生成的物理執行計劃呼叫儲存引擎的相關介面,進行資料查詢和處理。
  6. 處理完成後將結果返回客戶端。

客戶端和 MySQL 服務端的互動過程簡圖:

俗話說"條條大路通羅馬",優化器的作用就是找到這麼多路中最優的那一條。

儲存引擎更是決定SQL執行的核心元件,適當瞭解其中原理十分有益。

3. 儲存引擎和索引的那些事兒

3.1 儲存引擎

InnoDB 儲存引擎(Storage Engine)是 MySQL 預設之選,所以非常典型。

儲引擎的主要作用是進行資料的存取和檢索,也是真正執行 SQL 語句的元件。

InnoDB 的整體架構分為兩個部分:記憶體架構和磁碟架構,如圖:

儲存引擎的內容非常多,並不是一篇文章能說清楚的,本文不過多展開,我們在此只需要瞭解記憶體架構和磁碟架構的大致組成即可。

InnoDB 引擎是面向行儲存的,資料都是儲存在磁碟的資料頁中,資料頁裡面按照固定的行格式儲存著每一行資料。

行格式主要分為四種類型:Compact、Redundant、Dynamic 和 Compressed,預設為 Compact 格式。

磁碟預讀機制和區域性性原理

當計算機訪問一個數據時,不僅會載入當前資料所在的資料頁,還會將當前資料頁相鄰的資料頁一同載入到記憶體,磁碟預讀的長度一般為頁的整倍數,從而有效降低磁碟 I/O 的次數。

磁碟和記憶體的互動

MySQL 中磁碟的資料需要被交換到記憶體,才能完成一次 SQL 互動,大致如圖:

  • 扇區是硬碟的讀寫的基本單位,通常情況下每個扇區的大小是 512B。
  • 磁碟塊是作業系統(檔案系統)讀寫資料的最小單位,相鄰的扇區組合在一起形成一個塊,一般是 4KB。
  • 是記憶體的最小儲存單位,頁的大小通常為磁碟塊大小的 2n 倍。
  • InnoDB 頁面的預設大小是 16KB,是數倍個作業系統的頁。

隨機磁碟 I/O

MySQL 的資料是一行行儲存在磁碟上的,並且這些資料並非物理連續地儲存,這樣的話要查詢資料就無法避免隨機在磁碟上讀取和寫入資料。

對於 MySQL 來說,當出現大量磁碟隨機 I/O 時,大部分時間都被浪費到尋道上,磁碟呼嚕呼嚕轉,就是傳輸不了多少資料。

一次磁碟訪問由三個動作組成:

  • 尋道(Seek Time):磁頭移動定位到指定磁軌。
  • 旋轉(Rotational Latency):等待指定扇區從磁頭下旋轉經過。
  • 資料傳輸(Transfer Time):資料在磁碟與記憶體之間的實際傳輸。

對於儲存引擎來說,如何有效降低隨機 I/O 是個非常重要的問題。

3.2 索引

詳見《MySQL 索引》

4. 慢 SQL 解決之道

出現慢 SQL 的原因很多,我們拋開單表數億記錄和無索引的特殊情況,來討論一些更有普遍意義的慢 SQL 原因和解決之道。

我們從兩個方面來進行闡述:

  • 資料庫表索引設定不合理
  • SQL 語句有問題,需要優化

4.1 索引設計策略

詳見《MySQL 索引》

4.2 SQL 優化

即使資料庫表的索引設定已經比較合理,但 SQL 語句書寫不當的話,也會造成索引失效,甚至造成全表掃描,從而拉低效能。

開啟查詢快取

大多數的 MySQL 伺服器都開啟了查詢快取。這是提高性最有效的方法之一,而且這是被 MySQL 的資料庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個快取中,這樣,後續的相同的查詢就不用操作表而直接訪問快取結果了。

這裡最主要的問題是,對於程式設計師來說,這個事情是很容易被忽略的。因為我們的某些查詢語句會讓 MySQL 不使用快取。請看下面的示例:

SELECT username FROM user WHERE signup_date>= CURDATE();  -- 不走快取

SELECT username FROM user WHERE signup_date>= '2014-06-24';  -- 走快取

上面兩條 SQL 語句的差別就是 CURDATE() ,MySQL 的查詢快取對這個函式不起作用。所以,像 NOW() 和 RAND() 或是其它的諸如此類的 SQL 函式都不會開啟查詢快取,因為這些函式的返回是不確定的。

使用連線查詢代替子查詢

對於資料庫來說,在絕大部分情況下,連線會比子查詢更快,使用連線的方式,MySQL 優化器一般可以生成更佳的執行計劃,更高效地處理查詢。

而子查詢往往需要執行重複的查詢,子查詢生成的臨時表上也沒有索引, 因此效率會更低。

LIMIT 偏移量過大的優化

禁止分頁查詢偏移量過大,如 limit 100000,10

當只要一行資料時使用 LIMIT 1

加上 LIMIT 1 可以增加效能。MySQL 資料庫引擎會在找到一條資料後停止搜尋,而不是繼續往後查下一條符合記錄的資料(否則即使已經查到結果,也會查完全部資料再返回結果)。

多表關聯查詢時,小表在前,大表在後

在 MySQL 中,執行 from 後的表關聯查詢是從左往右執行的,第一張表會涉及到全表掃描,所以將小表放在前面,先掃小表,掃描快效率較高,在掃描後面的大表,或許只掃描大表的前 100 行就符合返回條件並 return 了。

調整 where 子句中的連線順序

MySQL 採用從左往右的順序解析 where 子句,可以將過濾資料多的條件放在前面,最快速度縮小結果集。

不要使用 ORDER BY RAND()

想打亂返回的資料行?隨機挑一個數據?但你卻不瞭解這樣做有多麼可怕的效能問題。

如果你真的想把返回的資料行打亂了,你有 N 種方法可以達到這個目的。而這樣使用只讓你的資料庫的效能呈指數級的下降。這裡的問題是:MySQL會不得不去執行 RAND() 函式(很耗 CPU),而且這是為每一行記錄去記行(掃全表),然後再對其排序,就算是用了 limit 1 也無濟於事(因為要排序)。

優化 GROUP BY 語句

使用 GROUP BY 但要避免排序結果的消耗。

GROUP BYORDER BY NULL;  -- 禁止排序

JOIN 查詢

如果你的應用程式有很多 JOIN 查詢,你應該確認兩個表中 JOIN 的欄位是被建過索引的。這樣,MySQL 內部會啟動為你優化 JOIN 語句的機制。

而且,這些被用來 JOIN 的欄位,應是相同型別的。例如:如果你要把 DECIMAL 欄位和一個 INT 欄位 JOIN 在一起,MySQL 就無法使用它們的索引。對於 STRING 型別,還需要有相同的字符集才行(兩個表的字符集有可能不一樣)。

SELECT company_name FROM users
LEFT JOIN companies ON users.state = companies.state
WHERE users.id = ...

例如以上兩個 state 欄位應該是被建過索引的,而且應是相當型別、相同字符集的。

4.3 慢 SQL 的分析

慢查詢相關設定

在分析慢 SQL 之前需要進行相關設定:

  • 開啟慢 SQL 日誌
  • 設定慢 SQL 的執行時間閾值
-- 開啟慢查詢日誌
SET GLOBAL slow_query_log = 1;

-- 檢視開啟狀態
SHOW VARIABLES LIKE '%slow_query_log%';

-- 設定慢查詢(時間)閾值
SET GLOBAL long_query_time=3;

-- 檢視閾值
SHOW GLOBAL VARIABLES LIKE 'long_query_time%'; 

explain 分析 SQL

在 select 語句之前增加 explain 關鍵字,MySQL 會在查詢上設定一個標記,執行查詢時,會返回執行計劃的資訊,而不是執行這條 SQL。

mysql> explain select * from actor;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | actor | ALL  | NULL          | NULL | NULL    | NULL |    2 | NULL  |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+

該命令會展示 SQL 語句的詳細執行過程,幫助我們定位問題,如檢視該 SQL 語句有沒有使用上了索引,有沒有做全表掃描等,這些都可以通過 explain 命令來檢視。

(MySQL 5)expain 出來的資訊有 10 列,概要描述如下:

  1. id:選擇識別符號。
  2. select_type:表示查詢的型別。
  3. table:輸出結果集的表。
  4. partitions(MySQL 8 新增):如果查詢是基於分割槽表的話,會顯示查詢將訪問的分割槽。
  5. type:表示表的連線型別。
  6. possible_keys:查詢時可能使用的索引。
  7. key:實際使用的索引。
  8. key_len:索引欄位的長度。
  9. ref:列與索引的比較。
  10. rows:掃描出的行數(估算的行數)。
  11. filtered(MySQL 8 新增):按表條件過濾的行百分比。rows * filtered/100 可以估算出將要和 explain 中前一個表進行連線的行數(前一個表指 explain 中的 id 值比當前表 id 值小的表)。
  12. Extra:執行情況的描述和說明。

explain 之後可以通過 show warnings 命令得到優化後的查詢語句,從而看出優化器優化了什麼。

mysql> explain extended select * from film where id = 1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | film  | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+----------+-------+

mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------+
| Level | Code | Message                                                                        |
+-------+------+--------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select '1' AS `id`,'film1' AS `name` from `test`.`film` where 1 |
+-------+------+--------------------------------------------------------------------------------+

詳解

1)id

id 列的編號是 select 的序列號,有幾個 select 就有幾個 id。

  • MySQL 將 select 查詢分為簡單查詢和複雜查詢。複雜查詢又分為三類:簡單子查詢、派生表(from 語句中的子查詢)、union 查詢。
  • id 值可能為 NULL,表示這一行是其他行的聯合結果。

id 值按倒序執行:

  • id 如果相同,可以認為是一組,從上往下順序執行;
  • 如果是子查詢會有遞增的多個 id 值,id 值越大優先順序越高,越先被執行。

簡單子查詢:

mysql> explain select (select 1 from actor limit 1) from film;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
|  1 | PRIMARY     | film  | index | NULL          | idx_name | 32      | NULL |    1 | Using index |
|  2 | SUBQUERY    | actor | index | NULL          | PRIMARY  | 4       | NULL |    2 | Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+ 

from 子句中的子查詢:

mysql> explain select id from (select id from film) as der;
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table      | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+
|  1 | PRIMARY     | <derived2> | ALL   | NULL          | NULL     | NULL    | NULL |    2 | NULL        |
|  2 | DERIVED     | film       | index | NULL          | idx_name | 32      | NULL |    1 | Using index |
+----+-------------+------------+-------+---------------+----------+---------+------+------+-------------+

如上述查詢執行時有個臨時表別名為 der,外部 select 查詢引用了這個臨時表。

union 查詢:

mysql> explain select 1 union all select 1;
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra           |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
|  1 | PRIMARY      | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
|  2 | UNION        | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+

union 結果總是放在一個匿名臨時表中,因為臨時表不在 SQL 中出現,因此它的 id 是 NULL。

2)select_type

select_type 表示對應行是簡單還是複雜的查詢,如果是複雜的查詢,又是上述三種複雜查詢中的哪一種。

  • simple:簡單查詢,即查詢不包含子查詢和 union。
mysql> explain select * from film where id = 2;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | film  | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
  • primary:複雜查詢中最外層的 select。
  • subquery:包含在 select 中的子查詢(不在 from 子句中)。
  • derived:包含在 from 子句中的子查詢。MySQL 會將結果存放在一個臨時表中,也稱為派生表(derived 的英文含義)。
mysql> explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+
|  1 | PRIMARY     | <derived3> | system | NULL          | NULL    | NULL    | NULL  |    1 | NULL        |
|  3 | DERIVED     | film       | const  | PRIMARY       | PRIMARY | 4       | const |    1 | NULL        |
|  2 | SUBQUERY    | actor      | const  | PRIMARY       | PRIMARY | 4       | const |    1 | Using index |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+-------------+ 
  • union:在 union 中的第二個和之後的 select。
  • union result:從 union 臨時表檢索結果的 select。
mysql> explain select 1 union all select 1;
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra           |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
|  1 | PRIMARY      | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
|  2 | UNION        | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL | No tables used  |
| NULL | UNION RESULT | <union1,2> | ALL  | NULL          | NULL | NULL    | NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+

3)table

這一列表示 explain 的該行正在訪問哪個表。

  • 當 from 子句中有子查詢時,table 列是 <derivenN> 格式,表示當前查詢依賴 id=N 的查詢,於是先執行 id=N 的查詢。
  • 當有 union 時,UNION RESULT 的 table 列的值為 <union1,2>,1 和 2 表示參與 union 的 select 行 id。

4)type

這一列表示關聯型別或訪問型別,即 MySQL 決定如何查詢表中的行。

依次從最優到最差分別為:NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

  • NULL:MySQL 能夠在優化階段分解查詢語句,在執行階段用不著再訪問表或索引。例如:在索引列中選取最小值,可以單獨查詢索引來完成,不需要在執行時訪問表。
mysql> explain select min(id) from film;
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra                        |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+
  • const, system:MySQL 能對查詢的某部分進行優化並將其轉化成一個常量(可以看 show warnings 的結果),常用於 primary key 或 unique key 的所有列與常數比較時,因此表最多有一個匹配行,讀取 1 次,速度比較快。
mysql> explain extended select * from (select * from film where id = 1) tmp;
+----+-------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table      | type   | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
|  1 | PRIMARY     | <derived2> | system | NULL          | NULL    | NULL    | NULL  |    1 |   100.00 | NULL  |
|  2 | DERIVED     | film       | const  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+

mysql> show warnings;
+-------+------+---------------------------------------------------------------+
| Level | Code | Message                                                       |
+-------+------+---------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select '1' AS `id`,'film1' AS `name` from dual |
+-------+------+---------------------------------------------------------------+
  • eq_ref:primary key 或 unique key 索引的所有部分被連線使用 ,最多隻會返回一條符合條件的記錄。這可能是在 const 之外最好的聯接型別了,簡單的 select 查詢不會出現這種 type。
mysql> explain select * from film_actor left join film on film_actor.film_id = film.id;
+----+-------------+------------+--------+---------------+-------------------+---------+-------------------------+------+-------------+
| id | select_type | table      | type   | possible_keys | key               | key_len | ref                     | rows | Extra       |
+----+-------------+------------+--------+---------------+-------------------+---------+-------------------------+------+-------------+
|  1 | SIMPLE      | film_actor | index  | NULL          | idx_film_actor_id | 8       | NULL                    |    3 | Using index |
|  1 | SIMPLE      | film       | eq_ref | PRIMARY       | PRIMARY           | 4       | test.film_actor.film_id |    1 | NULL        |
+----+-------------+------------+--------+---------------+-------------------+---------+-------------------------+------+-------------+
  • ref:相比eq_ref,不使用唯一索引,而是使用普通索引或者唯一索引的部分字首。索引要和某個值相比較,可能會找到多個符合條件的行。
-- 1. 簡單 select 查詢,name是普通索引(非唯一索引)
mysql> explain select * from film where name = "film1";
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key      | key_len | ref   | rows | Extra                    |
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | film  | ref  | idx_name      | idx_name | 33      | const |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+

-- 2. 關聯表查詢,idx_film_actor_id是film_id和actor_id的聯合索引,這裡使用到了film_actor的左邊字首film_id部分。
mysql> explain select * from film left join film_actor on film.id = film_actor.film_id;
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+-------------+
| id | select_type | table      | type  | possible_keys     | key               | key_len | ref          | rows | Extra       |
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+-------------+
|  1 | SIMPLE      | film       | index | NULL              | idx_name          | 33      | NULL         |    3 | Using index |
|  1 | SIMPLE      | film_actor | ref   | idx_film_actor_id | idx_film_actor_id | 4       | test.film.id |    1 | Using index |
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+-------------+
  • ref_or_null:類似ref,但是可以搜尋值為 NULL 的行。
mysql> explain select * from film where name = "film1" or name is null;
+----+-------------+-------+-------------+---------------+----------+---------+-------+------+--------------------------+
| id | select_type | table | type        | possible_keys | key      | key_len | ref   | rows | Extra                    |
+----+-------------+-------+-------------+---------------+----------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | film  | ref_or_null | idx_name      | idx_name | 33      | const |    2 | Using where; Using index |
+----+-------------+-------+-------------+---------------+----------+---------+-------+------+--------------------------+
  • index_merge:表示使用了索引合併的優化方法。例如下表:id 是主鍵,tenant_id 是普通索引。or 的時候沒有用 primary key,而是使用了 primary key(id) 和 tenant_id 索引。
mysql> explain select * from role where id = 11011 or tenant_id = 8888;
+----+-------------+-------+-------------+-----------------------+-----------------------+---------+------+------+-------------------------------------------------+
| id | select_type | table | type        | possible_keys         | key                   | key_len | ref  | rows | Extra                                           |
+----+-------------+-------+-------------+-----------------------+-----------------------+---------+------+------+-------------------------------------------------+
|  1 | SIMPLE      | role  | index_merge | PRIMARY,idx_tenant_id | PRIMARY,idx_tenant_id | 4,4     | NULL |  134 | Using union(PRIMARY,idx_tenant_id); Using where |
+----+-------------+-------+-------------+-----------------------+-----------------------+---------+------+------+-------------------------------------------------+
  • range:範圍掃描通常出現在 in(), between ,> ,<, >= 等操作中。使用一個索引來檢索給定範圍的行。
mysql> explain select * from actor where id > 1;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | actor | range | PRIMARY       | PRIMARY | 4       | NULL |    2 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
  • index:和 ALL 一樣,不同就是 MySQL 只需掃描索引樹,這通常比 ALL 快一些。
mysql> explain select count(*) from film;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | index | NULL          | idx_name | 33      | NULL |    3 | Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
  • ALL全表掃描,意味著 MySQL 需要從頭到尾去查詢所需要的行。通常情況下這需要增加索引來進行優化了。
mysql> explain select * from actor;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | actor | ALL  | NULL          | NULL | NULL    | NULL |    2 | NULL  |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+

5)possible_keys

這一列顯示查詢可能使用哪些索引來查詢。

explain 時可能出現 possible_keys 有列,而 key 顯示 NULL 的情況,這種情況通常是因為表中資料不多,MySQL 認為索引對此查詢幫助不大,於是選擇了全表查詢。

如果該列是 NULL,則表示沒有使用相關的索引。在這種情況下,可以通過檢查 where 子句看是否可以創造一個適當的索引來提高查詢效能,然後用 explain 檢視效果。

6)key

這一列顯示 MySQL 實際採用哪個索引來優化對該表的訪問。

如果沒有使用索引,則該列是 NULL。如果想強制 MySQL 使用或忽視 possible_keys 列中的索引,可以在查詢中使用 force index、ignore index。

7)key_len

這一列顯示了 MySQL 在索引裡使用的位元組數,通過這個值可以算出具體使用了索引中的哪些列。

舉例來說,film_actor 的聯合索引 idx_film_actor_id 由 film_id 和 actor_id 這兩個 int 列組成,並且每個 int 是 4 位元組。通過結果中的 key_len=4 可推斷出查詢使用了第一個列:film_id 列來執行索引查詢。

mysql> explain select * from film_actor where film_id = 2;
+----+-------------+------------+------+-------------------+-------------------+---------+-------+------+-------------+
| id | select_type | table      | type | possible_keys     | key               | key_len | ref   | rows | Extra       |
+----+-------------+------------+------+-------------------+-------------------+---------+-------+------+-------------+
|  1 | SIMPLE      | film_actor | ref  | idx_film_actor_id | idx_film_actor_id | 4       | const |    1 | Using index |
+----+-------------+------------+------+-------------------+-------------------+---------+-------+------+-------------+

key_len 計算規則如下:

  • 字串
    • char(n):n 位元組長度
    • varchar(n):2 位元組儲存字串長度;如果是 utf-8,則長度為 3*n + 2
  • 數值型別
    • tinyint:1 位元組
    • smallint:2 位元組
    • int:4 位元組
    • bigint:8 位元組  
  • 時間型別 
    • date:3 位元組
    • timestamp:4 位元組
    • datetime:8 位元組
  • 如果欄位允許為 NULL,則需要 1 位元組記錄是否為 NULL。

索引最大長度是 768 位元組,當字串過長時,MySQL 會做一個類似左字首索引的處理,將前半部分的字元提取出來做索引。

8)ref

這一列顯示了在 key 列記錄的索引中,表查詢值所用到的列或常量,常見的有:const(常量)、func、NULL、欄位名(例:film.id)。

9)rows

這一列是 MySQL 估計要讀取並檢測的行數,注意這個不是結果集裡的行數。

10)Extra

這一列展示的是額外資訊。常見的重要值如下:

  • distinct: 一旦 MySQL 找到了與行相聯合匹配的行,就不再搜尋了。
mysql> explain select distinct name from film left join film_actor on film.id = film_actor.film_id;
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+------------------------------+
| id | select_type | table      | type  | possible_keys     | key               | key_len | ref          | rows | Extra                        |
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+------------------------------+
|  1 | SIMPLE      | film       | index | idx_name          | idx_name          | 33      | NULL         |    3 | Using index; Using temporary |
|  1 | SIMPLE      | film_actor | ref   | idx_film_actor_id | idx_film_actor_id | 4       | test.film.id |    1 | Using index; Distinct        |
+----+-------------+------------+-------+-------------------+-------------------+---------+--------------+------+------------------------------+
  • Using index:這發生在對錶的請求列都是索引的時候,返回的列資料只使用了索引中的資訊,而沒有再去訪問表中的行記錄。這也是覆蓋索引的標識,是效能高的表現。
mysql> explain select id from film order by id;
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | index | NULL          | PRIMARY | 4       | NULL |    3 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+ 
  • Using where:MySQL 伺服器將在儲存引擎檢索行後再進行過濾。就是先讀取整行資料,再按 where 條件進行檢查,符合就留下,不符合就丟棄。
mysql> explain select * from film where id > 1;
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
|  1 | SIMPLE      | film  | index | PRIMARY       | idx_name | 33      | NULL |    3 | Using where; Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+--------------------------+
  • Using temporary:MySQL 需要建立一張臨時表來處理查詢。出現這種情況一般是要進行優化的,首先是想到用索引來優化。
-- 1. actor.name沒有索引,此時建立了張臨時表來distinct
mysql> explain select distinct name from actor;
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra           |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------+
|  1 | SIMPLE      | actor | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using temporary |
+----+-------------+-------+------+---------------+------+---------+------+------+-----------------+

-- 2. film.name建立了idx_name索引,此時查詢時extra是using index,沒有用臨時表
mysql> explain select distinct name from film;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | index | idx_name      | idx_name | 33      | NULL |    3 | Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
  • Using filesort:MySQL 會對結果使用一個外部索引排序,而不是按索引次序從表裡讀取行,這也是回表查詢的標識。此時 MySQL 會根據聯接型別瀏覽所有符合條件的記錄,並儲存排序關鍵字和行指標,然後排序關鍵字並按順序檢索行資訊。這種情況下一般也是要考慮使用索引來優化的。
-- 1. actor.name未建立索引,會瀏覽actor整個表,儲存排序關鍵字name和對應的id,然後排序name並檢索行記錄
mysql> explain select * from actor order by name;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
|  1 | SIMPLE      | actor | ALL  | NULL          | NULL | NULL    | NULL |    2 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+

-- 2. film.name建立了idx_name索引,此時查詢時extra是using index
mysql> explain select * from film order by name;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+
|  1 | SIMPLE      | film  | index | NULL          | idx_name | 33      | NULL |    3 | Using index |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+