最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

【七千字】教你如何用MySQL分析查詢語句Explain

2023-04-19 13:59 作者:汔源  | 我要投稿

分析查詢語句:EXPLAIN

1概述

定位了查詢慢的SQL之后,就可以使用EXPLAIN或者DESCRIBE工具做針對(duì)性的分析查詢。兩者使用方法相同,并且分析結(jié)果也是相同的。

MySQL中有專門負(fù)責(zé)SQL語句優(yōu)化的優(yōu)化器模塊,主要功能是計(jì)算分析系統(tǒng)中收集到的統(tǒng)計(jì)信息,為客戶端請(qǐng)求的Query提供它最優(yōu)的執(zhí)行計(jì)劃(它認(rèn)為的最優(yōu)數(shù)據(jù)檢索方案畢竟是自動(dòng)分析成的,所以不一定是DBA或者開發(fā)人員認(rèn)為的最優(yōu)方案

這個(gè)執(zhí)行計(jì)劃展示了接下來進(jìn)行具體查詢的方式,比如多表連接的順序、對(duì)每個(gè)表采用什么方式進(jìn)行具體的查詢等等,MySQL提供的EXPLAIN語句可以用來查詢某個(gè)查詢語句的具體執(zhí)行計(jì)劃,根據(jù)EXPLAIN語句的輸出項(xiàng),可以有針對(duì)性地提升查詢SQL的性能。

能查到什么?

  • 表的讀取順序

  • 數(shù)據(jù)讀取操作的操作類型

  • 哪些索引可以被使用

  • 哪些索引實(shí)際被使用

  • 表之間的引用關(guān)系

  • 每張表有多少行被優(yōu)化器查詢

版本區(qū)別

  • MySQL5.6.3之前只能使用EXPLAIN SELECT,之后可以使用EXPLAIN SELECT, UPDATE, DELETE

  • 5.7之前的版本,想要查看partitions(分區(qū))和filtered需要使用EXPLAIN partitions、filtered,而5.7之后直接默認(rèn)顯示

數(shù)據(jù)準(zhǔn)備

創(chuàng)建表

CREATE TABLE s1 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2(key2), INDEX idx_key3(key3), INDEX idx_key_part(key_part1, key_part2, key_part3) )ENGINE=INNODB CHARSET=utf8CREATE TABLE s2 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2(key2), INDEX idx_key3(key3), INDEX idx_key_part(key_part1, key_part2, key_part3) )ENGINE=INNODB CHARSET=utf8

創(chuàng)建存儲(chǔ)函數(shù)

-- 函數(shù)返回隨機(jī)字符串 DELIMITER //CREATE FUNCTION `rand_string`(n INT) RETURNS varchar(255) CHARSET utf8mb4BEGIN DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; DECLARE return_str VARCHAR(255) DEFAULT ''; DECLARE i INT DEFAULT 0; WHILE i < n DO ? ? ? SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1)); ? ? ? SET i = i + 1; ? ?END WHILE; ? ?RETURN return_str; END //DELIMITER ;

首先要確保相信函數(shù)的變量
log_bin_trust_function_creators為1

SELECT @@log_bin_trust_function_creators variable;SET GLOBAL log_bin_trust_function_creators = 1;

存儲(chǔ)過程

向s1、s2表添加數(shù)據(jù)的存儲(chǔ)過程

DELIMITER //CREATE PROCEDURE insert_s1 (IN min_num INT (10), IN max_num INT(10))BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO s1 VALUES( (min_num + i), rand_string(6), (min_num + 30* i + 5), rand_string(6), rand_string(10), rand_string(5), rand_string(10), rand_string(10) ); UNTIL i = max_num END REPEAT; COMMIT;END // DELIMITER; DELIMITER //CREATE PROCEDURE insert_s2 (IN min_num INT (10), IN max_num INT(10))BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO s1 VALUES( (min_num + i), rand_string(6), (min_num + 30* i + 5), rand_string(6), rand_string(10), rand_string(5), rand_string(10), rand_string(10) ); UNTIL i = max_num END REPEAT; COMMIT;END // DELIMITER;

執(zhí)行存儲(chǔ)過程添加數(shù)據(jù)

CALL insert_s1(10001, 10000);CALL insert_s2(10001, 10000);

Explain的輸出列


列名

描述

id

在一個(gè)大的查詢語句中每個(gè)SELECT關(guān)鍵字都對(duì)應(yīng)著一個(gè)唯一的id

select_type

SELECT關(guān)鍵字對(duì)應(yīng)查詢的類型

table

表名

partitions

匹配的分區(qū)信息

type

針對(duì)單表的訪問方法

possible_keys

可能使用到的索引

key

實(shí)際使用的索引

key_len

實(shí)際使用到的索引長(zhǎng)度

ref

當(dāng)使用索引列等值查詢的時(shí)候,與索引列進(jìn)行等值匹配的對(duì)象信息

rows

預(yù)估需要讀取的記錄條數(shù)

filtered

某個(gè)表經(jīng)過搜索條件過濾后剩余記錄條數(shù)的百分比

Extra

一些額外的信息

1 id

id,在一個(gè)大的查詢語句中每個(gè)SELECT關(guān)鍵字都對(duì)應(yīng)著一個(gè)唯一的id,所以有幾個(gè)select關(guān)鍵字就會(huì)有幾個(gè)id:

EXPLAIN SELECT * FROM s1


EXPLAIN SELECT * FROM s1 INNER JOIN s2


上面的兩個(gè)SQL都只有一個(gè)select所以只有一個(gè)id

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a'

子查詢有兩個(gè)select,所以對(duì)應(yīng)兩個(gè)id1和2


查詢優(yōu)化器可能會(huì)對(duì)涉及子查詢的查詢語句進(jìn)行重寫

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a')


優(yōu)化器在看到子查詢后判斷能夠變?yōu)槎啾磉B接以降低復(fù)雜度(O(n^2) -> O(n)):

SELECT * FROM s1, s2 ON s1.key1 = s2.key2 WHERE s2.common_field = 'a'

重寫后的sql變成了一個(gè)select,所以查詢結(jié)果仍然是一個(gè)id

但是如果s2查的是key1,就會(huì)變成下面這樣:

EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE common_field = 'a')


UNION去重

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;


union由于去重操作會(huì)使用到中間表,所以會(huì)有一個(gè)table<union, 1, 2>

但是我這里的臨時(shí)表也有id = 3,看康師傅視頻是沒有的,是版本的問題嗎?也就是還對(duì)中間表進(jìn)行了select

如果使用的是UNION ALL不進(jìn)行去重,則是:

EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;


小結(jié)

  • id如果相同,則會(huì)被認(rèn)為是同一組查詢,會(huì)按照從上往下的順序執(zhí)行

  • 如果不同,則id越大的優(yōu)先級(jí)越高,越先執(zhí)行

  • id的號(hào)碼表示一趟獨(dú)立的查詢,一個(gè)sql的查詢趟數(shù)越少越好

2 select_type

?一個(gè)大的查詢里面可以包含多個(gè)select關(guān)鍵字,每個(gè)select關(guān)鍵字代表一個(gè)小的查詢語句,而每個(gè)小的查詢中都包含著若干的表進(jìn)行連接操作,而每一張表都對(duì)應(yīng)著EXPLAIN查詢計(jì)劃的一條記錄,對(duì)于在同一個(gè)select關(guān)鍵字的表來說,他們的id是相同的。

select_type:SELECT關(guān)鍵字對(duì)應(yīng)查詢的類型,即我們只要知道了某個(gè)小查詢的select_type屬性,就能知道這個(gè)小查詢?cè)诖蟛樵冎邪缪莸慕巧⑵鸬降淖饔?/strong>

常見的select_type:

  • SIMPLE:不包含UNION或者子查詢的查詢都算是SIMPLE類型

  • UNION、PRIMARY、UNION RESULT:對(duì)于包含UNION和UNION ALL的語句,它是由幾個(gè)小的查詢組成的,除了最左邊的查詢的select_type是PRIMARY,其余的均為UNION,而針對(duì)臨時(shí)表的select則是UNION RESULT


  • SUBQUERY:如果包含子查詢的查詢語句不能夠轉(zhuǎn)化為semi-join的方式(即優(yōu)化器將子查詢優(yōu)化為表連接),并且子查詢不是相關(guān)子查詢(即用到了外表的子查詢),則該子查詢的第一個(gè)select關(guān)鍵字代表的那個(gè)查詢的select_type就是SUBQUERY

  • explain?select?*?from?s1?where?key1?in?(select?key1?from?s2)?or?key3?=?'a'


  • 首先這個(gè)子查詢不是相關(guān)子查詢,那么這個(gè)sql能不能優(yōu)化成表連接的sql呢?

  • select?*?from?s1?INNER?JOIN?s2?on?s1.key1?=?s2.key1

  • 答案是不能,這兩個(gè)sql是不同的:比如s1表中有一個(gè)key1值,s2表有兩個(gè)重復(fù)的key1值,則第一條語句由于是in,所以只會(huì)匹配一次,而第二條sql是等于號(hào),所以這種情況下會(huì)匹配兩次,從而二個(gè)sql得到的結(jié)果是完全不同的,因此這個(gè)sql會(huì)用到兩個(gè)select,也就出現(xiàn)兩個(gè)id了,一個(gè)select為Primary,子查詢的select為subquery。

  • DEPENDENT SUBQUERY:如果包含子查詢的查詢語句不能夠轉(zhuǎn)化為semi-join的方式,但是子查詢涉及到了外表,也就是為相關(guān)子查詢,那么該子查詢的第一個(gè)select關(guān)鍵字代表的那個(gè)查詢的select_type就是DEPENDENT SUBQUERY

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?IN?(SELECT?key1?from?s2?WHERE?s1.key2?=?s2.key2)?OR?key3?=?'a'


  • select_type 為 DEPENDENT SUBQUERY 的查詢可能會(huì)被執(zhí)行多次

  • DEPENDENT UNION:在包含UNION和UNION ALL的大查詢中,如果各個(gè)小查詢都依賴于外層查詢的話,那除了最左邊的小查詢之外,其余查詢的select_type均為DEPENDENT UNION

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?IN?(SELECT?key1?FROM?s2?WHERE?key1?=?'a'?UNION?SELECT?key1?FROM?s1?WHERE?key1?=?'b')


  • 第二個(gè)子查詢UNION加上了DEPENDENT 好理解,因?yàn)橛玫搅送獗?/p>

  • 但是,為什么第一個(gè)子查詢沒有用到外表,也是DEPENDENT SUBQUERY呢?

  • 這是由于優(yōu)化器對(duì)于in的改動(dòng):

  • where?exists?(s1.key1 = s2.key1 ...),這樣就變?yōu)榱讼嚓P(guān)子查詢,至于為啥這么做完全不知道了。。

  • DERIVED:派生表對(duì)應(yīng)子查詢的select_type為DERIVED

  • EXPLAIN?SELECT?*?FROM?(SELECT?key1, count(*)?AS?c?FROM?s1?GROUP?BY?key1)?AS?derived_s1?WHERE?c >?1


  • <drived2>即為id為2的派生表

  • MATERIALIZED(物化):當(dāng)查詢優(yōu)化器在執(zhí)行包含子查詢語句的時(shí)候,選擇將子查詢之后與外層查詢進(jìn)行連接時(shí),該子查詢對(duì)應(yīng)的select_type就是MATERIALIZED

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?IN?(SELECT?key1?FROM?s2)


  • SELECT key1 FROM s2的結(jié)果是一個(gè)個(gè)的記錄然后與外表進(jìn)行連接,則這些記錄就可以被稱作是物化表,查詢方式為MATERIALIZED

  • 而外層select直接將子查詢成的物化表看做普通的表,查詢方式為SIMPLE

  • 這個(gè)和上面的非相關(guān)子查詢有點(diǎn)像,后面添加了一個(gè)or key3 = 'a',非相關(guān)子查詢就變成物化表了???

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?IN?(SELECT?key1?FROM?s2)?or?key3?=?'a'


3 table

table,即表名

  • 查詢出來的每一行記錄都對(duì)應(yīng)著一個(gè)單表

  • EXPLAIN?SELECT?*?FROM?s1


  • EXPLAIN?SELECT?*?FROM?s1, s2


  • 可以看到兩個(gè)記錄的id是一樣的,因?yàn)閷儆谕粋€(gè)大的查詢語句(只有一個(gè)select)

  • 并且s2排在s1的前面,所以s2是驅(qū)動(dòng)表,s1是被驅(qū)動(dòng)表(并不能根據(jù)sql語句判斷,因?yàn)閟ql的順序有可能被優(yōu)化器優(yōu)化修改)

4 partitions

  • 代表分區(qū)表中的命中情況,非分區(qū)表,該值為NULL,一般情況下我們查詢語句執(zhí)行計(jì)劃的partitions列的值也都是NULL


5 type

執(zhí)行計(jì)劃的一條記錄就代表著MySQL對(duì)某個(gè)表的執(zhí)行查詢時(shí)的訪問方法,又稱訪問類型,即這里的type。比如,type是ref,表名mysql將使用ref方法對(duì)改行記錄的表進(jìn)行查詢。

完整的訪問方法如下:system?>?const?>?eq_ref?>?ref?> fulltext > ref_or_null > index_merge > unique_subquery > index_subquery >?range?>?index?> all,越靠前代表效率越高

?SQL性能優(yōu)化的目標(biāo):至少要達(dá)到range級(jí)別,要求是ref級(jí)別,最好是const級(jí)別。

  • system:當(dāng)表中只有一條記錄,并且該表使用的存儲(chǔ)引擎的統(tǒng)計(jì)數(shù)據(jù)是精確的,比如MyISAM、Memory,那么對(duì)該表的訪問方法就是system

  • CREATE?TABLE?t(i?INT) ENGINE=MYISAM;?INSERT?INTO?t?VALUES(1); EXPLAIN?SELECT?*?FROM?t


  • 存儲(chǔ)引擎的統(tǒng)計(jì)數(shù)據(jù)是精確的,意思是例如MyISAM存儲(chǔ)存儲(chǔ)引擎有記錄的記錄的個(gè)數(shù)

  • system是性能最高的情況

  • 而如果再添加一條記錄,會(huì)變?yōu)閍ll,而InnoDB即使一條數(shù)據(jù)也是all


  • 于此同時(shí),INNODB訪問count()的數(shù)據(jù)也是all的

  • CREATE?TABLE?tt(i?INT) ENGINE=INNODB;?INSERT?INTO?tt?VALUES(1); EXPLAIN?SELECT?count(*)?FROM?tt


  • const:當(dāng)根據(jù)主鍵或者唯一的二級(jí)索引與常數(shù)進(jìn)行等值匹配的時(shí)候,對(duì)單表的訪問就是const,表示常數(shù)級(jí)別

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?id?=?10005; EXPLAIN?SELECT?*?FROM?s1?WHERE?key2?=?10066;


  • 如果是key3,則為all

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key3?=?1006;


  • 這里其實(shí)牽扯到隱式轉(zhuǎn)換導(dǎo)致索引失效的問題:由于key3是varchar類型的,但是這里是數(shù)字進(jìn)而進(jìn)行了函數(shù)轉(zhuǎn)換,進(jìn)而索引失效只能all查詢了

  • eq_ref:在連接查詢的時(shí)候,如果被驅(qū)動(dòng)表是通過主鍵或者唯一的二級(jí)索引等值匹配的方式進(jìn)行訪問的(如果主鍵或者唯一的二級(jí)索引是聯(lián)合索引,則要求索引的每一列進(jìn)行聯(lián)合匹配),則對(duì)于該被驅(qū)動(dòng)表的訪問方式就是eq_ref

  • EXPLAIN?SELECT?*?from?s1?INNER?JOIN?s2?WHERE?s1.key2?=?s2.key2


  • key2是帶有唯一約束的二級(jí)索引,因此被驅(qū)動(dòng)表s2的訪問方式為eq_ref

  • 其中ref表示查詢的值已經(jīng)被指定:即通過all方式查詢的s1表指定的

  • ref:當(dāng)通過普通的二級(jí)索引與常量進(jìn)行等值匹配來查詢某個(gè)表,對(duì)該表的訪問方式可能是ref

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key3?=?'CUTLVwqweqweq';


  • 這里key3就是沒有唯一約束的普通索引,可以看到用到了索引key3,因此type為ref

  • ref_or_null:當(dāng)通過普通的二級(jí)索引與常量進(jìn)行等值匹配來查詢某個(gè)表,當(dāng)該值也可能是null值時(shí),那么對(duì)該 表的訪問方式可能就是ref_not_null

  • EXPLAIN SELECT * FROM s1?WHERE?key3?=?'CUTLVwqweqweq'?OR key3 IS NULL;


  • index_merge:?jiǎn)伪碓L問在某些情況下可以使用Intersection、Union、Sort-Union這三種索引合并的方式來執(zhí)行查詢

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?=?'a'?OR?key2?=?123131


  • key1和key2均為索引列,一個(gè)Select關(guān)鍵字只能使用一個(gè)索引,所以這里使用了合并索引為一個(gè)虛擬索引的辦法,相當(dāng)于掃描兩個(gè)索引樹取出主鍵并取并集再回表的操作

  • 但是,如果是AND的情況,只會(huì)使用一個(gè)索引(這里是唯一的二級(jí)索引。故而是const)

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?=?'rCLXEg'?AND?key2?=?10036


  • unique_subquery:是針對(duì)一些包含IN子查詢的查詢語句中,如果查詢優(yōu)化器決定將In子查詢語句變?yōu)镋XISTS子查詢,并且子查詢可以使用到主鍵的等值匹配的話,那么子查詢的type就是unique_subquery

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key2?IN?(SELECT?id?FROM?s2?WHERE?s1.key1?=?s2.key1)?OR?key3?=?'a'


  • range:如果使用索引獲取某些范圍區(qū)間的記錄,就可能使用到range方法

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?IN?('a',?'b',?'c')


  • 非索引列則為all

  • index:當(dāng)可以使用索引覆蓋,并且需要掃描全部的索引記錄,該表的訪問方法就是index

  • EXPLAIN?SELECT?key_part2?FROM?s1?WHERE?key_part3?=?'a'


  • 可以看到在key里面還是用到了聯(lián)合索引的,盡管根據(jù)最左前綴原則,只有檢索條件為key_part1才能用到索引,這里是因?yàn)?strong>檢索條件select返回列都是和聯(lián)合索引相關(guān)的列,所以使用了聯(lián)合索引掃描了全部的索引記錄因?yàn)檫@樣就不需要再回表找其他的列了(查的列都在索引上)

  • 不需要回表就能查找到所需要的數(shù)據(jù),稱作索引覆蓋

  • 這時(shí)候再添加一個(gè)其他的列:

  • EXPLAIN?SELECT?key1, key_part2?FROM?s1?WHERE?key_part3?=?'a'

  • 結(jié)果為ALL,因?yàn)槁?lián)合索引列上沒有key1的信息,需要回表去查key1

  • all:全表掃描

6 possible_key 和 key

在EXPLAIN語句輸出的執(zhí)行計(jì)劃中,possible_key?表示在單表查詢中可能會(huì)用到的索引,一般查詢涉及到的字段上存在索引,則該索引就將被列出,但不一定被查詢使用。

?key則表示經(jīng)過查詢優(yōu)化器計(jì)算使用不同索引的查詢成本之后,最終確定使用的索引。

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a'


key1和key3均為普通的二級(jí)索引,但是key3是等值匹配因此耗費(fèi)的成本較低,所以最終選擇使用索引key3

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' OR key3 = 'a'

而如果這里改成OR,則會(huì)演變成之前講的?index_merge 合并索引,即將兩個(gè)索引樹的主鍵提取取并集,然后統(tǒng)一到聚簇索引中執(zhí)行一次回表操作


EXPLAIN SELECT key1, key3 FROM s1 WHERE key1 > 'z' OR key3 = 'a'

再擴(kuò)展一下,即使查詢列可以使用覆蓋索引(即查詢列的值都可以在索引樹中找到),仍然需要進(jìn)行一次回表操作,因此兩個(gè)查詢的執(zhí)行計(jì)劃是相同的:


7 index_len (聯(lián)合索引分析)

?實(shí)際使用到的索引的長(zhǎng)度(即字節(jié)數(shù)),用來查看是否充分利用了索引,index_len的值越大越好

這里的越大越好是跟自己進(jìn)行的比較,因?yàn)橹饕轻槍?duì)的聯(lián)合索引,因?yàn)槔寐?lián)合索引的長(zhǎng)度越大,查詢需要讀入的數(shù)據(jù)頁就越少,效率也就越高

EXPLAIN SELECT * FROM s1 WHERE id = 10005


為什么是4:因?yàn)閕d列是int型所以真實(shí)數(shù)據(jù)占4個(gè)字節(jié),同時(shí)行格式中主鍵非空因此不需要NULL值列表,定長(zhǎng)不需要變長(zhǎng)字段長(zhǎng)度列表,故而是4

EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;


key2是int類型,占4個(gè)字節(jié),并且具有唯一性約束但是可能為空,因此行格式中null值列表占1個(gè)字節(jié),總共5個(gè)字節(jié)

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';

首先key1是varchar(100),并且表是utf8mb3格式的,因此真實(shí)數(shù)據(jù)存儲(chǔ)占(100 * 3) = 300個(gè)字節(jié),本身定長(zhǎng)所以行格式的變長(zhǎng)字段長(zhǎng)度列表占2個(gè)字節(jié),NULL值列表占1個(gè)字節(jié),共計(jì)303個(gè)字節(jié)

同理下面的查詢一個(gè)為303,另一個(gè)是606,這時(shí)候才體現(xiàn)出key_len的作用:第二個(gè)sql比第一個(gè)sql利用聯(lián)合索引更加充分

EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';

8 ref

?ref表示當(dāng)我們使用索引列等值查詢的時(shí)候,與索引列進(jìn)行等值匹配的對(duì)象的信息

EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';


key1是普通的二級(jí)索引,所以type是ref(唯一的二級(jí)索引是const),而等值的匹配類型是一個(gè)常量,因此ref列的值是const

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;


由于是表連接,所以只有一個(gè)select id,然后由于是主鍵進(jìn)行的連接,所以對(duì)于第二個(gè)表的訪問方式type是eq_ref(普通索引則為ref),同時(shí)等值比較的是s1的列,因此ref為atguigu1.s2.id

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);


key1是普通的二級(jí)索引,因此type為ref,等值比較的類型是一個(gè)函數(shù)返回值,因此ref列的值為func

9 rows

?rows:預(yù)估需要讀取的記錄條數(shù),值越小越好

值越小表示在同一個(gè)數(shù)據(jù)頁中的可能性越大,IO的次數(shù)也就越少

10 filtered (結(jié)合rows分析)

?filtered:表示某個(gè)表經(jīng)過條件過濾之后,剩余記錄條數(shù)的百分比,值越大越好

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';


如上表示經(jīng)過條件過濾后,100%都是符合要求的

值越大越好的原因:假設(shè)條件過濾后是40條記錄,如果filtered是100%,則原來有40條,如果filtered是10%,則原來有400條,相比之下40條需要讀取的數(shù)據(jù)頁要少一些

?而如果執(zhí)行的是索引的單表掃描,那么計(jì)算的時(shí)候除了估計(jì)出滿足對(duì)應(yīng)索引的搜索條件,還應(yīng)計(jì)算同時(shí)滿足其他條件的記錄是多少條

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'b';


如上面的sql,rows303表示預(yù)估滿足索引列key1需要讀取的記錄數(shù),而filtered表示加上common_field字段后預(yù)估讀取占全部的百分比

?對(duì)于單表查詢這個(gè)filtered列其實(shí)沒有太大作用,但是它在多表連接中驅(qū)動(dòng)表對(duì)應(yīng)的執(zhí)行計(jì)劃記錄的filtered值,決定了被驅(qū)動(dòng)表的執(zhí)行次數(shù)

EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';


首先多表連接查詢所以為同一個(gè)select id,其次連接條件是普通的二級(jí)索引,所以驅(qū)動(dòng)表的訪問類型type為all,被驅(qū)動(dòng)表的訪問類型type為ref,最后s1表預(yù)估讀取的記錄數(shù)rows為10152,再經(jīng)過條件過濾10152 * 10%和s2做等值匹配,因此1015就是s2表的執(zhí)行次數(shù)

11 Extra

Extra用來說明一些不適合在其他列中展示但是十二分重要的額外信息。通過這些額外信息可以更準(zhǔn)確地知道m(xù)ysql導(dǎo)致是怎么執(zhí)行給定的查詢語句的。

  • no tables used:沒有from字句,即沒有用到表的情況

  • EXPLAIN?select?1


  • impossible where:where語句永遠(yuǎn)為false的情況

  • EXPLAIN?select?*?FROM?s1?WHERE?1?!=?1


  • 這樣也沒有用到表,反正條件都不對(duì)

  • where:使用全表掃描來執(zhí)行針對(duì)某個(gè)表的查詢,字句中有針對(duì)該表的搜索條件,則在Extra中展現(xiàn)

  • EXPLAIN?select?*?FROM?s1?WHERE?common_field?=?'a'


  • common_field是一個(gè)不帶索引的普通字段,因此type為all,Extra展現(xiàn)了語句是通過where執(zhí)行的

  • no matching min/max row當(dāng)查詢列表處有min或者max聚合函數(shù),但是沒有符合where條件的記錄時(shí),將會(huì)提示該額外信息

  • ?EXPLAIN?SELECT?MIN(key1)?FROM?s1?WHERE?key1?=?'adqwdqweqwe'


  • 而當(dāng)where條件符合(或者根本沒有where條件的時(shí)候),則顯示Select tables optimized away,表示選擇優(yōu)化后的表

  • EXPLAIN?SELECT?MIN(key1)?FROM?s1


  • using index:當(dāng)出現(xiàn)索引覆蓋,即查詢和檢索條件的列都在使用的索引里面,也即是不需要回表操作的情況

  • EXPLAIN?SELECT?key1?FROM?s1?WHERE?key1?=?'a'


  • 當(dāng)出現(xiàn)主鍵的情況也是覆蓋索引

  • using index condition:即索引條件下推,考慮下面的sql查詢:

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?>?'z'?AND?key1?like?'%a%'


  • 這條sql執(zhí)行的正常順序應(yīng)該是:首先使用idx_key1的索引樹,查詢key1 > z的所有主鍵值,這里找到了385條記錄的主鍵,然后對(duì)這些主鍵進(jìn)行回表操作,在聚簇索引中找到包含其他列的數(shù)據(jù),然后判斷剩下的過濾條件進(jìn)行返回。

  • 而索引條件下推針對(duì)特殊情況進(jìn)行了優(yōu)化:就是如果剩余的過濾條件針對(duì)的是索引列,則不需要在回表后進(jìn)行判斷,這樣就能夠減少回表的操作,但是rows仍為385

  • using join buffer:即基于塊的嵌套循環(huán)算法:當(dāng)被驅(qū)動(dòng)表不能夠有效利用索引加快訪問速度,mysql就會(huì)為其在內(nèi)存中分配一塊join buffer的內(nèi)存塊來加快訪問的速度

  • EXPLAIN?SELECT?*?FROM?s1?INNER?JOIN?s2?ON?s1.common_field?=?s2.common_field


  • common_field是一個(gè)沒有索引的列

  • not exists:在表連接的時(shí)候,當(dāng)where條件中被驅(qū)動(dòng)表的某個(gè)列等于null,而這個(gè)列又有非空約束的時(shí)候,Extra就會(huì)展現(xiàn)not exists

  • EXPLAIN?SELECT?*?FROM?s1?LEFT?JOIN?s2?on?s1.key1?=?s2.key1?WHERE?s2.id?IS?NULL


  • 注意一定是被驅(qū)動(dòng)表的列,如果是主驅(qū)動(dòng)表出現(xiàn)這種情況,會(huì)直接顯示為impossible where,就不會(huì)再看被驅(qū)動(dòng)表了

  • using union(index_merge):or使用兩個(gè)索引的情況,即前面type講到的index_merge,這時(shí)候會(huì)將兩個(gè)索引樹查出的id取并集然后再回表在進(jìn)行where條件過濾

  • EXPLAIN?SELECT?*?FROM?s1?WHERE?key1?=?'a'?OR?key3?=?'a'


  • zero limit:limit為0的情況

  • file sort 文件排序:

    • 有一些情況排序是能夠用到索引的:

    • EXPLAIN?SELECT?*?FROM?s1?ORDER?BY?key1 LIMIT?10;


    • 這個(gè)查詢利用idx_key1索引直接取出key1列的10條記錄(按照索引列排序的),然后再拿著記錄的主鍵值進(jìn)行回表得到全部列的值。但是更多情況下的排序操作無法利用到索引,只能在內(nèi)存中(記錄較少的情況)或者磁盤中進(jìn)行排序,mysql把這種在內(nèi)存或者磁盤中排序的方式統(tǒng)稱為文件排序 file sort

    • 但是這里有個(gè)地方很不理解,為什么去掉limit或者limit較大的時(shí)候,就會(huì)變成文件排序?

    • EXPLAIN?SELECT?*?FROM?s1?ORDER?BY?key1 LIMIT?97;


    • 個(gè)人猜測(cè):有一個(gè)地方需要注意,就是隨著limit的增大rows也在增大,尤其是在limit為95左右的時(shí)候突然增大了很多,這是不是因?yàn)椋簂imit較小的時(shí)候,通過索引順序得到的主鍵值也比較集中,這時(shí)候回表操作也是順序查詢的級(jí)別,但是limit過大甚至沒有的時(shí)候,主鍵值就會(huì)特別分散(因?yàn)槭前凑誯ey1索引列排序的,所以key1集中而主鍵值分散),因此這時(shí)候回表讀取操作相當(dāng)于是隨機(jī)查找的級(jí)別了,那這樣查詢優(yōu)化器判斷成本后,還不如直接在內(nèi)存或者磁盤中進(jìn)行文件排序。

    • 對(duì)于沒有索引的查詢,自然只能文件排序了:

    • EXPLAIN?SELECT?*?FROM?s1?ORDER?BY?common_field LIMIT?10;


  • using temporary:mysql在進(jìn)行一些如去重、排序的功能的時(shí)候,如果不能夠有效地利用索引,就可能需要通過建立內(nèi)部的臨時(shí)表來完成。

  • EXPLAIN?SELECT?DISTINCT?common_field?FROM?s1;


  • 執(zhí)行計(jì)劃中出現(xiàn)臨時(shí)表不是一個(gè)很好的征兆,因?yàn)榻⒑途S護(hù)臨時(shí)表都需要很大的成本,應(yīng)該盡量通過使用索引來替換臨時(shí)表

小結(jié)

  • Explain不考慮Cache(不考慮記錄的加載方式,只是考量sql語句)

  • Explain不能顯示mysql在執(zhí)行查詢時(shí)做的優(yōu)化工作

  • Explain不會(huì)顯示關(guān)于觸發(fā)器、存儲(chǔ)過程或用戶自定義函數(shù)對(duì)于查詢的影響

  • 部分信息是估算的,并非精確的值

Explain的進(jìn)一步使用

Explain的四種輸出格式

Explain的四種輸出格式:傳統(tǒng)格式、Json格式、Tree格式、可視化格式

1 傳統(tǒng)格式

即上面一直在使用的EXPLAIN語句,概要說明查詢計(jì)劃

2 JSON格式

?傳統(tǒng)的EXPLAIN語句的輸出缺少了一個(gè)衡量執(zhí)行計(jì)劃好壞的重要屬性--成本。JSON格式是四種格式里面信息最詳盡的格式,包含了執(zhí)行的成本信息。 接下來對(duì)比一下傳統(tǒng)和JSON格式的EXPLAIN:

EXPLAIN SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'


EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'{ ?"query_block": { ? ?"select_id": 1, // 原來的id ? ?"cost_info": { ? ? ?"query_cost": "1394.77" // 查詢成本 ? ?}, ? ?"nested_loop": [ ? ? ?{ ? ? ? ?"table": { ? ? ? ? ?"table_name": "s1", // table ? ? ? ? ?"access_type": "ALL", // type ? ? ? ? ?"possible_keys": [ ? ? ? ? ? ?"idx_key1" ? ? ? ? ?], ? ? ? ? ?"rows_examined_per_scan": 10152, // rows ? ? ? ? ?"rows_produced_per_join": 1015, // rows * filtered ? ? ? ? ?"filtered": "10.00", ? ? ? ? ?"cost_info": { ? ? ? ? ? ?"read_cost": "937.93", ? ? ? ? ? ?"eval_cost": "101.52", ? ? ? ? ? ?"prefix_cost": "1039.45", // read + eval ? ? ? ? ? ?"data_read_per_join": "1M" // 讀取的數(shù)據(jù)量 ? ? ? ? ?}, ? ? ? ? ?"used_columns": [ // 查詢字段 ? ? ? ? ? ?"id", ? ? ? ? ? ?"key1", ? ? ? ? ? ?"key2", ? ? ? ? ? ?"key3", ? ? ? ? ? ?"key_part1", ? ? ? ? ? ?"key_part2", ? ? ? ? ? ?"key_part3", ? ? ? ? ? ?"common_field" ? ? ? ? ?], ? ? ? ? ?"attached_condition": "((`atguigudb1`.`s1`.`common_field` = 'a') and (`atguigudb1`.`s1`.`key1` is not null))" // 查詢條件 ? ? ? ?} ? ? ?}, ? ? ?{ ? ? ? ?"table": { ? ? ? ? ?"table_name": "s2", ? ? ? ? ?"access_type": "eq_ref", ? ? ? ? ?"possible_keys": [ ? ? ? ? ? ?"idx_key2" ? ? ? ? ?], ? ? ? ? ?"key": "idx_key2", ? ? ? ? ?"used_key_parts": [ ? ? ? ? ? ?"key2" ? ? ? ? ?], ? ? ? ? ?"key_length": "5", ? ? ? ? ?"ref": [ ? ? ? ? ? ?"atguigudb1.s1.key1" ? ? ? ? ?], ? ? ? ? ?"rows_examined_per_scan": 1, ? ? ? ? ?"rows_produced_per_join": 1015, ? ? ? ? ?"filtered": "100.00", ? ? ? ? ?"index_condition": "(cast(`atguigudb1`.`s1`.`key1` as double) = cast(`atguigudb1`.`s2`.`key2` as double))", ? ? ? ? ?"cost_info": { ? ? ? ? ? ?"read_cost": "253.80", ? ? ? ? ? ?"eval_cost": "101.52", ? ? ? ? ? ?"prefix_cost": "1394.77", ? ? ? ? ? ?"data_read_per_join": "1M" ? ? ? ? ?}, ? ? ? ? ?"used_columns": [ ? ? ? ? ? ?"id", ? ? ? ? ? ?"key1", ? ? ? ? ? ?"key2", ? ? ? ? ? ?"key3", ? ? ? ? ? ?"key_part1", ? ? ? ? ? ?"key_part2", ? ? ? ? ? ?"key_part3", ? ? ? ? ? ?"common_field" ? ? ? ? ?] ? ? ? ?} ? ? ?} ? ?] ?} }

  • read_cost:由兩部分組成:IO成本rows * (1 - filtered)條記錄的CPU成本

  • eval_cost: rows * filtered

3 Tree格式

Tree格式是8.0.16版本之后引入的新格式,主要根據(jù)各個(gè)部分之間的關(guān)系和各個(gè)部分的執(zhí)行順序來描述如何查詢。

EXPLAIN FORMAT=TREE SELECT * FROM s1 INNER JOIN s2 on s1.key1 = s2.key2 WHERE s1.common_field = 'a'-> Nested loop inner join ?(cost=1394.77 rows=1015) ? ?-> Filter: ((s1.common_field = 'a') and (s1.key1 is not null)) ?(cost=1039.45 rows=1015) ? ? ? ?-> Table scan on s1 ?(cost=1039.45 rows=10152) ? ?-> Single-row index lookup on s2 using idx_key2 (key2=s1.key1), with index condition: (cast(s1.key1 as double) = cast(s2.key2 as double)) ?(cost=0.25 rows=1)

4 可視化輸出

需要安裝MySQL workbench


Show Warnings的使用

當(dāng)我們使用Explain語句查看了某個(gè)查詢語句的執(zhí)行計(jì)劃之后,緊接著還可以使用Show warnings來查看與這個(gè)查詢計(jì)劃有關(guān)的一些擴(kuò)展信息,比如:

EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 on s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;


正常來說,我們使用s2 left join s1,那么s2應(yīng)該是驅(qū)動(dòng)表,s1是被驅(qū)動(dòng)表,但是可以看到執(zhí)行計(jì)劃中實(shí)際上是反著的,這是由于優(yōu)化器在判斷兩個(gè)表作為驅(qū)動(dòng)表的執(zhí)行成本的時(shí)候?qū)ql進(jìn)行的優(yōu)化(where語句是針對(duì)的s2),使用show warnings可以看到這種優(yōu)化:

mysql> show warnings \G*************************** 1. row *************************** ?Level: Note ? Code: 1003 Message: /* select#1 */ select `atguigudb1`.`s1`.`key1` AS `key1`,`atguigudb1`.`s2`.`key1` AS `key1` from `atguigudb1`.`s1` join `atguigudb1`.`s2` where ((`atguigudb1`.`s1`.`key1` = `atguigudb1`.`s2`.`key1`) and (`atguigudb1`.`s2`.`common_field` is not null)) 1 row in set (0.00 sec) SQL 復(fù)制 全屏

看著挺別扭,即下面:

select s1.key1, s2.key1from s1 join s2where s1.key1 = s2.key1 and s2.common_field is not null;


【七千字】教你如何用MySQL分析查詢語句Explain的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
肇州县| 乌鲁木齐县| 阜南县| 黑龙江省| 旺苍县| 习水县| 越西县| 泸定县| 丘北县| 泰和县| 玉龙| 孙吴县| 廊坊市| 洪雅县| 博罗县| 司法| 鹤壁市| 海淀区| 宝兴县| 莆田市| 张家港市| 科技| 合江县| 绥中县| 固镇县| 轮台县| 衡南县| 安岳县| 普兰店市| 承德县| 黄平县| 金乡县| 六枝特区| 泰兴市| 双流县| 云龙县| 鸡泽县| 都江堰市| 安图县| 新河县| 和林格尔县|