學習數據庫查詢優化技術,第一步需要看明白查詢執行計劃,根據查詢執行計劃理解查詢優化器的執行過程,體會優化技術的運用情況。所以,讀懂查詢執行計劃是掌握查詢優化技術的必要條件。
以下從MySQL查詢執行計劃的格式和關鍵字,介紹MySQL的查詢執行計劃,並結合實例,幫助讀者理解查詢執行計劃。
示例 演示如何閱讀MySQL的查詢執行計劃。先創建5張表,命令如下(各表的數據量爲:t1表10000行數據,t2表100行數據,t3表100行數據,t4表7行數據,t5表10行數據):
CREATE TABLE t1(id1 INT,a1 INT,b1INT,PRIMARY KEY(id1));
CREATE TABLE t2(id2 INT,a2 INT,b2 INT);
CREATE TABLE t3(id3 INT UNIQUE,a3 INT,b3INT);
CREATE TABLE t4(id4 INT,a4 INT,b4 INT);
CREATE TABLE t5(id5 INT UNIQUE,a5 INT,b5INT);
使用下面的存儲過程插入表中的數據:
DELIMITER //
CREATE PROCEDURE proc01()
begin
declare var int;
set var=0;
while var<1000 do
insert into t1 values(var,var,var);
set var=var+1;
end while;
end;
//
DELIMITER ;
注:具體的測試環境搭建過程,我在另一個博客中有所描述,可以參考。MySQL的查詢執行計劃解釋
MySQL 5.6.10 版本使用Explain類表示查詢執行計劃。通過EXPLAIN這個SQL語句觸發,顯示查詢執行計劃。
1. EXPLAIN功能
語法格式:
EXPLAN [explain_type] explainable_stmt
可選項包括:
EXTENDED | PARTITIONS | FORMAT=format_name
Format_name:
TRADITIONAL |JSON
說明:
EXPLAIN命令:顯示SQL語句的查詢執行計劃。
EXPLAIN EXTENDED命令:顯示SQL語句的詳細的查詢執行計劃;之後可以通過 show warnings命令查看詳細的信息。
EXPLAIN PARTITIONS命令:顯示SQL語句的帶有分區表信息的查詢執行計劃。
EXPLAIN命令:輸出格式有以下兩種:
TRADITIONAL:傳統類型;按行隔離,每行標識一個子操作。
JSON:JSON格式。
Explainable_stmt:可被EXPLAIN執行的SQL語句,包括的類型有:select、insert、update、delete。
2. 查詢執行計劃
理解MySQL查詢執行計劃,需要理解執行順序和結點解析兩個部分。
1) 執行順序
執行5表連接的查詢語句,演示MySQL的查詢執行計劃樣式,語句如下:
- EXPLAIN SELECT * FROM (t1 LEFT JOIN t2 ON true),(t3 FULL JOIN t4 ON true),t5 WHERE id1=id2 AND id2=id3 AND id3=id4 AND id4=id5;
執行SQL,結果如下所示:
- EXPLAIN SELECT * FROM (t1 LEFT JOIN t2 ON true),(t3 FULL JOIN t4 ON true),t5 WHERE id1=id2 AND id2=id3 AND id3=id4 AND id4=id5;
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- | 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 192 | Using where |
- | 1 | SIMPLE | FULL | ref | id3 | id3 | 5 | test.t2.id2 | 1 | NULL |
- | 1 | SIMPLE | t5 | ref | id5 | id5 | 5 | test.t2.id2 | 1 | NULL |
- | 1 | SIMPLE | t1 | eq_ref | PRIMARY | PRIMARY | 4 | test.t2.id2 | 1 | NULL |
- | 1 | SIMPLE | t4 | ALL | NULL | NULL | NULL | NULL | 400 | Using where; Using join buffer (Block Nested Loop) |
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- 5 rows in set (0.00 sec)
- > EXPLAIN SELECT * FROM (t1 LEFT JOIN t2 ON true),(t3 FULL JOIN t4 ON true),t5 WHERE id1=id2 AND id2=id3 AND id3=id4 AND id4=id5;
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- | 1 | SIMPLE | t4 | ALL | NULL | NULL | NULL | NULL | 7 | Using where |
- | 1 | SIMPLE | FULL | ref | id3 | id3 | 5 | test.t4.id4 | 1 | NULL |
- | 1 | SIMPLE | t5 | ref | id5 | id5 | 5 | test.t4.id4 | 1 | NULL |
- | 1 | SIMPLE | t1 | eq_ref | PRIMARY | PRIMARY | 4 | test.t4.id4 | 1 | NULL |
- | 1 | SIMPLE | t2 | ALL | NULL | NULL | NULL | NULL | 100 | Using where; Using join buffer (Block Nested Loop) |
- +----+-------------+-------+--------+---------------+---------+---------+-------------+------+----------------------------------------------------+
- 5 rows in set (0.00 sec)
下面對該執行結果進行簡要分析:
從第1行到第9行,表示了完成的查詢計劃;
第1行到第3行,表明查詢計劃的結構;id表示對象被操作的順序;ID值大,先被執行;如果相同,執行順序從上到下;
從第4行起,每一行爲一個結點,表示本結點被操作對象的可用信息,如索引等;
表的連接次序爲:t4,t5,t3,t1,t2.這和初始給定的連接次序不同,經過優化,外連接被消除;
T4表的元組數最少,安裝mysql多表連接算法,表經過排序後,順序爲t4,t5,t2,t3,t1.
因爲t5,t3,t1上有索引可以利用,所以t4上的一條元組確定後,則額可以利用索引之間定位t5,t3,t1表上的元組,所以第5,6,7行的key列又索引可用;ref列表明瞭這3個表都是引用了t4表的id4列。
t2表的數據相對較多,且又沒有索引,最後被連接,連接使用了Extra列表明的嵌套循環連接算法,並且使用了連接緩存。
2) 結點解析
MySQL查詢計劃的輸出列的含義如下:
id:每個被獨立執行的操作的標識,表示對象被操作的順序;ID值大,先被執行;如果相同,執行順序從上到下;
select_type:查詢每個select子句中的類型;具體值如下表所示;
table:名字,被操作的對象名稱,通常是表明,但又其他格式。
partitions:匹配的分區信息(對於非分區表值爲NULL)
type:連接操作的類型;具體值見後面的表格;
possible_keys:備選的索引(列出可能被使用到的索引)
key:經過優化器選定的索引;常用analyzetable命令,可以使優化器正確地選擇索引。
key_len:被優化器選定的索引鍵的長度,單位是字節。
ref:表示本行被操作的對象的參照對象(被參照的對象可能是一個常量用const表示,也可能是其他表的key指向的對象)。
rows:查詢執行所掃描的元組個數(對於InnoDB,此值是估計值)。
filtered:安裝條件表上數據被元組過濾的元組個數的百分比,rows*filetered/100可以求出過濾後的元組數即實際的元組數。
extra:MySQL查詢優化器執行查詢過程中對查詢計劃的重要補充信息。
MySQL查詢執行計劃select_type列子句類型表
Select_type | 說明 |
SIMPLE | 簡單的select語句(不包括UNION操作或子查詢操作) |
PRIMARY | 查詢中最外層的select(如兩表做UNION或存在有子查詢,外層的表被稱作primary,內層被作爲UNION) |
UNION | UNION操作中,查詢中處於內層的select(內存的select語句與外層的select語句沒有依賴關係) |
DEPENDENT UNION | UNION操作中,查詢中處於內層的select(內存的select語句與外層的select語句有依賴關係) |
UNION RESULT | UNION操作的結果,ID通常爲NULL |
SUBQUERY | 子查詢中的首個select(如果有多個子查詢存在) |
DEPENDENT SUBQUERY | 子查詢中的首個select,但依賴於外層的表(如果有多個子查詢存在) |
DERIVED | 被驅動的select子查詢(子查詢位於from子句) |
MATERIALIZED | 被物化的子查詢 |
UNCACHEABLE SUBQUERY | 對於外層的主表,子查詢不可被物化,每次都需要計算(耗時操作) |
UNCACHEABLE UNION | UNION操作中,內層的不可物化的子查詢(類似於UNCACHEABLE SUBQUERY) |
連接操作數據訪問方式表
代碼表示方式 | 說明 |
JT_SYSSTEM | 常量表情況一,表上只有一條元組匹配 |
JT_COUNST | 常量表情況二,where條件篩選後表上至多有一條元組匹配,如:where table.pk=2(pk列是主鍵列,值爲2的要麼有一條,要麼沒有) |
JT_EQ_REF | 參與連接運算的表,是內表(在嗲嗎實現的算法中,兩表連接時作爲循環中的內循環遍歷對象,這樣的表爲內表)。基於索引(連接字段上存在唯一索引或主機索引,且操作符必須是“=”謂詞,索引的值不能爲NULL)做掃描,使得對外表的一條元組,內表只有唯一一條元組與之對應 |
JT_REF | 可用於單表索引或連接。參與連接運算的表,是內表。基於索引(連接字段上的索引是非唯一索引,操作符必須是“=”謂詞,連接字段值不可爲NULL)做掃描,是的對外表的一條元組,內表可有若干條元組與之對應 |
JT_REF_OR_NULL | 類似ref,只是搜索條件敖闊:連接字段的值可爲NULL的情況,如:where col=… or col IS NULL |
JT_RANGE | 範圍掃描。基於索引做範圍掃描,爲諸如 BETWEEN,IN,>=,like類操作提供支持 |
JT_INDEX_SCAN | 索引做掃描。是基於索引在索引的葉子結點上找滿足條件的數據(不需要訪問數據文件) |
JT_ALL | 全表掃描或範圍掃描;不使用索引,順序掃描,直接讀取表上的數據(訪問數據文件) |
JT_UNIQUE_SUBQUERY | 在子查詢中,基於唯一索引進行掃描。類似於eq_ref |
JT_INDEX_SUBQUERY | 在子查詢中,基於除唯一索引之外的索引進行掃描 |
JT_INDEX_MERGE | 多重範圍掃描,兩表連接的每個表的連接字段上均有索引存在且索引有序,結果合併在一起。適用於做集合的並、交操作 |
JT_FT | FT,FullText,全文檢索 |
Extra信息解釋表
Extra值 | 說明 |
Child of ‘table’ pushed join@l | MySQL cluster使用的參數。表示在連接操作中某個表被下推到NDB引擎的某個結點上執行 |
Const row not found | 在連接中,查詢一個沒有元組的空表 |
Deleting all rows | 對於刪除操作,某些存儲引擎支持快速刪除數據 |
Distinct | 優化distinct操作,在找到第一匹配的元組後即停止找同樣值的動作 |
FirstMatch(tb1_name) | 半連接算法中對錶採取了受此匹配的連接策略 |
Full scan on NULL key | 子查詢中,優化器不能使用基於索引的訪問方式時(因有空值存在)所採取的一種數據訪問策略 |
Impossible HAVING | Having的子句值總是FALSE(不能獲取任何元組) |
Impossible WHERE | Where子句的值總是FALSE(不能獲取任何元組) |
Materialize,Scan | MySQL 5.6.7版本之前,表示使用單一被物化的臨時表;之後的版本,物化是通過select_type列表達的 |