MySQL執行計划【explain】詳解


本文已經收錄到github倉庫,倉庫用於分享Java相關知識總結,包括Java基礎、MySQL、Springboot、mybatis、Redis、rabbitMQ等等,歡迎大家提pr和star!

github地址:https://github.com/Tyson0314/Java-learning

gitlab地址:https://gitee.com/tysondai/Java-learning

簡介

本文主要講述如何通過 explain 命令獲取 select 語句的執行計划,通過 explain 可以知道 select 語句以下信息:

  • 表的加載順序
  • sql 的查詢類型
  • 可能用到哪些索引,實際上用到哪些索引
  • 讀取的行數
  • ...

explain 執行計划包含字段信息如下:分別是 idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredExtra 12個字段。

通過explain extended + show warnings可以在原本explain的基礎上額外提供一些查詢優化的信息,得到優化以后的可能的查詢語句(不一定是最終優化的結果)。

先搭建測試環境:

CREATE TABLE `blog` (
  `blog_id` int NOT NULL AUTO_INCREMENT COMMENT '唯一博文id--主鍵',
  `blog_title` varchar(255) NOT NULL COMMENT '博文標題',
  `blog_body` text NOT NULL COMMENT '博文內容',
  `blog_time` datetime NOT NULL COMMENT '博文發布時間',
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `blog_state` int NOT NULL COMMENT '博文狀態--0 刪除 1正常',
  `user_id` int NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`blog_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8

CREATE TABLE `user` (
  `user_id` int NOT NULL AUTO_INCREMENT COMMENT '用戶唯一id--主鍵',
  `user_name` varchar(30) NOT NULL COMMENT '用戶名--不能重復',
  `user_password` varchar(255) NOT NULL COMMENT '用戶密碼',
  PRIMARY KEY (`user_id`),
  KEY `name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8

CREATE TABLE `discuss` (
  `discuss_id` int NOT NULL AUTO_INCREMENT COMMENT '評論唯一id',
  `discuss_body` varchar(255) NOT NULL COMMENT '評論內容',
  `discuss_time` datetime NOT NULL COMMENT '評論時間',
  `user_id` int NOT NULL COMMENT '用戶id',
  `blog_id` int NOT NULL COMMENT '博文id',
  PRIMARY KEY (`discuss_id`)
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8

id

表示查詢中執行select子句或者操作表的順序,id的值越大,代表優先級越高,越先執行

explain select discuss_body 
from discuss 
where blog_id = (
    select blog_id from blog where user_id = (
        select user_id from user where user_name = 'admin'));

三個表依次嵌套,發現最里層的子查詢 id最大,最先執行。

explain-id

select_type

表示 select 查詢的類型,主要是用於區分各種復雜的查詢,例如:普通查詢聯合查詢子查詢等。

  1. SIMPLE:表示最簡單的 select 查詢語句,在查詢中不包含子查詢或者交並差集等操作。
  2. PRIMARY:查詢中最外層的SELECT(存在子查詢的外層的表操作為PRIMARY)。
  3. SUBQUERY:子查詢中首個SELECT。
  4. DERIVED:被驅動的SELECT子查詢(子查詢位於FROM子句)。
  5. UNION:在SELECT之后使用了UNION。

table

查詢的表名,並不一定是真實存在的表,有別名顯示別名,也可能為臨時表。當from子句中有子查詢時,table列是 <derivenN>的格式,表示當前查詢依賴 id為N的查詢,會先執行 id為N的查詢。

explain-table

partitions

查詢時匹配到的分區信息,對於非分區表值為NULL,當查詢的是分區表時,partitions顯示分區表命中的分區情況。

explain-partitions

type

查詢使用了何種類型,它在 SQL優化中是一個非常重要的指標。

system

當表僅有一行記錄時(系統表),數據量很少,往往不需要進行磁盤IO,速度非常快。比如,Mysql系統表proxies_priv在Mysql服務啟動時候已經加載在內存中,對這個表進行查詢不需要進行磁盤 IO。

explain-system

const

單表操作的時候,查詢使用了主鍵或者唯一索引。

explain-const

eq_ref

多表關聯查詢的時候,主鍵和唯一索引作為關聯條件。如下圖的sql,對於user表(外循環)的每一行,user_role表(內循環)只有一行滿足join條件,只要查找到這行記錄,就會跳出內循環,繼續外循環的下一輪查詢。

explain-eq_ref

ref

查找條件列使用了索引而且不為主鍵和唯一索引。雖然使用了索引,但該索引列的值並不唯一,這樣即使使用索引查找到了第一條數據,仍然不能停止,要在目標值附近進行小范圍掃描。但它的好處是不需要掃全表,因為索引是有序的,即便有重復值,也是在一個非常小的范圍內做掃描。

explain-ref

ref_or_null

類似 ref,會額外搜索包含NULL值的行。

index_merge

使用了索引合並優化方法,查詢使用了兩個以上的索引。新建comment表,id為主鍵,value_id為非唯一索引,執行explain select content from comment where value_id = 1181000 and id > 1000;,執行結果顯示查詢同時使用了id和value_id索引,type列的值為index_merge。

type-index_merge

range

有范圍的索引掃描,相對於index的全索引掃描,它有范圍限制,因此要優於index。像between、and、'>'、'<'、in和or都是范圍索引掃描。

type為range的情況

index

index包括select索引列,order by主鍵兩種情況。

  1. order by主鍵。這種情況會按照索引順序全表掃描數據,拿到的數據是按照主鍵排好序的,不需要額外進行排序。

    type為index的情況1-orderBy主鍵

  2. select索引列。type為index,而且extra字段為using index,也稱這種情況為索引覆蓋。所需要取的數據都在索引列,無需回表查詢。

    type為index的情況2-select索引列

all

全表掃描,查詢沒有用到索引,性能最差。

explain-all

possible_keys

此次查詢中可能選用的索引。但這個索引並不定一會是最終查詢數據時所被用到的索引

key

此次查詢中確切使用到的索引。

rows

估算要找到所需的記錄,需要讀取的行數。評估SQL 性能的一個比較重要的數據,mysql需要掃描的行數,很直觀的顯示 SQL 性能的好壞,一般情況下 rows 值越小越好。

filtered

存儲引擎返回的數據在經過過濾后,剩下滿足條件的記錄數量的比例。

extra

表示額外的信息說明。為了方便測試,這里新建兩張表。

CREATE TABLE `t_order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int DEFAULT NULL,
  `order_id` int DEFAULT NULL,
  `order_status` tinyint DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_userid_order_id_createdate` (`user_id`,`order_id`,`create_date`)
) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8

CREATE TABLE `t_orderdetail` (
  `id` int NOT NULL AUTO_INCREMENT,
  `order_id` int DEFAULT NULL,
  `product_name` varchar(100) DEFAULT NULL,
  `cnt` int DEFAULT NULL,
  `create_date` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_orderid_productname` (`order_id`,`product_name`)
) ENGINE=InnoDB AUTO_INCREMENT=152 DEFAULT CHARSET=utf8

using where

查詢的列未被索引覆蓋,where篩選條件非索引的前導列。對存儲引擎返回的結果進行過濾(Post-filter,后過濾),一般發生在MySQL服務器,而不是存儲引擎層。

extra-using-where

using index

查詢的列被索引覆蓋,並且where篩選條件符合最左前綴原則,通過索引查找就能直接找到符合條件的數據,不需要回表查詢數據。

extra-using-index

Using where&Using index

查詢的列被索引覆蓋,但無法通過索引查找找到符合條件的數據,不過可以通過索引掃描找到符合條件的數據,也不需要回表查詢數據。

包括兩種情況:

  • where篩選條件不符合最左前綴原則

    extra-using-where&index

  • where篩選條件是索引列前導列的一個范圍

    extra-using-where&index

null

查詢的列未被索引覆蓋,並且where篩選條件是索引的前導列,也就是用到了索引,但是部分字段未被索引覆蓋,必須回表查詢這些字段,Extra中為NULL。

extra-null

using index condition

索引下推(index condition pushdown,ICP),先使用where條件過濾索引,過濾完索引后找到所有符合索引條件的數據行,隨后用 WHERE 子句中的其他條件去過濾這些數據行。

不使用ICP的情況(set optimizer_switch='index_condition_pushdown=off'),如下圖,在步驟4中,沒有使用where條件過濾索引:

no-icp

使用ICP的情況(set optimizer_switch='index_condition_pushdown=on'):

icp

下面的例子使用了ICP:

explain select user_id, order_id, order_status  
from t_order where user_id > 1 and user_id < 5\G;

icp-demo

關掉ICP之后(set optimizer_switch='index_condition_pushdown=off'),可以看到extra列為using where,不會使用索引下推。

no-icp-demo

索引下推參考鏈接:索引下推例子 | 索引下推圖解 |索引下推優化

using temporary

使用了臨時表保存中間結果,常見於 order by 和 group by 中。典型的,當group by和order by同時存在,且作用於不同的字段時,就會建立臨時表,以便計算出最終的結果集。

filesort

文件排序。表示無法利用索引完成排序操作,以下情況會導致filesort:

  • order by 的字段不是索引字段
  • select 查詢字段不全是索引字段
  • select 查詢字段都是索引字段,但是 order by 字段和索引字段的順序不一致

explain-filesort

using join buffer

Block Nested Loop,需要進行嵌套循環計算。兩個關聯表join,關聯字段均未建立索引,就會出現這種情況。比如內層和外層的type均為ALL,rows均為4,需要循環進行4*4次計算。常見的優化方案是,在關聯字段上添加索引,避免每次嵌套循環計算。

本文參考了一些優秀的博客,感興趣的可以了解下:

碼字不易,如果本文寫的不錯,可以點個贊,讓我知道,支持我寫出更好的文章!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM