慢 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 服務端的互動過程簡介:
- 客戶端傳送一條 SQL 語句給服務端,服務端的聯結器先進行賬號/密碼、許可權等環節驗證,若有異常則直接拒絕請求。
- 服務端查詢快取,如果 SQL 語句命中了快取,則返回快取中的結果,否則繼續處理。
- 服務端對 SQL 語句進行詞法解析、語法解析、預處理來檢查 SQL 語句的合法性。
- 服務端通過優化器對之前生成的解析樹進行優化處理,生成最優的物理執行計劃。
- 將生成的物理執行計劃呼叫儲存引擎的相關介面,進行資料查詢和處理。
- 處理完成後將結果返回客戶端。
客戶端和 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 BY … ORDER 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 列,概要描述如下:
- id:選擇識別符號。
- select_type:表示查詢的型別。
- table:輸出結果集的表。
- partitions(MySQL 8 新增):如果查詢是基於分割槽表的話,會顯示查詢將訪問的分割槽。
- type:表示表的連線型別。
- possible_keys:查詢時可能使用的索引。
- key:實際使用的索引。
- key_len:索引欄位的長度。
- ref:列與索引的比較。
- rows:掃描出的行數(估算的行數)。
- filtered(MySQL 8 新增):按表條件過濾的行百分比。rows * filtered/100 可以估算出將要和 explain 中前一個表進行連線的行數(前一個表指 explain 中的 id 值比當前表 id 值小的表)。
- 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 | +----+-------------+-------+-------+---------------+----------+---------+------+------+-------------+