9月 04, 2019 MySQL
> 原文網址 : https://blog.csdn.net/zbw18297786698/article/details/54349046

在項目中驗證 SQL 語句執行效率的時候最直觀的方式就是查看其執行時間,但是在線上環境中如果不慎運行一個效率十分低下的 SQL 導致數據庫當掉了,那就悲劇了。並且只看執行時間,並無法有效的定位影響效率的原因。因此通過 `EXPLAIN` 命令查看SQL語句的執行計劃,根據執行計劃可以對SQL進行相應的優化。理解SQL執行計劃各個字段的含義這時候顯得十分重要。

如下圖

```sql
EXPLAIN SELECT COUNT(*) FROM blog
```

![](https://cdn.19site.net/files/9d/58/9d58eec4-a530-4064-a8e6-3e6e522d6ec5.png)

這是一個簡單的 SQL 的執行計劃,可以看到其包含十個字段來描述這個執行計劃。

其中比較重要的字段有 `select_type`、`type`、`ref`、`extra` 。

下面為更好的理解執行計劃,這裡對每個字段進行相應的解釋。

### id

一個複雜的sql會生成多執行計劃如下圖:

```sql
EXPLAIN SELECT COUNT(*) FROM (SELECT id from blog where id = 1) a
```

![](https://cdn.19site.net/files/e0/cf/e0cfe0bb-610e-45a1-bbff-98d6e3e97ad9.png)

可以看到含有子查詢的sql產生了兩條記錄,分別表示該條sql的執行順序。

### select_type

查詢類型,有如下幾種值

1. simple 表示簡單查詢,沒有子查詢和union 如圖1所示
2. primary 最外邊的select,在有子查詢的情況下最外邊的select查詢就是這種類型如圖2所示
3. union union語句的後一個語句執行的時候為該類型如圖2.1所示

```sql
EXPLAIN SELECT COUNT(*) FROM blog UNION SELECT id from blog where id = 1
```

![](https://cdn.19site.net/files/04/bf/04bf58b4-d505-43bc-aacb-d266b63425b0.png)

union result union語句的結果如圖所示。

### table

使用的表名

### type

連接類型,十分重要的字段 按照代表的效果由最優到最差情況進行介紹。

#### system

表僅有一行 const的特例。

#### const

最多匹配一行並且使用 `PRIMARY KEY` 或 `UNIQUE` 索引,才會是 `const`。

```sql
EXPLAIN SELECT * FROM blog where id =1
```

![](https://cdn.19site.net/files/42/8c/428c6d72-e9a4-4753-aadb-c02430ae038a.png)

下面這種情況搜索到一條數據但是沒有用到主鍵或索引 所以 type 不是 `const` 關於all的含義將在下文介紹

```sql
EXPLAIN SELECT * FROM blog LIMIT 1
```

![](https://cdn.19site.net/files/44/23/44238f68-22e3-41cf-9c8c-10c1dcb8bd98.png)

#### eq_ref

根據 MYSQL 官方手冊的解釋: "對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是除了 `const` 類型最好的聯接類型。它用在一個索引的所有部分被聯接使用並且索引是 `UNIQUE` 或`PRIMARY KEY`。 `eq_ref` 可以用於使用=比較帶索引的列。看下面的語句

```sql
EXPLAIN SELECT * FROM blog, author where blog.blog_author_id = author.id
```

![](https://cdn.19site.net/files/4b/e4/4be4f2f7-a0d5-47e8-b526-fb96331d4a3e.png)

```sql
EXPLAIN SELECT * FROM author, blog where blog_author_id = author.id
```

![](https://cdn.19site.net/files/cd/21/cd2172e6-ba62-49dd-a3ca-6aa43fc291ac.png)

#### ref

對於所有取自前表的行組合,所有的匹配項都是通過索引讀出的。也可以理解為連接不能基於關鍵字選擇單個行,可能查找到多個符合條件的行。叫做 `ref` 是因為索引要跟某個參考值相比較。這個參考值或者是一 個常數,或者是來自一個表裡的多表查詢的 結果值。

如下圖

```sql
EXPLAIN SELECT * FROM blog where blog_author_id = 2 // 其中blog_author_id有索引
```

![](https://cdn.19site.net/files/2c/99/2c994ed4-0840-40db-b68e-e394783db0ec.png)

寫到這裡 相信大家還是對以上各種類型的解釋有點迷迷糊糊。下面看一個等值連接的例子,會加深對索引和以上解釋的理解。

```sql
SELECT * FROM author, blog where author.id=blog.blog_author_id and author.id = 2
```

這條語句查出作者2發表的所有博客。 id為author表主鍵,mysql會自動為主鍵創建唯一索引。而blog_author_id是blog一個普通字段,如果對其加個索引看一下運行的效果。

![](https://cdn.19site.net/files/39/b6/39b6633f-37e3-4f3a-8dcb-7f68309bd4ca.png)

先觀察下一下這個執行計劃,可以看出 MYSQL 對 SQL 語句的執行已經做了很好的優化.這裡可以看到其中一條優化規則,先做選擇操作縮小連接操作的集合維度,再做連接操作,詳細可查看 MYSQL 生成執行計劃的優化策略。

解釋一下:第一行代表 MYSQL 生成的第一個執行計劃。即 `select * from author where id= 2` 由於 `id` 是 `author` 表的主鍵,且表包含多條數據但僅命中一行,所以其類型為 `const`。

第二行:對於 `blog` 表中 `auhorid` 為 2 的記錄有多個,且是通過索引讀出的。滿足 `ref` 的條件。

自然而然 如果把 `blog` 表中的 `author_id` 所以去除掉,則其類型應該不會再是 `ref`。讓我們來驗證這個想法。

```sql
drop index author_id on blog
```

再來執行以下查詢語句

![](https://cdn.19site.net/files/0c/b7/0cb73bc7-0754-4620-9011-79ac2cd8430a.png)

可以看到 `type` 類型變為 `ALL` 了,這種類型的效率非常慢,同時你可以看到 rows 這一行數據也發生了變化。由於沒有索引,所以需要掃描全表。詳細關於 `ALL` 類型和 rows 列的含義將在下文中介紹。

下面接著看下一個類型。

#### ref_or_null

如同 ref,但是添加了 MySQL 可以專門搜索包含 NULL 值的行。在解決子查詢中經常使用該聯接類型的優化。或解釋為MySQL 必須在初次查找的結果 裡找出 null 條目,然後進行二次查找。

這種類型沒搞明白 做實驗都沒出現這種類型 希望各位朋友給個例子。

但是上面說的這五種類型是屬於總體來說效果很不錯的了。如果能滿足以上類型的查詢 基本上不需要太大的優化、下面介紹效率較低幾種類型 當出現以下幾種類型的查詢 就要好好考慮做做優化了。

#### index_merge

該聯接類型表示使用了索引合併優化方法。在這種情況下,key列包含了使用的索引的清單,key_len包含了使用的索引的最長的關鍵元素。查看下面這條sql

```sql
EXPLAIN SELECT * FROM blog where blog_title = "first" and blog_author_id = 1
```

![](https://cdn.19site.net/files/70/ee/70ee330f-e275-4e36-8a2c-55a86341005d.png)

大致解釋一下索引和並優化的概念,這時mysql針對sql使用多個索引進行查詢時的優化方案。通俗的說就是mysql會把同一個表的多個索引掃描的結果進行合併。詳細的去看看相關博客。

解釋一下上述的例子,分別對blog_title和authorid創建索引,這時用and查詢滿足以上兩種條件的結果,如果查到一條的話它就是ref 但是如果匹配多條的話他就會進行索引合併。

#### unique_subquery

顧名思義 subquery可以看出這種類型跟子查詢有關係,同時大家知道子查詢在mysql中是十分不建議使用的一種查詢方式,當遇到子查詢時多思考如果通過連接查詢來優化。盡可能少的使用IN語句。

在某些 IN 查詢中使用此種類型,而不是常規的 ref:value IN (SELECT primary_key FROM single_table WHERE some_expr)

```sql
EXPLAIN SELECT * FROM blog where blog_author_id in (SELECT id from author where author_name = 'test1')
```

![](https://cdn.19site.net/files/60/46/6046a2f0-dbd3-448a-b5a0-6827eca75352.png)

即使對 authorname 創建索引也是相同的執行計劃

對於這種情況你可以將其改寫成一個 left join 語句

```sql
SELECT blog.* FROM blog LEFT JOIN author ON blog_author_id = author.id WHERE author_name = 'test1'
```

一樣的執行結果 但是執行計劃就是不同的如下圖

![](https://cdn.19site.net/files/ca/21/ca21b807-a9eb-473e-aa1f-0f5fede14eaa.png)

可見這種查詢就是用到了索引。效率可想而知。

#### index_subquery

在某些 IN 查詢中使用此種類型, 與 unique_subquery 類似,但是查詢的是非唯一性索引:

```sql
value IN (SELECT key_column FROM single_table WHERE some_expr)
```

```sql
EXPLAIN SELECT * FROM author where id in ( SELECT blog_author_id from blog where blog_title = 'secend')
```

![](https://cdn.19site.net/files/a7/70/a770f2b8-63b8-4a00-889f-569678743e77.png)

同樣的要盡量避免使用這種方式的查詢。

### range

顧名思義,range意思就是范圍。因此可以解釋為:只檢索給定範圍的行,使用一個索引來選擇 行。 key 列顯示使用了哪個索引。當使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操作符,用常量比較關鍵字列時,可 以使用 range。

這種類型解釋的很清楚了 稍微舉個栗子大家看看吧。

```sql
EXPLAIN SELECT * FROM blog where id > 2
```

![](https://cdn.19site.net/files/ef/cc/efcc457d-826c-4e79-9b99-32e1acccea32.png)

#### index

這種類型的意思也十分明顯,查詢過程中使用到了索引。解釋為: 全表掃描,只是掃描表的時候按照索引次序 進行而不是行。主要優點就是避免了排序, 但是開銷仍然非常大。舉個栗子

```sql
EXPLAIN SELECT * FROM blog ORDER BY id
```

![](https://cdn.19site.net/files/54/d1/54d12b57-591e-47b4-9c14-9a50c72d000e.png)

#### all

最壞的情況,從頭到尾全表掃描 。性能最差的一種類型 遇到這種類型 你得想想 為什麼不建索引!為什麼 不改造 sql!改造sql也是為了讓mysql運行的時候盡可能的使用到索引, 這裡又牽扯出一個問題 如何建索引 數據庫維護索引也是一件十分費時費力的事情。詳細內容自行查詢 本人還未總結~~~

這個就不舉例子了 大家看看上邊的例子 有很多連接查詢計劃中都存在all類型,順便想想如何優化。

解釋到這里大家對執行計劃所代表的效率含義基本上有個認識了,現在對後面的字段進行介紹。

### possible_keys

很明顯了 它的意思就是有可能使用到的索引。

### key

MySQL 實際從 possible_key 選擇使用的索引。如果為 NULL,則沒有使用索引。很少的情況 下,MYSQL 會選擇優化不足的索引。這種情 況下,可以在 SELECT 語句中使用 USE INDEX (indexname)來強制使用一個索引或者用 IGNORE INDEX(indexname)來強制 MYSQL 忽略索引。

### key_length

使用索引的長度。當然在不失精度的情況下 長度越小越好!

### ref

顯示索引的那一列被引用到了。

### rows

MYSQL 認為必須檢查的用來返回請求數據的行數,越大越不好。說明沒有很好的使用到索引。

### Extra

表示mysql解決查詢的詳細信息。

#### Using Index

表示使用到索引

#### using filesort

表示 MySQL 會對結果使用一個外部索引排序,而不是從表裡按索引次序讀到相關內容。可能在內存或者磁盤上進行排序。 MySQL 中無法利用索引完成的排序操作稱為“文件排序” 常見於 order by 和group by語句中。注意如果你對排序列創建索引mysql仍然會提示你使用的是filesort,所以對於這個字段應該有自己的判斷。

```sql
EXPLAIN SELECT * FROM blog order by blog_title
```

![](https://cdn.19site.net/files/d9/8e/d98ef303-aabc-4aa9-afb6-dec66e5335d7.png)

#### Using temporary

表示進行查詢時使用到臨時表。當使用到臨時表時,表示sql的效率需要進行相應的優化了。這種類型可能會在連接排序查詢中出現。

為了便於理解先舉一個例子。

```sql
EXPLAIN SELECT * FROM author,blog where author.id=blog.blog_author_id and blog.blog_title='first' order by author.id desc
```

![](https://cdn.19site.net/files/87/bb/87bbc4d8-4874-43d6-8e06-990a664b5842.png)

這條語句是要查出寫first這篇博客的博主信息,並按用戶id排序。

先來看看mysql連接查詢算法 Nested Loop Join 通過驅動表的結果集,一條一條的按照連接條件查詢下個表中的記錄。

這裡出現了一個名詞 驅動表

驅動表定義:

1. 當連接條件確定時,查詢條件篩選後記錄少的為驅動表。

2. 當連接條件不確定時,行數少的表為驅動表。

按照上述定義,由於blog_tiltle經過篩選條件後查詢得到的記錄數為2,而未對author表進行條件過濾,因此該sql的驅動表為blog。

將過濾後的blog表的記錄一條條的對author表查詢,而後合併,這時需要按照author表的id字段進行排序,因此需要對合併結果(臨時表)進行排序。

如果按照驅動表排序,則可以直接排序而無需臨時表。

```sql
EXPLAIN SELECT * FROM author,blog where author.id=blog.blog_author_id and blog.blog_title='first' order by blog.id desc
```
![](https://cdn.19site.net/files/aa/08/aa085187-9cea-4647-a418-c0967be5e365.png)
過去文章
2025 (9)
4 (5)
3 (1)
2 (3)
2024 (25)
11 (3)
10 (3)
9 (1)
3 (18)
2022 (6)
10 (1)
6 (2)
5 (1)
3 (1)
1 (1)
2021 (21)
11 (7)
7 (1)
6 (2)
5 (2)
4 (6)
3 (2)
2 (1)
2020 (92)
12 (1)
11 (2)
10 (4)
9 (10)
8 (5)
7 (1)
6 (3)
5 (1)
4 (4)
3 (25)
2 (7)
1 (29)
2019 (57)
12 (25)
11 (7)
9 (25)