一、背景
在日常工作中,可能會收到一些超時或慢響應的告警,最根到底可能是因為一些執行時間比較的SQL語句,這就跟我們平時開發需要注意細節相關了。那么找到這些SQL語句怎么優化呢?到底是哪里的問題導致SQL執行時間長呢? 這個時候Explain命令尤其重要,它可以查看該SQL語句有沒有使用上索引、使用了哪個索引、有沒有做全表掃描、有沒有使用臨時表等等。下面都是基於mysql 8進行案例說明的。
二、語法
EXPLAIN語句提供有關MySQL如何執行語句的信息。 EXPLAIN 通常與SELECT,DELETE,INSERT,REPLACE和UPDATE語句一起使用。
例如:explain select * from tb_student;
三、explain 輸出列詳解
Column
|
JSON Name
|
Meaning
|
select_id
|
The SELECT identifier
|
|
None
|
The SELECT type
|
|
table_name
|
The table for the output row
|
|
partitions
|
The matching partitions
|
|
access_type
|
The join type
|
|
possible_keys
|
The possible indexes to choose
|
|
key
|
The index actually chosen
|
|
key_length
|
The length of the chosen key
|
|
ref
|
The columns compared to the index
|
|
rows
|
Estimate of rows to be examined
|
|
filtered
|
Percentage of rows filtered by table condition
|
|
None
|
Additional information
|
example:
+----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t | NULL | index | NULL | idx_id | 5 | NULL | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
- id:select標志符,有幾個 select 就有幾個id,並且id的順序是按 select 出現的順序增長的。MySQL將 select 查詢分為簡單查詢和復雜查詢。復雜查詢可以如下:
mysql> explain select (select 1 from t limit 1) from t1; +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | PRIMARY | t1 | NULL | index | NULL | PRIMARY | 4 | NULL | 1 | 100.00 | Using index | | 2 | SUBQUERY | t | NULL | index | NULL | idx_id | 5 | NULL | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- select_type
select_type 表示對應行是是簡單還是復雜的查詢。總共有12種類型,挑其中幾種描述下:
-
- simple:簡單查詢。查詢不包含子查詢和union
mysql> explain select * from t; +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t | NULL | index | NULL | idx_id | 5 | NULL | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+--------+---------+------+------+----------+-------------+
- simple:簡單查詢。查詢不包含子查詢和union
-
- primary:復雜查詢中最外層的 select。如上面一個復雜查詢
- union:在 union 中的第二個或隨后的 select
mysql> explain select id from t1 union select id from t2; +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | t1 | NULL | index | NULL | PRIMARY | 4 | NULL | 1 | 100.00 | Using index | | 2 | UNION | t2 | NULL | index | NULL | PRIMARY | 4 | NULL | 2 | 100.00 | Using index | | NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
-
- DEPENDENT UNION UNION中的第二個或后面的SELECT語句,取決於外面的查詢
mysql> explain select * from t where id in (select t1.id from t1 union select id from t2 ); +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+--------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+--------------------------+ | 1 | PRIMARY | t | NULL | index | NULL | idx_id | 5 | NULL | 1 | 100.00 | Using where; Using index | | 2 | DEPENDENT SUBQUERY | t1 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | func | 1 | 100.00 | Using index | | 3 | DEPENDENT UNION | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | func | 1 | 100.00 | Using index | | NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------------+------------+------------+--------+---------------+---------+---------+------+------+----------+--------------------------+
- DEPENDENT UNION UNION中的第二個或后面的SELECT語句,取決於外面的查詢
-
- UNION RESULT union的結果,如上。
- table
顯示這一行的數據是關於哪張表的, 有時不是真實的表名字,看到的是derivedN(N是個數字)
- partitions
查詢將匹配記錄的分區。 對於非分區表,該值為NULL
- type
這列很重要,顯示了連接使用了哪種類別,有無使用索引。從最好到最差的連接類型為system、const、eq_ref、ref、fulltext、ref_or_null、index_merge、unique_subquery、index_subquery、range、index和ALL
- system 該表只有一行(=系統表)。 這是const join類型的特例
- const 該表最多具有一個匹配行,該行在查詢開始時讀取。 因為只有一行,所以優化器的其余部分可以將這一行中列的值視為常量。 const表非常快,因為它們只能讀取一次。當將PRIMARY KEY或UNIQUE索引的所有部分與常量值進行比較時,將使用const。形如:
SELECT * FROM tbl_name WHERE primary_key=1; SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2;
example:
mysql> explain select * from t1 where id = 1; +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | t1 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
- eq_ref
對於先前表中的每行組合,從此表中讀取一行。 除了system和const類型,這是可能的最佳聯接類型。 當連接使用索引的所有部分並且索引是PRIMARY KEY或UNIQUE NOT NULL索引時,將使用它。
eq_ref可用於使用=運算符進行比較的索引列。 比較值可以是常量,也可以是使用在此表之前讀取的表中列的表達式。 在以下示例中,MySQL可以使用eq_ref連接來處理ref_table:
SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
example:
mysql> explain select * from t1, t2 where t1.id = t2.id; +----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------+ | 1 | SIMPLE | t1 | NULL | ALL | PRIMARY | NULL | NULL | NULL | 1 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | test_db.t1.id | 1 | 100.00 | NULL | +----+-------------+-------+------------+--------+---------------+---------+---------+---------------+------+----------+-------+
- ref
對於先前表中的每個行組合,將從該表中讀取具有匹配索引值的所有行。 如果聯接僅使用鍵的最左前綴,或者如果鍵不是PRIMARY KEY或UNIQUE索引(換句話說,如果聯接無法基於鍵值選擇單個行),則使用ref。 如果使用的鍵僅匹配幾行,則這是一種很好的聯接類型。
ref可以用於使用=或<=>運算符進行比較的索引列。 在以下示例中,MySQL可以使用ref聯接來處理ref_table:
SELECT * FROM ref_table WHERE key_column=expr; SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;
example:
mysql> explain select * from t where id = 1; +----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | t | NULL | ref | idx_id | idx_id | 5 | const | 1 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
- fulltext 使用FULLTEXT索引執行連接。
- ref_or_null
該連接類型類似於ref,但是MySQL額外搜索包含NULL值的行。 此聯接類型優化最常用於解析子查詢。 在以下示例中,MySQL可以使用ref_or_null連接來處理ref_table:
SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;
- index_merge
此聯接類型指示使用索引合並優化。 在這種情況下,輸出行中的鍵列包含使用的索引列表,而key_len包含使用的索引的最長鍵部分的列表。
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;
- unique_subquery
此類型將eq_ref替換為以下形式的某些IN子查詢, 形如
value IN (SELECT primary_key FROM single_table WHERE some_expr)
- index_subquery
此連接類型類似於unique_subquery。 它代替了IN子查詢,但適用於以下形式的子查詢中的非唯一索引,形如:
value IN (SELECT key_column FROM single_table WHERE some_expr)
- range
使用該索引選擇行,僅檢索給定范圍內的行。 輸出行中的鍵列指示使用哪個索引。 key_len包含使用的最長的鍵部分。 此類型的ref列為NULL。
使用=,<>,>,> =,<,<=,IS NULL,<=>,BETWEEN,LIKE或IN()運算符將鍵列與常量進行比較時,可以使用range.
mysql> explain select * from t1 where t1.id > 1; +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1 | 100.00 | Using where | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.01 sec)
- index
該索引連接類型與ALL相同,除了掃描索引樹外。 這發生兩種方式:
如果索引是查詢的覆蓋索引,並且可用於滿足表中所需的所有數據,則僅掃描索引樹。 在這種情況下,“額外”列顯示“使用索引”。 僅索引掃描通常比ALL更快,因為索引的大小通常小於表數據。
使用對索引的讀取執行全表掃描,以按索引順序查找數據行。
mysql> explain select id from t1; +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | index | NULL | PRIMARY | 4 | NULL | 1 | 100.00 | Using index | +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- all
對來自先前表的行的每個組合進行全表掃描。 如果該表是未標記為const的第一個表,則通常不好,並且在所有其他情況下通常非常糟糕。 通常,您可以通過添加索引來避免ALL,這些索引允許基於早期表中的常量值或列值從表中檢索行。
- possible_keys
指MySQL可以從中選擇的索引來查找此表中的行。 請注意,此列完全獨立於EXPLAIN輸出中顯示的表順序。 這意味着在實踐中可能無法將某些鍵用於生成的表順序。 如果此列為NULL(或在JSON格式的輸出中未定義),則沒有相關的索引。 在這種情況下,您可以通過檢查WHERE子句來檢查它是否引用了某些適合索引的列,從而可以提高查詢性能。 如果是這樣,請創建適當的索引,然后再次使用EXPLAIN檢查查詢
- key
指MySQL實際決定使用的鍵(索引)。 如果MySQL決定使用mays_keys索引之一來查找行,則將該索引列為鍵值。
可能會命名一個可能索引中不存在的索引。 如果沒有任何可能的索引索引適合於查找行,但是查詢選擇的所有列都是其他索引的列,則可能發生這種情況。 也就是說,命名索引覆蓋了選定的列,因此盡管不使用索引來確定要檢索的行,但索引掃描比數據行掃描更有效。
對於InnoDB,即使查詢也選擇了主鍵,輔助索引也可能覆蓋選定的列,因為InnoDB將主鍵值與每個輔助索引一起存儲。 如果key為NULL,則MySQL未找到可用於更有效地執行查詢的索引。
- key_len
key_len列指示MySQL決定使用的密鑰的長度。 key_len的值使您能夠確定MySQL實際使用的多部分鍵的多少部分。 如果鍵列為NULL,則len_len列也為NULL。使用的索引的長度。在不損失精確性的情況下,長度越短越好
- ref
ref列顯示將哪些列或常量與鍵列中命名的索引進行比較,以從表中選擇行。
- rows
rows列顯示MySQL認為它執行查詢時必須檢查的行數。
- filtered
已過濾的列指示將被表條件過濾的表行的估計百分比。 最大值為100,這表示未過濾行。 值從100減小表示過濾量增加。 rows顯示了檢查的估計行數,×過濾后的行顯示了將與下表連接的行數。 例如,如果行數為1000,過濾條件為50.00(50%),則與下表聯接的行數為1000×50%= 500。
- extra
此列包含有關MySQL如何解析查詢的其他信息.
-
- Distinct
一旦MYSQL找到了與行相聯合匹配的行,就不再搜索了
-
- Not exists
MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標准的行, 就不再搜索了
-
- Using filesort
看到這個的時候,查詢就需要優化了。MYSQL需要進行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行
-
- Using index
列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對表的全部的請求列都是同一個索引的部分的時候
-
- Using temporary
看到這個的時候查詢需要優化了。這里標示MYSQL需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行ORDER BY上,而不是GROUP BY
-
- Using where
使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,並且連接類型ALL或index,這就會發生,或者是查詢有問題
四、參考文獻
表結構:
mysql> show create table t; +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | t | CREATE TABLE `t` ( `id` int(11) DEFAULT NULL, `name` varchar(10) NOT NULL, PRIMARY KEY (`name`), KEY `idx_id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> show create table t1; +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ | t1 | CREATE TABLE `t1` ( `id` int(11) NOT NULL, `age` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> show create table t2; +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ | t2 | CREATE TABLE `t2` ( `id` int(11) NOT NULL, `sex` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci | +-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)