日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

MySQL 优化 —— EXPLAIN 执行计划详解

發布時間:2025/3/12 数据库 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MySQL 优化 —— EXPLAIN 执行计划详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

本博客大部分內容翻譯自MySQL 官網?Understanding the Query Execution Plan?專題。另外有一些補充,則來自于網課以及《高性能MySQL(第三版)》。

根據我們的表、字段、索引、以及 where 子句中的條件等信息,MySQL 優化器會考慮各種技術來更高效地執行查找。一個大表中的查找不一定要讀取所有記錄;多表連接也不一定需要比較每條聯合記錄。優化器選擇的執行最優查詢的操作集,稱為“查詢執行計劃”也可以說是 EXPLAIN 計劃。我們的目標就是找到那些可以將查詢優化地更好的點,然后通過學習 SQL 語法和索引等技術,來改善執行計劃。

一、EXPLAIN 介紹

EXPLAIN 語句提供了 MySQL 如何執行語句的信息:

1、MySQL5.6 之后 EXPLAIN 可以和 SELECT DELETE INSERT REPLACE UPDATE 語句等一起工作;

2、當 EXPLAIN 和一個可解釋的語句一起使用時,MySQL 就會展示來自優化器的關于語句執行計劃的信息。即,MySQL 會解釋它將會怎樣執行語句,包括表是如何連接的,以什么方式排序的等信息。

3、When EXPLAIN is used with?FOR CONNECTION?connection_id? rather than an explainable statement, it displays the execution plan for the statement executing in the named connection.(這句暫不翻譯)

4、對于 SELECT 語句, EXPLAIN 提供了額外的執行計劃信息,可以用 SHOW WARNINGS 來查看。參考:Section?8.8.3, “Extended EXPLAIN Output Format”.

5、EXPLAIN對于檢查涉及分區表的查詢非常有用。參考:Section?22.3.5, “Obtaining Information About Partitions”.

6、FORMAT 選項可以用于選擇輸出格式。TRADITIONAL 以表格的形式展示。如果沒有指定 FORMAT 選項,TRADITIONAL 就是默認的。JSON 格式會以 json 格式展示 EXPLAIN 信息。例如:EXPLAIN FORMAT = JSON SELECT... 。

在 EXPLAIN 的幫助下,你可以清楚的知道為了讓查詢變得更快,該在哪里給表添加索引。你也可以知道優化器是否以最佳的順序連接各個表。為了讓優化器使用 SELECT 語句中表的命名順序連接各表,以 SELECT? STRAIGHT_JOIN(而不是SELECT)開頭即可。(參考:Section?13.2.9, “SELECT Statement”)但是,STRAIGHT_JOIN?可能會妨礙索引的使用,因為它禁用了半連接轉換(because it disables semijoin transformations.?)。參考:Section?8.2.2.1, “Optimizing Subqueries, Derived Tables, and View References with Semijoin Transformations”.

優化器跟蹤(The optimizer trace有時可能提供與 EXPLAIN 互補的信息。但是,優化器跟蹤的格式和內容會受不同版本的影響。更多細節,參考:MySQL Internals: Tracing the Optimizer.

如果你對本應該使用索引而沒有用到索引的情況感到疑惑,執行一下 ANALYZE TABLE 來更新表統計信息。例如列的基數(cardinality of keys),這會影響優化器做出的選擇。參考:Section?13.7.2.1, “ANALYZE TABLE Statement”.

注意:

EXPLAIN 還可以用來獲取表的列的信息

EXPLAIN? tb1_name??與??DESCRIBE? tb1_name??、?SHOW? COLUMNS? FROM? tb1_name? 是等價的。

更多信息,參考:Section?13.8.1, “DESCRIBE Statement”,和??Section?13.7.5.5, “SHOW COLUMNS Statement”。

二、EXPLAIN 的輸出格式

EXPLAIN會為 select 語句中的每張表返回一行信息。并會以MySQL處理語句時讀取這些表的順序羅列它們。

MySQL 解決所有 join 的方法是使用一個“嵌套循環關聯”的方法。也就是說,MySQL會從第一張表中讀取一條記錄,然后找到第二張表中與之匹配的記錄,然后再找第三張表,依此類推。當所有的表處理完畢,MySQL會輸出查詢的列并回溯表列表(table list),直到找到一個有更多行的表(譯者注:連接表的時候,主表查詢出的記錄往往是最多的,從主表開始關聯查詢,再回溯到主表,可能官網想表達的是這個意思)。下一條記錄會從該表中讀取,并且繼續處理下一張表。

EXPLAIN 輸出包含了分區信息(partitions 列)。同樣,對于 SELECT 語句,EXPLAIN 會生成擴展信息,只要在 EXPLAIN 執行完成后,直接執行 SHOW WARNINGS 即可。參考:Section?8.8.3, “Extended EXPLAIN Output Format”

注意

舊的MySQL版本中, 分區和擴展信息使用?EXPLAIN PARTITIONS?和?EXPLAIN EXTENDED?輸出。這些語法依然向后兼容,但是分區和擴展輸出現在默認都是開啟的了。所以?PARTITIONS?和?EXTENDED?關鍵字完全多余。未來版本也會移除的。

不可以在EXPLAIN語句中同時使用 PARTITIONS 和 EXTENDED 關鍵字。另外,哪一個都不能和 FORMAT 選項一起使用。

MySQL Workbench (譯者注:這是一款由 MySQL 官方出品的,類似 Navicat 的數據庫管理工具)有一個 Visual Explain 功能,可以提供可視化的 EXPLAIN 輸出信息。參考:Tutorial: Using Explain to Improve Query Performance.

2.1 EXPLAIN 輸出字段(EXPLAIN Output Columns)

這一節描述了 EXPLAIN 的輸出字段。后面的兩節則提供了更多的關于 type 和 Extra 字段的信息。

EXPLAIN輸出的每一行都對應一張表。下面的表提供了EXPLAIN的輸出字段,第一列是字段名稱,第二列是當 FORMAT = JSON 時的輸出字段名稱:

ColumnJSON NameMeaning
idselect_idThe?SELECT?identifier:查詢id
select_typeNoneThe?SELECT?type:查詢類型
tabletable_nameThe table for the output row:對應的表
partitionspartitionsThe matching partitions:匹配的分區
typeaccess_typeThe join type:訪問類型
possible_keyspossible_keysThe possible indexes to choose:可能用到的索引
keykeyThe index actually chosen:真正被用到的索引
key_lenkey_lengthThe length of the chosen key:用到的索引長度
refrefThe columns compared to the index:與索引比較的列
rowsrowsEstimate of rows to be examined:大約要檢索的行數
filteredfilteredPercentage of rows filtered by table condition:按表條件過濾的行的百分比
ExtraNoneAdditional information:附加信息

1、id(JSON 名:select_id)

SELECT 標識符(SELECT identifier)。這是一個連續的數字,用以標識查詢中的 SELECT 。如果引用了其他行的聯合結果集(union result of other rows),那么 id 會為 NULL。這種情況下,該行的 table 字段會顯示為 <union M, N> 這樣的形式,表示該行代表了?id 值為 M 和 N 的行的聯合(the row refers to the union of the rows with?id?values of?M?and?N?)。

重點:id 是一個自然數編號,如1、2,但有時也可以是NULL。如上所述,NULL的時候,就是引用了一個 UNION 結果集

當 id 為數字的時候,編號大的會先執行。有時候,編號會相同,相同編號就從上到下執行

2、select_type(JSON 名:無)

查詢類型。MySQL將查詢分為簡單和復雜類型,復雜類型可分為三大類簡單子查詢FROM子查詢以及UNION查詢。select_type 就是用于區分這三類復雜查詢。可選值如下(紅色標記為常見值):

select_type?ValueJSON NameMeaning
SIMPLENone簡單查詢(沒有任何 UNION 或 子查詢)。
PRIMARYNone主查詢,如果查詢中包含任何復雜的子部分,那么最外層查詢被標記PRIMARY
UNIONNoneUNION 中的第二個或后面的SELECT語句
DEPENDENT UNIONdependent?(true)UNION 中的第二個或后面的SELECT語句, 依賴于外部查詢
UNION RESULTunion_result從UNION 的結果獲取數據的SELECT。
SUBQUERYNoneSELECT子句或WHERE子句中的子查詢
DEPENDENT SUBQUERYdependent?(true)子查詢中的第一個 SELECT, 依賴于外層查詢
DERIVEDNone派生表。FROM子句中的子查詢。MySQL會遞歸執行這些子查詢,把結果放在臨時表里
MATERIALIZEDmaterialized_from_subqueryMaterialized subquery? 物化子查詢。參考《MySQL高級 —— 查詢性能優化》4.1節
UNCACHEABLE SUBQUERYcacheable?(false)非緩存子查詢,結果不能被緩存的子查詢,必須被外部查詢的每一行重新求得
UNCACHEABLE UNIONcacheable?(false)非緩存子查詢(uncacheable subquery)的 UNION 中的第二個或后面的 SELECT

SUBQUERY還可以被標記為DEPENDENT SUBQUERY,這一般是指SELECT依賴于外層查詢發現的數據(很可能是依賴于FROM派生表的外層SELECT)。參考:Section?13.2.10.7, “Correlated Subqueries”?。

DEPENDENT SUBQUERY 的取值與 UNCACHEABLE SUBQUERY(由于用戶變量等原因)?的取值不同。對于 DEPENDENT SUBQUERY ,對于來自其外部查詢的變量的每組不同值,子查詢只重新計算一次。而對于 UNCACHEABLE SUBQUERY ,對外部查詢的每行記錄,該子查詢都會計算一遍。

子查詢緩存與緩存中的查詢結果緩存不一樣(具體描述參考?Section?8.10.3.1, “How the Query Cache Operates”)。子查詢緩存發生在查詢執行過程中,而查詢結果緩存只在查詢執行完畢時才會存儲結果。

當你在 EXPLAIN 語句中指定了 FORMAT = JSON ,輸出的結果并沒有一個對應 select_type 的單獨屬性;query_block 屬性對應給定的 SELECT 。與剛才顯示的大多數 SELECT 子查詢類型等價的屬性都是有的,并且在合適的時機就會展示。不過并沒有與 SIMPLE 和 PRIMARY 等價的 JSON 值。

select_type 屬性值對于非 SELECT 語句,會展示影響表的語句類型如 DELETE 語句的 select_type 就是 DELETE

3、table(JSON 名:table_name)

explain 輸出的每一行都對應一個表別名或表名。它可以是下面的值中的一個:

<union M, N> : 這一行引用了 id 值為 M 和 N 的表的聯合。

<derived N> : 這一行引用了 id 值為 N 的表所派生的表。派生的表可能是一個結果集,比如,FROM 子句中的子查詢。

<subquery N> : 這一行引用了 id 值為 N 的物化子查詢的結果。參考:Section?8.2.2.2, “Optimizing Subqueries with Materialization”.

4、partitions(JSON 名:partitions)

查詢的記錄將會在哪個分區中匹配。NULL 代表沒有分區表。參考:?Section?22.3.5, “Obtaining Information About Partitions”.

5、type(JSON 名:access_type)

關聯類型,但更準確的說法是——訪問類型,換言之就是MySQL決定如何查找表中的行。參考 2.2 節。

6、possible_keys(JSON 名:possible_keys)

該屬性可以表明查詢中,對應表有哪些索引可以使用。注意這個屬性完全不依賴于表在 explain 輸出中的顯示順序。也就是說,以生成的表順序 ,possible_keys 中的有些索引可能實際中并不會用到。

如果該屬性是 NULL (或者在 JSON 格式中是 undefined ),代表沒有相關的索引。這時,你可能就應該努力通過調試 WHERE 子句來提升你的查詢性能,檢查是否涉及到了一些字段或者適合索引查詢的字段。如果有,就創建一個合適的索引,然后再次通過 EXPLAIN 進行檢驗。

查看一個表有哪些索引,可以使用 SHOW INDEX FROM tbl_name 語句。

7、key(JSON 名:key)

這一列表示 MySQL 決定采用哪個索引來優化對該表的訪問。如果 MySQL 決定使用 possible_keys 中的一個索引去查找記錄,那么這個索引就會列在 key 屬性中。

key 中也會出現 possible_key 中沒有出現的索引。發生這種情況,很可能是 possible_keys 沒有找到適合查詢的索引,但是所有查詢的字段都在索引中。也就是說,查詢使用了覆蓋索引。因此,盡管它不用于決定要查詢哪些行,但卻依然可以用于查詢字段,因為索引掃描依然比行掃描更高效。換句話說,possible_keys 揭示了哪一個索引能有助于高效地行查找,而 key 顯示的是優化采用哪一個索引可以最小化查詢成本

對于InnoDB ,即使查詢列表中有主鍵,二級索引也可能覆蓋所查詢的字段,因為InnoDB用每個二級索引存儲了主鍵值。如果列是NULL, MySQL就找不到索引來更有效地執行查詢。

要強制MySQL使用或忽略在 possiblele_keys 中列出的索引,請在查詢中使用?FORCE INDEX,USE INDEX?或?IGNORE INDEX 。參考:?Section?8.9.4, “Index Hints”.

對于 MyISAM,運行 ANALYZE TABLE 可以幫助優化器選擇更好的索引。對于 MyISAM 表來說, myisamchk --analyze 也是一樣的。參考:?Section?13.7.2.1, “ANALYZE TABLE Statement”, 和?Section?7.6, “MyISAM Table Maintenance and Crash Recovery”.

8、key_len(JSON 名:key_length)

該字段表示 MySQL 在索引里使用的字節數。

因為key_len是通過查找表的定義而被計算出,而不是表中的數據,因此它顯示了在索引字段中可能的最大長度,而不是表中數據使用的實際字節數。key_len 的值可以讓你判斷?MySQL 究竟用到了復合索引的哪幾個索引列。如果 key 屬性的值為 NULL , 那么 key_len 肯定也是 NULL 。

由于索引的存儲格式,那些可以為 NULL 的字段的索引長度要比非空字段的索引長度大一些。

MySQL并不總是顯示一個索引真正使用了多少。例如,如果對一個前綴模式匹配(例如 '張%')執行LIKE查詢,它會顯示列的完整寬度正在被使用。

計算 key_len 的簡易方法:

int 類型在MySQL中以4個字節存儲,key_len 為 4,如果列值允許為 NULL,那么需要 + 1,即 key_len 為 5.

double 類型以8個字節存儲,key_len 為 8,如果允許 NULL,那么同樣 +1, 即 key_len 為 9.

char(n) 定長字符串,首先需要看字符集,常見的utf8以3個字節存儲每個字符,gbk用2個,latin用1個。key_len 就等于每個字節長度乘以允許最大字符數n,如果允許NULL,key_len 也要 +1。例如 char(20) DEFAULT NULL,編碼為utf8 ,那么 key_len 就是 3 × 20 + 1 = 61。如果不允許為 NULL ,就是60。

varchar(n)變長字符串,每個字符:utf8為3字節、gbk為2字節、latin為1字節。由于是變長,因此 key_len 要 +2,如果允許 NULL,同樣 +1。其他和 char計算方式一樣。例如,varchar(20) DEFAULT NULL,編碼 utf8,那么 key_len 就是:

3 × 20 + 2 + 1 = 63,如果不允許為 NULL,就是62。

上面的說明只是單獨計算每種列值類型的方法,如果是復合索引,那么key_len 就是用到的索引列長度和。

9、ref(JSON 名:ref)

ref 列顯示了常量或哪些列與 key 列中的索引進行了比較。只有 type 列是 ref 的時候,ref 列才會有值。

簡單的說,就是 key 中的索引,如果與一個常量比較,那么 ref 會顯示 const,如果是與其他表的某個列進行比較,那么就會顯示該列名。

如果 ref 屬性的值是 func ,那么用到的值就是某些函數的結果。想要知道是哪個函數,在 EXPLAIN 執行后使用 SHOW WARNINGS ,查看EXPLAIN 的擴展信息。

函數實際上可能是一個運算符,比如算術運算符。

10、rows(JSON 名:rows)

rows 列表示MySQL認為執行查詢必須檢查的行數。這個數字是內嵌關聯循環計劃里的循環數目。也就是說,它不是最終的結果集里的行數,而是MySQL為了找到符合條件的結果集而必須讀取的行的平均數。

對于 InnoDB 表,這個數是一個估值,而且可能并不總是準確的。

11、filtered(JSON 名:filtered)

filtered 屬性表示被篩選條件過濾掉的記錄條數占全表的估計百分比。最大值是100,意味著記錄全部被過濾掉。從100開始遞減的值表示過濾的量在增加。rows 屬性表示了需要檢查的估計行數,rows 乘 filtered 表示了將會被后面的表關聯的記錄條數。例如,如果 rows 是1000,filtered 是 50.00(50%),那么要與后面的表連接的記錄條數就是 1000 × 50% = 500。

對于filtered ,原文的描述是:The?filtered?column indicates an estimated percentage of table rows that will be filtered by the table condition. The maximum value is 100, which means no filtering of rows occurred.? 這里面有一個語義上的陷阱,即 filtered 究竟表示的是 “被過濾掉的” ?還是 “過濾后(留下來)的” ,經過本人測試,filtered 表示的是前者,即 “被過濾掉的” ,這樣后面的語義也就基本自洽了。而 filtering 則表示 “過濾后(留下來)的” 。

12、Extra(JSON 名:none)

這一列顯示了關于 MySQL如何處理查詢的額外信息。對于不同值的描述,參考:Extra Information. 或參考下面 2.3 節。

2.2 EXPLAIN type訪問類型(EXPLAIN Join Types)

type 屬性描述了表之間是如何連接(或關聯)的。在 JSON 格式輸出中,對應 access_type 屬性。下面的列表描述了訪問類型,順序從“最理想類型”到“最糟糕的類型”:

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

2.2.1 system(不常見)

表只有一行(=系統表)。是 const 連接類型的一種特殊情況。

2.2.2 const

表最多只有 1 條匹配記錄,在查詢開始時就會讀取該表。因為只有一行,所以這一行中列的值可以被其他優化器視為常量。const 訪問類型非常快,因為他們只會被讀取一次。MySQL能將這個查詢轉換為一個常量,然后可以高效地將表從連接操作中移除。

const 會在你使用整個主鍵(all parts of a PRIMARY KEY)唯一索引(UNIQUE index)去比較一個常量的時候用到。在下面的查詢中,tb1_name 就是一張 const 表:

SELECT * FROM tbl_name WHERE primary_key=1;SELECT * FROM tbl_nameWHERE primary_key_part1=1 AND primary_key_part2=2;

2.2.3 eq_ref

使用這種索引查找,MySQL知道最多只返回一條符合條件的記錄。它會在所有的索引部分都被用到的時候以及索引是主鍵非空唯一索引時出現到,它會將它們與某個參考值做比較。MySQL 對于這類訪問類型的優化做的非常好,因為MySQL知道無須估計匹配行的范圍或在找到匹配行后再繼續查找

eq_ref 會在索引列使用 = 號的時候用到。比較的值可以是一個常量也可以是一個從前表讀取的列(的表達式)。在下面的例子中,MySQL 可以使用 eq_ref 類型來處理 ref_table:

SELECT * FROM ref_table,other_tableWHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column_part1=other_table.columnAND ref_table.key_column_part2=1;

2.2.4 ref

這是一種索引訪問(有時也叫“索引查找”)它返回所有匹配某個單個值的行,是查找和掃描的混合體。此類索引訪問只有當使用非唯一性索引唯一性索引的非唯一性前綴時才會發生。把它叫做 ref 是因為索引要跟某個參考值相比較。這個參考值可以是一個常數,或是來自多表查詢的結果值。如果該篩選列可以匹配少量的記錄,那 ref 還算是一個不錯的連接類型。

ref_or_null 是ref 之上的一個變體,它意味著MySQL必須在初次查找的結果里進行第二次查找以找出NULL條目。

ref 也可以在索引列使用 = 或 <=> 號的時候被用到。下面的例子,MySQL 可以使用 ref 來處理 ref_table:

SELECT * FROM ref_table WHERE key_column=expr;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column_part1=other_table.columnAND ref_table.key_column_part2=1;

2.2.5 full_text(不常見)

這種連接方式會在使用 FULLTEXT 索引的時候用到。

2.2.6 ref_or_null(不常見)

這種連接方式和 ref 類似,除此之外, MySQL 還會額外搜索包含 NULL 值的記錄。這種連接類型的優化絕大多數是在處理子查詢的時候。在下面的例子中, MySQL 會使用 ref_or_null 來處理 ref_table:

SELECT * FROM ref_tableWHERE key_column=expr OR key_column IS NULL;

參考:Section?8.2.1.13, “IS NULL Optimization”.?

2.2.7 index_merge(不常見)

這種連接類型表示使用了索引合并優化(Index Merge optimization)。這種情況下,explain 中的 key 屬性會羅列出被用到的索引,key_len 屬性會列出用到的索引的最長的索引部分。參考:Section?8.2.1.3, “Index Merge Optimization”.

2.2.8 unique_subquery(不常見)

這種類型在類似下面的一些使用 IN 的子查詢時取代了 eq_ref:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

?unique_subquery?只是一個索引查找函數,它完全取代了子查詢,以提高效率。

2.2.9 index_subquery(不常見)

這種連接類型有點像 unique_subquery 。它取代了 IN 子查詢,但它只在子查詢中有非唯一索引時才會起作用,類似下面這樣:

value IN (SELECT key_column FROM single_table WHERE some_expr)

2.2.10 range?

這種連接類型會使用索引查詢給定范圍內的記錄。EXPLAIN 輸出中的 key 屬性表示了哪個 索引列 被用到。key_len 包含了被用到的最長的索引部分。ref 屬性為 NULL。

range 類型會在索引列使用 =、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN、LIKE、或 IN() 任意一種操作符去比較常量的時候被用到。當使用 IN或 OR 列表的時候,顯示的范圍掃描,其實并不能和 > 這類比較符的性能等同,雖然它們在EXPLAIN中顯示的類型都是 range,但是 IN() 列表其實屬于等值列表。參考《MySQL高級 —— 高性能索引》6.2 節。

SELECT * FROM tbl_nameWHERE key_column = 10;SELECT * FROM tbl_nameWHERE key_column BETWEEN 10 and 20;SELECT * FROM tbl_nameWHERE key_column IN (10,20,30);SELECT * FROM tbl_nameWHERE key_part1 = 10 AND key_part2 IN (10,20,30);

2.2.11 index

index 類型除了會掃描索引樹之外,其他和 ALL 是一樣的。會有兩種情況出現:

1、如果索引是一個覆蓋索引,那么這種類型的查詢就只會掃描索引樹。這種情況下, Extra 屬性會顯示 Using Index。一個只掃描索引的方式比 ALL 更快,這是因為索引數據肯定要比表中數據要少。

2、以索引次序掃描全表。Extra 不會顯示 Uses Index。

index 類型的主要優點是避免了排序,最大缺點是要承擔按索引次序讀取整個表的開銷。

MySQL 會在查詢只用到了單一索引列的時候用到 index 這種類型。

2.2.12 ALL

這就是人們常說的“全表掃描”,這種類型會對前面各表的組合記錄都進行全表掃描。如果表是第一個沒有被標記為 const 的表,這通常是不好的,在所有其他情況下通常是非常糟糕的。通常你可以通過增加索引來避免 ALL 。但也有例外,例如在查詢中使用了 LIMIT,或在 Extra 列中顯示“Using distinct/not exists”。

2.3 EXPLAIN Extra 信息(EXPLAIN Extra Information)

Extra 屬性顯示了MySQL如何執行查詢的額外信息。

2.3.1 Using index

此值表示MySQL將使用覆蓋索引,以避免訪問表。不要把覆蓋索引和 type = index 訪問類型混淆了。

2.3.2 Using where

這意味著MySQL服務器將在存儲引擎檢索行后再進行過濾。當它讀取索引時,就能被存儲引擎檢驗,因此不是所有帶有 WHERE子句的查詢都會顯示“Using where” 。有時“Using where” 的出現就是一個暗示:查詢可受益于不同的索引。

2.3.3 Using temporary

這意味著MySQL在對查詢結果排序時會使用一個臨時表。

2.3.4 Using filesort

這意味著MySQL會對結果使用一個外部索引排序,而不是按照索引次序從表里讀取行。MySQL有兩種文件排序算法,兩種方式都可以在內存或磁盤上完成。EXPLAIN 不會告訴你 MySQL將使用哪一種文件排序,也不會告訴你排序會在內存里還是在磁盤上完成。

2.3.5 Range checked for each record (index map:N)

這個值意味著沒有好用的索引,新的索引將在連接的每一行上重新估算。N是顯示在possible_keys 列中索引的位圖,并且是冗余的。

?

2.4 EXPLAIN 輸出的解釋

EXPLAIN輸出可以給你在連接各種表查詢的時候一個非常好的指示作用。這會大致告訴你MySQL 在執行查詢的時候必須要檢查多少行記錄。如果你限制了 max_join_size 系統變量,那么 EXPLAIN 也會被用來告訴我們一些有用的東西。參考:?Section?5.1.1, “Configuring the Server”.

下面的例子顯示了多表連接是如何基于 EXPLAIN 提供的信息一點點優化的

假設你有一個查詢語句,并且你通過 EXPLAIN 來檢查它:

EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,tt.ProjectReference, tt.EstimatedShipDate,tt.ActualShipDate, tt.ClientID,tt.ServiceCodes, tt.RepetitiveID,tt.CurrentProcess, tt.CurrentDPPerson,tt.RecordVolume, tt.DPPrinted, et.COUNTRY,et_1.COUNTRY, do.CUSTNAMEFROM tt, et, et AS et_1, doWHERE tt.SubmitTime IS NULLAND tt.ActualPC = et.EMPLOYIDAND tt.AssignedPC = et_1.EMPLOYIDAND tt.ClientID = do.CUSTNMBR;

對于這個例子,做出下面的假設:

1、比較的列(譯者注:columns being compared,實際上指的就是where 子句后面作為篩選條件的列,因為往往需要用到 = 號等操作符,因此在官網中一般都被稱為被比較的列)定義如下:

TableColumnData Type
ttActualPCCHAR(10)
ttAssignedPCCHAR(10)
ttClientIDCHAR(10)
etEMPLOYIDCHAR(15)
doCUSTNMBRCHAR(15)

2、表有以下這些索引:

TableIndex
ttActualPC
ttAssignedPC
ttClientID
etEMPLOYID?(primary key)
doCUSTNMBR?(primary key)

3、tt 表的?ActualPC 字段不是均勻分布的。

首先,在所有優化執行之前, EXPLAIN 語句輸出了下面的信息:

table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 do ALL PRIMARY NULL NULL NULL 2135 et_1 ALL PRIMARY NULL NULL NULL 74 tt ALL AssignedPC, NULL NULL NULL 3872ClientID,ActualPCRange checked for each record (index map: 0x23)

因為每張表的連接類型都是 ALL ,這表明MySQL 正在生成一張笛卡爾集(a Cartesian product),也就是表中的每一行都進行了組合。這會花費相當長的時間,因為必須檢查每個表中行數的乘積。對于這個案例,乘積就是:74 × 2135 × 74 × 3872 = 45,268,558,720 行。如果表再大一點,你可以想象一下它需要花費多長時間。

這里有個問題,如果比較的列被聲明以相同的大小和類型,那么?MySQL 就可以更高效的使用列上的索引。在這種語境下,VARCHAR 和 CHAR 如果被設定為相同的大小,那么就被認為是相同的。tt.ActualPC 被聲明為 CHAR(10) 而 et.EMPLOYID 聲明為 CHAR(15),所以長度不匹配。

為了修復這種列長度的不一致,使用 ALTER TABLE 來延長 ActualPC ,從 10個字符到15個字符。

mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);

現在 tt.ActualPC 和 et.EMPLOYID 都是 VARCHAR(15) 了。再次執行 EXPLAIN 就會得到下面的結果:

table type possible_keys key key_len ref rows Extra tt ALL AssignedPC, NULL NULL NULL 3872 UsingClientID, whereActualPC do ALL PRIMARY NULL NULL NULL 2135Range checked for each record (index map: 0x1) et_1 ALL PRIMARY NULL NULL NULL 74Range checked for each record (index map: 0x1) et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1

這依然不夠完美,但是也稍微好了點:rows 的乘積少了 74 倍(譯者注:et 表的 rows 由 74 變為了 1)。這一版的執行會在幾秒鐘完成。

第二處修改可以針對 tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.SUTNMBR 這兩個比較中有關列長度不匹配的問題。

mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),MODIFY ClientID VARCHAR(15);

這次修改之后,EXPLAIN 輸出就會變成下面這樣:

table type possible_keys key key_len ref rows Extra et ALL PRIMARY NULL NULL NULL 74 tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 UsingClientID, whereActualPC et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

此時,查詢幾乎已經優化的足夠好了。遺留的問題是,默認情況下,MySQL 假設 tt.ActualPC 字段上的值是均勻分布的,但 tt 表并不是這樣的(前面的假設)。幸運的是,要告訴 MySQL 分析列值分布情況是非常簡單的,你只需要這樣做:

mysql> ANALYZE TABLE tt;

憑借額外的索引信息,連接查詢已經變得完美,EXPLAIN 也變成了如下結果:

table type possible_keys key key_len ref rows Extra tt ALL AssignedPC NULL NULL NULL 3872 UsingClientID, whereActualPC et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1 et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1 do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1

EXPLAIN輸出中的rows列是來自MySQL連接優化器的猜測。通過將 rows 的乘積與查詢返回的實際行數進行比較,就可以檢查這些數字是否接近實際情況。如果數字與實際查詢的行數相差甚遠,你可以通過在你的 SELECT 語句中使用 STRAIGHT_JOIN 并嘗試在 FROM 子句中以不同的順序羅列所查各表來獲取更好的性能。(但是,STRAIGHT_JOIN 可能會妨礙到索引的使用,因為它禁用了半連接轉換。參考:Section?8.2.2.1, “Optimizing Subqueries, Derived Tables, and View References with Semijoin Transformations”.)

在某些情況下,當EXPLAIN SELECT與子查詢一起使用時,可以執行修改數據的語句。參考:Section?13.2.10.8, “Derived Tables”.

總結

這篇譯文翻譯了很長時間,斷斷續續可能有一個月。本篇文章有些地方可能翻譯的并不準確,因此希望各位可以與原文比較閱讀,增加理解。

另外,本來想在 Extra 部分就結束本篇翻譯,沒想到 MySQL 官網在最后一節給出了一個非常親民的案例講解,可以讓我們一覽 EXPLAIN 的常規用法。這一部分也是我認為翻譯的比較準確的部分。

因為 EXPLAIN 語句非常重要,因此,這篇譯文我也會經常翻閱,加深理解的同時不斷糾正文中翻譯的不準確或有所偏頗之處,同時希望大家能給予意見或建議。

2020-05-29 追加的部分,分散在文章的各個小節中,主要是在讀完《高性能MySQL(第三版)》的五六章,以及附錄EXPLAIN的部分,對執行計劃和一些索引的概念有了更進一步的理解和認識。之前翻譯的不是很準確的地方做了校對和潤色,某些廢話也是能刪就刪,我還寫了很多關于索引及查詢優化相關的文章,可以和這些文章一起閱讀,結合實踐并反復回看的話,相信一定可以成為MySQL性能優化領域的好手。

?

總結

以上是生活随笔為你收集整理的MySQL 优化 —— EXPLAIN 执行计划详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。