EXPLAIN描述了 MySQL 是如何計劃,去執(zhí)行給定的查詢的。即這是一個預執(zhí)行的考慮,不要和查詢性能的分析混淆了。
在評估了許多可能的執(zhí)行方式后,EXPLAIN 打印出了最佳的執(zhí)行計劃。
在 “possible_keys” 中有可以簡明地看到其他可能選擇的計劃,但一般來說你需要在 “OPTIMIZER_TRACE” 看這些信息。
我們推薦一直使用EXPLAIN FORMAT=JSON,因為它會打印出查詢代價(默認格式則沒有)。查詢代價很關(guān)鍵,它讓我們可以量化地思考和交流,曾聽到一些數(shù)據(jù)庫從業(yè)者說“創(chuàng)建臨時表不好”、“連表查詢不好”,你很難直接回應(yīng)這些評價,因為缺少必要的上下文信息;水在過多的情況下同樣是對人有害的。
和上面一樣,你也可以用EXPLAIN FORMAT=JSON FOR CONNECTION <connection_id>去看一個運行中的數(shù)據(jù)庫連接是如何最佳化執(zhí)行的。這在診斷偶現(xiàn)的錯誤時是很有用的,因為數(shù)據(jù)和底層參數(shù)的修改會影響執(zhí)行計劃的選擇。
例子1:查詢亞洲中人口多于 500W 的國家,一次全表掃描
EXPLAIN FORMAT=JSON
SELECT * FROM Country WHERE continent='Asia' and population > 5000000;
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "53.80" # 這次查詢的代價
},
"table": {
"table_name": "Country",
"access_type": "ALL", # ALL 即全表掃描
"rows_examined_per_scan": 239, # 訪問了全表 239 行
"rows_produced_per_join": 11,
"filtered": "4.76",
"cost_info": {
"read_cost": "51.52",
"eval_cost": "2.28",
"prefix_cost": "53.80",
"data_read_per_join": "2K"
},
"used_columns": [
...
],
"attached_condition": "((`world`.`Country`.`Continent` = 'Asia') and (`world`.`Country`.`Population` > 5000000))"
}
}
}
例子1 中EXPLAIN 表明了這次查詢會執(zhí)行全表掃描,需要花費 53.80 的代價。信息表明沒有可用的索引 possible_keys(這一項通常就在 access_type 后面)。
rows_examined_per_scan 這個數(shù)字 239 是全表掃描的行數(shù),近似全世界國家的總數(shù),需要指出這個數(shù)字不會是百分之百精確的,因為那樣會影響 EXPLAIN 的性能。
attached_condition 附加狀態(tài)這一項表明在讀取每一行時,會用到過濾器,條件就是“在亞洲中、人口多于 500W”。在有索引的情況下,這些條件在讀取行之前就可以判斷了,但條件被列出來就說明不是這種情況。
我們來對比一下 掃描過的行數(shù)(239) 和 返回結(jié)果的行數(shù)(32),發(fā)送給客戶端的結(jié)果行在執(zhí)行完查詢才能得知。這次查詢中服務(wù)端掃描了是返回結(jié)果 7.5倍 的行數(shù),表明是可以優(yōu)化的。
非常小的比例(甚至 1:1)并不意味著不能優(yōu)化。要追求更好,可以再優(yōu)化分區(qū)或分頁大小。
需要指出,掃描 239 行不算多,而且所有行都可以放進內(nèi)存。但全表掃描還是很消耗內(nèi)存的,而隨著表的增大性能會下降得越來越快。添加索引可以改善性能,并在數(shù)據(jù)增長時維持這種性能。