sql在進行查詢的時候首先,與MySQL建立連接,然后查詢緩存,緩存中存在,則直接返回,緩存中沒有,通過解析器對sql進行解析,然后通過查詢優化器對sql進行優化(包括重寫查詢、選擇合適的索引等等..),然后去存儲引擎中查詢結果。
而對sql性能影響非常關鍵的一步,就是查詢優化器對sql的優化,而"explain"就是去查詢優化器中查詢sql的執行計劃。
explain(翻譯:執行)
- 執行計劃格式
explain + 具體sql
我們先來一個簡單地執行計劃:
explain select * from dept where id = 5
explain select * from dept where id in (select id from employee)
我們可以看到,一條執行計劃會展示12個相關的字段,下邊我們 一 一 了解一下這些字段的額含義:
- id
含義:是一組數字,表示select子句或者是操作表的順序
規則:
1.id不相同的,id值越大越先執行
2.id值相同的從上到下順序執行
- select_type
simple:簡單的select語句(不包括union操作或子查詢操作)
primary:查詢中最外層的select(如兩表做union或者存在子查詢的外層的表操作為primary,內層的操作為union)
union:union操作中,查詢中處于內層的select,即被union的select
subquery:子查詢中的select
derived:表示包含在 from 子句中的 select 查詢
union result:union的結果,此時id為null
- table
涉及到的表 - type(重要)
這列很重要,顯示了連接使用哪種類型,有無使用索引,
常見的值從最好到最差如下:
system > const > eq_ref > ref > range > index > all
各值的描述如下:
1.system:表只有一行,MyISAM引擎所有。
2.const:常量連接,表最多只有一行匹配,通常用于主鍵或者唯一索引比較時,如:
explain select * from dept where id=5
3.eq_ref:表關聯查詢時,對于前表的每一行,后表只有一行與之匹配。
(1) join查詢
(2) 命中主鍵或者非空唯一索引
4.ref:只使用了索引的最左前綴或者使用的索引是非唯一索引、非主鍵索引
5.range:between,in,>等都是典型的范圍(range)查詢 如:explain select * from dept where id BETWEEN 5 and 8;
6.index:需要掃描索引列上的全部數據, 如:explain select id from dept
7.All:全表掃描 如:explain select * from dept
- possible_keys
表示可能用到的索引 - key
表示最終用到的key - ref
顯示索引的哪一列被使用了,有時候會是一個常量:表示哪些列或常量被用于查找索引列上的值 - rows
估算出結果集行數,表示MySQL根據表統計信息及索引選用情況,估算的找到所需的記錄所需要讀取的行數, 原則上 rows 越少越好。 - filtered
查詢結果的行數占上面rows的百分比 - Extra(重要)
這一列也很重要,主要展示額外的信息說明,能夠給出讓我們深入理解執行計劃進一步的細節信息
常見的值及描述如下:
Using filesort:當order by 無法利用索引完成排序時,優化器不得不選擇合適的算法從內存或者磁盤進行排序
Using temporary:使用了臨時表
Using index:select后面的查詢字段在索引中就可以取到,無需再回表了,即所謂的覆蓋索引,這種查詢性能很好
Using index condition:mysql5.6之后引入了ICP(索引條件下推)
Using where:Mysql 服務器在存儲引擎檢索行后再進行過濾
優化原則
1. 讓主要查詢語句使用到合適的索引,type出現ALL(全表掃描)需格外注意,
同時建立合適的索引以減少possible_keys的數量
2. type最好能達到ref級別
3. Extra列出現Using temporary、Using filesort(文件排序)務必去除
優化思路
針對上面提到的幾點優化原則,提供如下的優化思路:
上述1,2點其實都可以通過優化索引來達到目的,而要想讓我們建的索引達到最優,則需要依據一個原則: 三星索引原則,簡單描述就是:
☆: where后條件匹配的索引列越多掃描的數據將越少,
比如組合索引(a,b,c),最好在where后面能同時用到索引上的a,b,c這三列
☆: 避免再次排序
簡單來說,就是排序字段盡量使用索引字段,因為索引默認是排好序的,使用索引字段排序可以避免再次排序
☆: 索引行包含查詢語句中所有的列,即覆蓋索引
基于這一點,我們應該少用select *來查詢,以增加覆蓋索引的可能性
如果你的索引能集齊上述三顆星,則說明你的索引是最優的索引!
基于第3點,
我們創建如下用戶表:
CREATE TABLE `t_user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`group_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1240277101395107842 CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
分組表:
CREATE TABLE `t_group` (
`id` bigint(20) NOT NULL,
`group_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
并插入一些數據。
我們首先來看下Using filesort,出現Using filesort常見的有以下幾種情況:
- order by 的字段不在where條件中,比如:下面這條sql會出現Using filesort:
select * from t_user where group_id = 2 and age = 32 order by name;
但是下面這條sql不會:
select * from t_user where group_id = 2 and age = 32 order by group_id ;
- 組合索引跨列: 舉例: 給t_user表創建索引(name,age,group_id),
則下面這條sql排序會出現Using filesort:
select * from t_user where name= '李A' order by group_id;
但是下面這條就不會:
select * from t_user where name = '李A' order by age;
因為第一條查詢order by跳過了age,直接使用了group_id;
刪除索引(name,age,group_id);
- 由于group by第一步默認進行了排序,所以當group by 的字段滿足上述條件是,也會出現
Using filesort,可以在group by后面加上order by null取消排序。
最后,我們來看下Using temporary(使用了臨時表):
臨時表的出現對性能影響是很大的,主要會出現在以下情況中:
- 分組字段不在where條件后面,并且group by字段不是最終使用到的索引,原因有點類似于上面的Using filesort:
比如:
下面這條sql會出現Using temporary:
select * from t_user where group_id = 2 and name= '李A' group by age;
但是下面這條sql不會:
select * from t_user where name = '李A' and age = 21 group by age;
結論: where哪些字段,就group by 哪些字段.
- 表連接中,order by的列不是驅動表中的
如下sql是會創建臨時表的:
explain select * from t_user t1 left join t_group t2 on t1.group_id = t2.id order by t2.id;
因為t1和t2連接的時候,t1是驅動表,但是排序使用了被驅動表t2中的字段。改為t1的字段排序就不會出現臨時表了,這里就不舉例了。
結論: 連接查詢的時候,排序字段使用驅動表的字段
- order by和group by的子句不一樣時
如下Sql:
explain select * from t_user group by group_id order by `name`;
這種情況只能盡量使用同一個字段來分組和排序了,否則無法避免
- distinct查詢并且加上order by時
如下sql:
explain select DISTINCT(`name`) from t_user order by age;
這種情況有時候無法避免,只能盡量將distinct的字段和order by的字段使用相同的索引。
還有會出現臨時表的情況有: from 中的子查詢、union,這里就不一一舉例了。
此文章參考:https://kuy8.com/sIFEp