SQL優化-使用explain分析SQL執行計劃

有時候咱們會遇到這樣的事情:項目上線了,一切順利,就等產品驗收完畢回家睡覺了,可是產品忽然來找你了:mysql

產品:首頁爲何加載這麼慢算法

開發:數據量太大了,沒辦法sql

產品:不行,必須優化後端

開發:....緩存

這種狀況,很大機率是sql出現慢查詢了,此時咱們就須要把查詢sql拉出來優化優化了,那咱們就須要使用到本文要說的explain命令了。bash

本文目的

  1. 幫助你們認識explain,遇到上述問題的時候能夠到此來查閱執行計劃中每一個字段的意思
  2. 能根據慢查詢的執行計劃快速找到問題所在
  3. 提供常見的問題緣由以及解決方案

explain能幹嗎

在瞭解explain以前,不妨先看下mysql服務大體的邏輯架構圖,以對其有一個總體的認識服務器

explain

從圖中能夠看出,咱們的sql在查詢的時候主要須要經歷如下步驟:架構

  1. 與mysql創建鏈接
  2. 查詢緩存是否存在,若是有則直接返回結果
  3. 解析器,主要是對sql進行解析
  4. 查詢優化器,主要對sql進行各類優化,包括重寫查詢、決定表的讀取順序以及選擇合適的索引等等。。併產生執行計劃
  5. 去存儲引擎查詢結果

而咱們使用explain便是去查詢優化器查詢執行計劃框架

explain字段解釋

看一條簡單的執行計劃性能

explain select * from t_user where id = 1;
複製代碼

執行計劃1

咱們能夠看到,一個執行計劃會展現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

各值的描述以下

描述
system 表只有一行,MyISAM引擎全部
const 常量鏈接,表最多隻有一行匹配,一般用於主鍵或者惟一索引比較時,如:
select * from t_user where id = 1;
eq_ref 表關聯查詢時,對於前表的每一行,後表只有一行與之匹配。
(1) join查詢
(2) 命中主鍵或者非空惟一索引
ref 只使用了索引的最左前綴或者使用的索引是非惟一索引、非主鍵索引
range between,in,>等都是典型的範圍(range)查詢
index 須要掃描索引上的所有數據,如:
select count(*) from t_user;
all 全表掃描

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

上述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

  1. 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 ;
    複製代碼

    執行計劃

  2. 組合索引跨列

    舉例:給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);

  3. 因爲group by第一步默認進行了排序,因此當group by 的字段知足上述條件是,也會出現Using filesort,能夠在group by後面加上order by null取消排序

Using temporary

臨時表的出現對性能影響是很大的,主要會出如今如下狀況中

  1. 分組字段不在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 哪些字段

  2. 錶鏈接中,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的字段排序就不會出現臨時表了,這裏就不舉例了。

    結論: 鏈接查詢的時候,排序字段使用驅動表的字段

  3. order by和group by的子句不同時

    explain select * from t_user group by group_id order by `name`;
    複製代碼

    執行計劃

    這種狀況只能儘可能使用同一個字段來分組和排序了,不然沒法避免

  4. distinct查詢而且加上order by時

    explain select DISTINCT(`name`) from t_user order by age;
    複製代碼

    執行計劃

    這種狀況有時候沒法避免,只能儘可能將distinct的字段和order by的字段使用相同的索引。還有會出現臨時表的狀況有: from 中的子查詢、union,這裏就不一一舉例了。

總結

sql優化已是咱們後端開發的內化技能之一了,在學習框架,設計思想的同時,不要忘記打牢基礎,但願各位可以有所收穫。

相關文章
相關標籤/搜索