SQL優化 MySQL版 ,explain SQL執行計劃詳解

目錄
一、執行計劃分析
二、SQL執行計劃中的參數講解——explain中的id
1、id值相同,從上往下順序執行。
2、ID值不同,id值越大越優先查詢
3、id值有相同,又有不同。id值越大越優先;id值相同,從上往下順序執行
二、SQL執行計劃中的參數講解——explain中的select_type
三、SQL執行計劃中的參數講解——explain中的table
四、SQL執行計劃中的參數講解——explain中的type
1、type字段中的——system
2、type字段中的——const
3、type字段中的——eq_ref
4、type字段中的——ref
5、type字段中的——range
6、type字段中的——index
7、type字段中的——all
四、SQL執行計劃中的參數講解——explain中的possible_keys
五、SQL執行計劃中的參數講解——explain中的key
六、SQL執行計劃中的參數講解——explain中的key_len
七、SQL執行計劃中的參數講解——explain中的ref
八、SQL執行計劃中的參數講解——explain中的rows
九、SQL執行計劃中的參數講解——explain中的Extra
1、Extra字段中——using filesort
2、Extra字段中——using temporary
3、Extra字段中——using index
4、Extra字段中——using where
十、優化案例,單表優化、兩表優化、三表優化
十一、避免索引失效的一些原則

前不久看了SQL優化的視頻,學的時候醍醐灌頂,學完後發現就是在圍繞着explain的各個字段講解,先把學習的知識梳理一下,當然自己還是有很多不會的地方,後期不斷完善。

sql優化是面試必問的面試點,而SQL優化主要就是在優化索引。在學習索引優化之前,需要知道SQL的執行順序,這裏只給結論,深入學習可參考文章:步步深入:MySQL架構總覽->查詢執行流程->SQL解析順序

SQL執行順序
先執行SQL語句中的from,在執行on.. join ..where ..group by ....having ...,最後執行select dinstinct ..order by limit ...

from .. on.. join ..where ..group by ....having ...
select dinstinct ..order by limit ...

一、執行計劃分析

通過explain可以知道mysql是如何處理語句的,並分析出查詢或是表結構的性能瓶頸,其實就是在幹查詢優化器的事,通過expalin可以得到查詢執行計劃。

#語法:
explain +SQL語句
explain  select  * from tb 
  • 各字段含義

id : 編號
select_type :查詢類型
table :表
type :類型
possible_keys :預測用到的索引
key :實際使用的索引
key_len :實際使用索引的長度
ref :表之間的引用
rows :通過索引查詢到的數據量
Extra :額外的信息

下面講解explain中各字段的具體含義,我們只關注三種,分別是type,key,rows

二、SQL執行計劃中的參數講解——explain中的id

explain中id是SQL執行順序的標識,id的返回結果是數字,id 值越大優先級越高,越先被執行;id 相同,從上往下順序執行。返回結果有三種情況。

目錄
1、id值相同,從上往下順序執行。
2、ID值不同,id值越大越優先查詢
3、id值有相同,又有不同。id值越大越優先;id值相同,從上往下順序執行

1、id值相同,從上往下順序執行。

案例如下

explain select t.* from teacher t,course c,teacherCard tc where  t.tid=c.tid 
and t.tcid = tc.tcid 
and (c.cid=2 or tc.tcid=3)

在上圖SQL邏輯中,id值相同都爲1,從上往下順序執行。先執行teacher表(t表),在執行teacherCard 表(tc表),最後執行course 表(c表)。

建表:
create table course
(
cid int(3),
cname varchar(20),
tid int(3)
);
create table teacher
(
tid int(3),
tname varchar(20),
tcid int(3)
);

create table teacherCard
(
tcid int(3),
tcdesc varchar(200)
);

插入數據
insert into course values(1,'java',1);
insert into course values(2,'html',1);
insert into course values(3,'sql',2);
insert into course values(4,'web',3);

insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);

insert into teacherCard values(1,'tzdesc') ;
insert into teacherCard values(2,'twdesc') ;
insert into teacherCard values(3,'tldesc') ;
  • 改變表中數據量會影響表的執行順序
圖1表中數據量:
teacher表3條
teacherCard 表3條
course 表4條

改變teacher表數據條數,增加3條數據

insert into teacher values(4,'ta',4);
insert into teacher values(5,'tb',5);
insert into teacher values(6,'tc',6);

查看錶的執行順序



我們可以看見修改了表數據量之後,表的執行也改變了。先執行teacherCard 表(tc表),在執行course 表(c表),最後執行teacher表(t表)。

  • 表的執行順序因數量的個數改變而改變的原因
    笛卡兒積。數據量小對程序佔用內存小,優先計算(數據小的表,優先查詢)
2、ID值不同,id值越大越優先查詢

案例如下

mysql> explain select tc.tcdesc from teacherCard tc where tc.tcid = 
    -> (select t.tcid from teacher t where  t.tid =  
    ->     (select c.tid from course c where c.cname = 'sql')
    -> );

+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+
|  1 | PRIMARY     | tc    | ALL  | NULL          | NULL        | NULL    | NULL  |    3 | Using where           |
|  2 | SUBQUERY    | t     | ALL  | NULL          | NULL        | NULL    | NULL  |    6 | Using where           |
|  3 | SUBQUERY    | c     | ref  | cname_index   | cname_index | 23      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+

在上面邏輯中,id值不相同,id值越大越優先查詢 (本質:在嵌套子查詢時,先查內層 再查外層)
id字段和table字段可以知道程序在底層先執行course 表(c表),在執行teacher表(t表),最後執行teacherCard 表(tc表)

3、id值有相同,又有不同。id值越大越優先;id值相同,從上往下順序執行

案例如下

mysql> explain select t.tname ,tc.tcdesc from teacher t,teacherCard tc where t.tcid= tc.tcid
    -> and t.tid = (select c.tid from course c where cname = 'sql') ;

+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key         | key_len | ref   | rows | Extra                                              |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+
|  1 | PRIMARY     | tc    | ALL  | NULL          | NULL        | NULL    | NULL  |    3 | NULL                                               |
|  1 | PRIMARY     | t     | ALL  | NULL          | NULL        | NULL    | NULL  |    6 | Using where; Using join buffer (Block Nested Loop) |
|  2 | SUBQUERY    | c     | ref  | cname_index   | cname_index | 23      | const |    1 | Using index condition                              |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+----------------------------------------------------+

在上面案例中,id值有相同,又有不同。先執行course 表(c表),在執行teacherCard 表(tc表),最後執行teacher表(t表)。

二、SQL執行計劃中的參數講解——explain中的select_type

select_type是查詢類型,常見的查詢類型如下。

1、\color{#0000FF }{SIMPLE }: 簡單查詢,查詢SQL中不包含子查詢或者UNION
2、PRIMARY: 查詢中若包含複雜的子查詢,最外層的查詢則標記爲
3、SUBQUERY : 包含子查詢SQL中的 子查詢 (非最外層)
4、DERIVED : 衍生查詢(使用到了臨時表)。在from列表中包含子查詢被標記爲DRIVED衍生,MYSQL會遞歸執行這些子查詢,把結果放到臨時表中
5、UNION: 若第二個SELECT出現在union之後,則被標記爲UNION, 若union包含在from子句的子查詢中,外層select被標記爲:derived
6、UNION RESULT: 從union表獲取結果的select

查詢類型這裏只總結了結果,進一步瞭解建議大家看參考文章。
參考文章1:MySQL的Explain信息中select_type字段解釋
參考文章2:explain之select_type

三、SQL執行計劃中的參數講解——explain中的table

顯示這一行的數據是關於哪張表的,有時不是真實的表名字,看到的是derivedx(x是個數字,我的理解是第幾步執行的結果)

mysql> explain select cr.cname from
    ->  ( select * from course where tid = 1 union select * from course where tid = 2 ) cr;

+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
| id | select_type  | table      | type | possible_keys | key  | key_len | ref  | rows | Extra           |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+
|  1 | PRIMARY      | <derived2> | ALL  | NULL          | NULL | NULL    | NULL |    8 | NULL            |
|  2 | DERIVED      | course     | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where     |
|  3 | UNION        | course     | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where     |
| NULL | UNION RESULT | <union2,3> | ALL  | NULL          | NULL | NULL    | NULL | NULL | Using temporary |
+----+--------------+------------+------+---------------+------+---------+------+------+-----------------+

四、SQL執行計劃中的參數講解——explain中的type

表示MySQL在表中找到所需行的方式,又稱“訪問類型”,sql查詢優化中一個很重要的指標,結果值從好到壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般來說,好的sql查詢至少達到range級別,最好能達到ref

目錄:
1、type字段中的——system
2、type字段中的——const
3、type字段中的——eq_ref
4、type字段中的——ref
5、type字段中的——range
6、type字段中的——index
7、type字段中的——all

1、type字段中的——system

實現場景:當表裏面只有一行數據的時候就會這樣,而且不管有沒有索引都一樣,這是const連接類型的特殊情況。
實現只有一條數據的系統表 ;或衍生表只有一條數據的主查詢

2、type字段中的——const

實現場景:當查詢只有唯一的一條記錄被匹配,並且使用\color{red}{主鍵等於常數或者唯一索引等於某常數}作爲查詢條件時, MySQL 會視查詢出來的值爲常數(可以爲字符串或者數字),這種類型非常快。和system不同的地方是system是表裏面只有一行數據,而const有多行數據,const是隻有一行數據被匹配。

案例如下

create table test01
(
    tid int(3),
    tname varchar(20)
);
插入數據
insert into test01 values(1,'a') ;
insert into test01 values(2,'b') ;
增加索引
alter table test01 add constraint tid_pk primary key(tid) ;
#查詢一個字段
mysql> explain select tid from test01 where tid =1 ;

+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref   | rows | Extra       |
+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | test01 | const | PRIMARY       | PRIMARY | 4       | const |    1 | Using index |
+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------------+

#查詢表中所有字段
mysql> explain select * from test01 where tid =1 ;

+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table  | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | test01 | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
+----+-------------+--------+-------+---------------+---------+---------+-------+------+-------+

3、type字段中的——eq_ref

唯一性索引:對於每個索引鍵的查詢,返回匹配唯一行數據(有且只有1個,不能多 、不能0),常見於唯一索引 和主鍵索引。
必須滿足查詢結果和表中數據是一致的。
案例如下

create table teacher
(
tid int(3),
tname varchar(20),
tcid int(3)
);

create table teacherCard
(
tcid int(3),
tcdesc varchar(200)
);

insert into teacher values(1,'tz',1);
insert into teacher values(2,'tw',2);
insert into teacher values(3,'tl',3);
insert into teacher values(4,'ta',4);
insert into teacher values(5,'tb',5);
insert into teacher values(6,'tc',6);

insert into teacherCard values(1,'tzdesc') ;
insert into teacherCard values(2,'twdesc') ;
insert into teacherCard values(3,'tldesc') ;

建索引
alter table teacherCard add constraint pk_tcid primary key(tcid);
alter table teacher add constraint uk_tcid unique index(tcid) ;
#1、teacher表和teacherCard 都有唯一索引tcid,查詢teacher表的tcid,type類型是ref 

mysql> explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref          | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+
|  1 | SIMPLE      | tc    | index | PRIMARY       | PRIMARY | 4       | NULL         |    3 | Using index |
|  1 | SIMPLE      | t     | ref   | uk_tcid       | uk_tcid | 5       | test.tc.tcid |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+

#2、teacher表和teacherCard 都有唯一索引tcid,查詢teacherCard 表的tcid,type類型是ref 

mysql> explain select tc.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+
| id | select_type | table | type  | possible_keys | key     | key_len | ref          | rows | Extra       |
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+
|  1 | SIMPLE      | tc    | index | PRIMARY       | PRIMARY | 4       | NULL         |    3 | Using index |
|  1 | SIMPLE      | t     | ref   | uk_tcid       | uk_tcid | 5       | test.tc.tcid |    1 | Using index |
+----+-------------+-------+-------+---------------+---------+---------+--------------+------+-------------+

爲什麼上面type類型不是eq_ref
原因:teacher有6條數據,索引查到的數據只有3條,還有3條數據沒查到,沒查到的數據結果就是0。

上面查詢如何達到eq_ref

#刪除teacher表中的三條數據
delete from teacher where tid>3;

刪除數據後查看type類型

mysql> explain select t.tcid from teacher t,teacherCard tc where t.tcid = tc.tcid ;

+----+-------------+-------+--------+---------------+---------+---------+-------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref         | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | t     | index  | uk_tcid       | uk_tcid | 5       | NULL        |    3 | Using where; Using index |
|  1 | SIMPLE      | tc    | eq_ref | PRIMARY       | PRIMARY | 4       | test.t.tcid |    1 | Using index              |
+----+-------------+-------+--------+---------------+---------+---------+-------------+------+--------------------------+

以上SQL,用到的索引是 t.tcid,即teacher表中的tcid字段;如果teacher表的數據個數 和 連接查詢teacherCard 表的數據個數一致(都是3條數據),則有可能滿足eq_ref級別;否則無法滿足。

4、type字段中的——ref

非唯一性索引,對於每個索引鍵的查詢,返回匹配的所有行(0行,多行或者一行)

出現該連接類型的條件是: 查找條件列使用了索引而且不爲主鍵和unique。其實,意思就是雖然使用了索引,但該索引列的值並不唯一,有重複。這樣即使使用索引快速查找到了第一條數據,仍然不能停止,要進行目標值附近的小範圍掃描。但它的好處是它並不需要掃全表,因爲索引是有序的,即便有重複值,也是在一個非常小的範圍內掃描。

準備數據:
 insert into teacher values(4,'tz',4) ;
 insert into teacherCard values(4,'tz222');

添加索引:
alter table teacher add index index_name (tname) ;
mysql> explain select * from teacher where tname='tz';
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table   | type | possible_keys | key        | key_len | ref   | rows | Extra                 |
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | teacher | ref  | index_name    | index_name | 23      | const |    2 | Using index condition |
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+


mysql> explain select * from teacher where tname='tw';
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+
| id | select_type | table   | type | possible_keys | key        | key_len | ref   | rows | Extra                 |
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | teacher | ref  | index_name    | index_name | 23      | const |    1 | Using index condition |
+----+-------------+---------+------+---------------+------------+---------+-------+------+-----------------------+
5、type字段中的——range

range指的是有範圍的索引掃描,where後面是一個範圍查詢(between ,> < >=, **特殊:in有時候會失效 **,從而轉爲 無索引all)

#添加索引
alter table teacher add index tid_index (tid) ;
#1、in範圍查詢時索引失效
mysql> explain select t.* from teacher t where t.tid in (1,2) ;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | t     | ALL  | tid_index     | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

#2、<範圍查詢時type 類型是range
mysql> explain select t.* from teacher t where t.tid <3 ;
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-----------------------+
| id | select_type | table | type  | possible_keys | key       | key_len | ref  | rows | Extra                 |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-----------------------+
|  1 | SIMPLE      | t     | range | tid_index     | tid_index | 5       | NULL |    1 | Using index condition |
+----+-------------+-------+-------+---------------+-----------+---------+------+------+-----------------------+

6、type字段中的——index

查詢全部索引中數據

mysql> explain select tid from teacher;--tid 是索引, 只需要掃描索引表,不需要所有表中的所有數據

+----+-------------+---------+-------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table   | type  | possible_keys | key       | key_len | ref  | rows | Extra       |
+----+-------------+---------+-------+---------------+-----------+---------+------+------+-------------+
|  1 | SIMPLE      | teacher | index | NULL          | tid_index | 5       | NULL |    4 | Using index |
+----+-------------+---------+-------+---------------+-----------+---------+------+------+-------------+
7、type字段中的——all

查詢全部表中的數據。這便是所謂的“全表掃描”,如果是展示一個數據表中的全部數據項,倒是覺得也沒什麼,如果是在一個查找數據項的sql中出現了all類型,那通常意味着你的sql語句處於一種最原生的狀態,有很大的優化空間。

mysql> explain select cid from course;--cid不是索引,需要全表所有,即需要所有表中的所有數據
+----+-------------+--------+------+---------------+------+---------+------+------+-------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | course | ALL  | NULL          | NULL | NULL    | NULL |    4 | NULL  |
+----+-------------+--------+------+---------------+------+---------+------+------+-------+

參考文章1:mysql中explain的type的解釋
參考文章2:MySQL執行計劃分析(EXPLAIN)之type字段詳細介紹說明

四、SQL執行計劃中的參數講解——explain中的possible_keys

possible_keys可能用到的索引,是一種預測。查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢使用,如果爲空,說明沒有可用的索引。

mysql> explain select cid from course;
+----+-------------+--------+------+---------------+------+---------+------+------+-------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | course | ALL  | NULL          | NULL | NULL    | NULL |    4 | NULL  |
+----+-------------+--------+------+---------------+------+---------+------+------+-------+

該查詢中possible_keys 爲null,說明沒有索引。

五、SQL執行計劃中的參數講解——explain中的key

實際使用到的索引

六、SQL執行計劃中的參數講解——explain中的key_len

key_len 索引的長度 ,用於判斷複合索引是否被完全使用

案例如下

#創建一張表
create table test_kl
(
    name char(20)
);

創建索引
alter table test_kl add index index_name(name) ;

查看執行計劃

mysql> explain select * from test_k2 where name ='' ; 
+----+-------------+---------+------+---------------+------------+---------+-------+------+--------------------------+
| id | select_type | table   | type | possible_keys | key        | key_len | ref   | rows | Extra                    |
+----+-------------+---------+------+---------------+------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | test_k2 | ref  | index_name    | index_name | 23      | const |    1 | Using where; Using index |
+----+-------------+---------+------+---------------+------------+---------+-------+------+--------------------------+

在utf8:1個字符站3個字節
如果索引字段可以爲Null,則會使用1個字節用於標識。
20*1=20 + 1(null) +2(用2個字節 標識可變長度,字段類型是可變長度) =23

utf8:1個字符3個字節
gbk:1個字符2個字節
latin:1個字符1個字節

複合索引案例如下

alter table test_k2 add column name1 varchar(20) ;  --name1可以爲null
drop index index_name on test_k2 ;

增加一個複合索引 
alter table test_k2 add index name_name1_index (name,name1) ; 
#複合索引,要用name1 ,得先用name
mysql> explain select * from test_k2 where name1 = '' ; 
+----+-------------+---------+-------+---------------+------------------+---------+------+------+--------------------------+
| id | select_type | table   | type  | possible_keys | key              | key_len | ref  | rows | Extra                    |
+----+-------------+---------+-------+---------------+------------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | test_k2 | index | NULL          | name_name1_index | 46      | NULL |    1 | Using where; Using index |
+----+-------------+---------+-------+---------------+------------------+---------+------+------+--------------------------+

#2、雖然是複合索引,但是隻用到了name
mysql> explain select * from test_k2 where name = '' ; 
+----+-------------+---------+------+------------------+------------------+---------+-------+------+--------------------------+
| id | select_type | table   | type | possible_keys    | key              | key_len | ref   | rows | Extra                    |
+----+-------------+---------+------+------------------+------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | test_k2 | ref  | name_name1_index | name_name1_index | 23      | const |    1 | Using where; Using index |
+----+-------------+---------+------+------------------+------------------+---------+-------+------+--------------------------+

七、SQL執行計劃中的參數講解——explain中的ref

注意與type中的ref值區分,顯示索引的哪一列被使用了 。返回兩種結果,const(是常量則該字段結果是const),或者使用了那個字段。

alter table course  add index tid_index (tid) ;
mysql> explain select * from course c,teacher t where c.tid = t.tid  and t.tname ='tw' ;
+----+-------------+-------+------+----------------------+------------+---------+------------+------+------------------------------------+
| id | select_type | table | type | possible_keys        | key        | key_len | ref        | rows | Extra                              |
+----+-------------+-------+------+----------------------+------------+---------+------------+------+------------------------------------+
|  1 | SIMPLE      | t     | ref  | index_name,tid_index | index_name | 23      | const      |    1 | Using index condition; Using where |
|  1 | SIMPLE      | c     | ref  | tid_index            | tid_index  | 5       | test.t.tid |    1 | NULL                               |
+----+-------------+-------+------+----------------------+------------+---------+------------+------+------------------------------------+

八、SQL執行計劃中的參數講解——explain中的rows

被索引優化查詢的數據個數 (實際通過索引而查詢到的 數據個數)
explain select * from course c,teacher t where c.tid = t.tid
and t.tname = 'tz' ;

九、SQL執行計劃中的參數講解——explain中的Extra

目錄:
1、Extra字段中——using filesort
2、Extra字段中——using temporary
3、Extra字段中——using index
4、Extra字段中——using where

Extra其他信息,出現Using filesort、Using temporary 意味着不能使用索引,效率會受到重大影響。應儘可能對此進行優化

1、Extra字段中——using filesort

性能消耗大;需要“額外”的一次排序(或者是額外得一次查詢),常見於 order by 語句中。
排序:排序得前提是先查詢,在排序。比如需要對10個人根據年齡排序,所以排序之前需要先排序

  • 案例如下
create table test02
(
    a1 char(3),
    a2 char(3),
    a3 char(3),
    index idx_a1(a1),
    index idx_a2(a2),
    index idx_a3(a3)
);
mysql> explain select * from test02 where a1 ='' order by a2 ; 

+----+-------------+--------+------+---------------+--------+---------+-------+------+----------------------------------------------------+
| id | select_type | table  | type | possible_keys | key    | key_len | ref   | rows | Extra                                              |
+----+-------------+--------+------+---------------+--------+---------+-------+------+----------------------------------------------------+
|  1 | SIMPLE      | test02 | ref  | idx_a1        | idx_a1 | 4       | const |    1 | Using index condition; Using where; Using filesort |
+----+-------------+--------+------+---------------+--------+---------+-------+------+----------------------------------------------------+

Extra字段中出現了Using filesort。

小結:對於單索引, 如果排序和查找是同一個字段,則不會出現using filesort;如果排序和查找不是同一個字段,則會出現using filesort;
如何避免: where哪些字段,就order by那些字段2

  • 複合索引——分析Extra字段
    複合索引,不能跨列(最佳左前綴)
drop index idx_a1 on test02;
drop index idx_a2 on test02;
drop index idx_a3 on test02;

alter table test02 add index idx_a1_a2_a3 (a1,a2,a3) ;

test02 有複合索引idx_a1_a2_a3 (a1,a2,a3) ,where條件是a1,排序是a3。跨列了,所以出現了Using filesort。

mysql> explain select *from test02 where a1='' order by a3 ;
+----+-------------+--------+------+---------------+--------------+---------+-------+------+------------------------------------------+
| id | select_type | table  | type | possible_keys | key          | key_len | ref   | rows | Extra                                    |
+----+-------------+--------+------+---------------+--------------+---------+-------+------+------------------------------------------+
|  1 | SIMPLE      | test02 | ref  | idx_a1_a2_a3  | idx_a1_a2_a3 | 4       | const |    1 | Using where; Using index; Using filesort |
+----+-------------+--------+------+---------------+--------------+---------+-------+------+------------------------------------------+

Extra字段存在Using filesort

mysql> explain select *from test02 where a2='' order by a3 ;
+----+-------------+--------+-------+---------------+--------------+---------+------+------+------------------------------------------+
| id | select_type | table  | type  | possible_keys | key          | key_len | ref  | rows | Extra                                    |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+------------------------------------------+
|  1 | SIMPLE      | test02 | index | NULL          | idx_a1_a2_a3 | 12      | NULL |    1 | Using where; Using index; Using filesort |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+------------------------------------------+

Extra字段無Using filesort

mysql> explain select *from test02 where a1='' order by a2 ;

+----+-------------+--------+------+---------------+--------------+---------+-------+------+--------------------------+
| id | select_type | table  | type | possible_keys | key          | key_len | ref   | rows | Extra                    |
+----+-------------+--------+------+---------------+--------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | test02 | ref  | idx_a1_a2_a3  | idx_a1_a2_a3 | 4       | const |    1 | Using where; Using index |
+----+-------------+--------+------+---------------+--------------+---------+-------+------+--------------------------+

小結:避免where和order by 按照複合索引的順序使用,不要跨列或無序使用。

2、Extra字段中——using temporary

using temporary性能損耗大 ,用到了臨時表。一般出現在group by 語句中,已經有表了,但不適用,必須在來一張表。瞭解表概念,需要知道sql的執行順序,SQL的執行順序會產生虛擬表。
參考文章:步步深入:MySQL架構總覽->查詢執行流程->SQL解析順序

mysql> explain select a1 from test02 where a1 in ('1','2','3') group by a2;

+----+-------------+--------+-------+---------------+--------------+---------+------+------+-----------------------------------------------------------+
| id | select_type | table  | type  | possible_keys | key          | key_len | ref  | rows | Extra                                                     |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+-----------------------------------------------------------+
|  1 | SIMPLE      | test02 | index | idx_a1_a2_a3  | idx_a1_a2_a3 | 12      | NULL |    1 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+-----------------------------------------------------------+

出現了Using temporary,根據a2分組,但是沒查詢a2.

3、Extra字段中——using index

出現using index性能會提升,即索引覆蓋(覆蓋索引)。
索引覆蓋該SQL不讀取原文件,只從索引文件中獲取數據 (不需要回表查詢),只要使用到的列 全部都在索引中,就是索引覆蓋using index。

案例如下

#例如:test02表中有一個複合索引(a1,a2,a3)
mysql> explain select a1,a2 from test02 where a1='' or a2= '' ;

+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
| id | select_type | table  | type  | possible_keys | key          | key_len | ref  | rows | Extra                    |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | test02 | index | idx_a1_a2_a3  | idx_a1_a2_a3 | 12      | NULL |    1 | Using where; Using index |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
4、Extra字段中——using where

using where 回表查詢,需要在索引中查,有需要在原表中查就會出現using where

mysql> explain select a1,a3 from test02 where a3 = '' ; 

+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
| id | select_type | table  | type  | possible_keys | key          | key_len | ref  | rows | Extra                    |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | test02 | index | NULL          | idx_a1_a2_a3 | 12      | NULL |    1 | Using where; Using index |
+----+-------------+--------+-------+---------------+--------------+---------+------+------+--------------------------+
5、Extra字段中——impossible where

where子句永遠爲false,會出現impossible where

案例如下:

mysql> explain select * from test02 where a1='x' and a1='y'  ;

+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra            |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+
|  1 | SIMPLE      | NULL  | NULL | NULL          | NULL | NULL    | NULL | NULL | Impossible WHERE |
+----+-------------+-------+------+---------------+------+---------+------+------+------------------+

a1不會出現即等於x,又等於y

十、優化案例,單表優化、兩表優化、三表優化

1、單表優化

小結:a.最佳做前綴,保持索引的定義和使用的順序一致性
b.索引需要逐步優化
c.將含In的範圍查詢 放到where條件的最後,防止失效。

2、兩表優化

1、索引往哪張表加?
小表驅動大表
2、索引建立經常使用的字段上
一般情況對於左外連接,給左表加索引;右外連接,給右表加索引
3、join時,爲什麼需要用小表驅動大表?
原因是join時是雙層循環,一般建議將數據小的循環放外層;數據大的循環放內存。
在編程語言中,外層越小,內存越大,性能越高(小表在左,外層循環少,依賴的原則是程序優化)

3、三張表優化A B C

a.小表驅動大表
b.索引建立在經常查詢的字段上--where 字段加索引,和常用字段加索引

十一、避免索引失效的一些原則

使用數據如下

create table book
(
    bid int(4) primary key,
    name varchar(20) not null,
    authorid int(4) not null,
    publicid int(4) not null,
    typeid int(4) not null 
);

insert into book values(1,'tjava',1,1,2) ;
insert into book values(2,'tc',2,1,2) ;
insert into book values(3,'wx',3,2,1) ;
insert into book values(4,'math',4,2,3) ;   

alter table book add index idx_bta (bid,typeid,authorid);

1、複合索引
a.複合索引,不要跨列或無序使用(最佳左前綴)
(a,b,c)
b.複合索引,儘量使用全索引匹配
(a,b,c)
2、不要在索引上進行任何操作(計算、函數、類型轉換),否則索引失效

mysql> explain select * from book where authorid = 1 and typeid = 2 ;
---用到了at 2個索引
+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+
| id | select_type | table | type | possible_keys | key     | key_len | ref         | rows | Extra |
+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+
|  1 | SIMPLE      | book  | ref  | idx_atb       | idx_atb | 8       | const,const |    1 | NULL  |
+----+-------------+-------+------+---------------+---------+---------+-------------+------+-------+
1 row in set (0.00 sec)

mysql> explain select * from book where authorid = 1 and typeid*2 = 2 ; 
--用到了a1個索引
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref   | rows | Extra                 |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | book  | ref  | idx_atb       | idx_atb | 4       | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)

mysql> explain select * from book where authorid*2 = 1 and typeid*2 = 2 ; 
----用到了0個索引
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql> explain select * from book where authorid*2 = 1 and typeid = 2 ;
---用到了0個索引,
--原因:對於複合索引,如果左邊失效,右側全部失效。(a,b,c),例如如果 b失效,則b c同時失效。

+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | book  | ALL  | NULL          | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

3、複合索引不能使用不等於(!= <>)或is null (is not null)
複合索引不能使用不等於(!= <>)或is null (is not null),否則自身以及右側所有全部失效。複合索引中如果有>,則自身和右側索引全部失效。

案例如下

mysql> explain select * from book where authorid != 1 and typeid =2 ;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | book  | ALL  | idx_atb       | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql> explain select * from book where authorid != 1 and typeid !=2 ;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | book  | ALL  | idx_atb       | NULL | NULL    | NULL |    4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

SQL優化,是一種概率層面的優化。至於是否實際使用了我們的優化,需要通過explain進行推測。

  • 體驗概率情況(< > =)
    原因是服務層中有SQL優化器,可能會影響我們的優化。
    drop index idx_typeid on book;
    drop index idx_authroid on book;
    alter table book add index idx_book_at (authorid,typeid);
    explain select * from book where authorid = 1 and typeid =2 ;--複合索引at全部使用
explain select * from book where authorid > 1 and typeid =2 ; --複合索引中如果有>,則自身和右側索引全部失效。
explain select * from book where authorid = 1 and typeid >2 ;--複合索引at全部使用
  • 明顯的概率問題
explain select * from book where authorid < 1 and typeid =2 ;--複合索引at只用到了1個索引
explain select * from book where authorid < 4 and typeid =2 ;--複合索引全部失效

我們學習索引優化 ,是一個大部分情況適用的結論,但由於SQL優化器等原因 該結論不是100%正確。一般而言, 範圍查詢(> < in),之後的索引失效。

4、 like儘量以“常量”開頭,不要以'%'開頭,否則索引失效
如下

select * from xx where name like '%x%' ; --name索引失效
    
explain select * from teacher  where tname like '%x%'; --tname索引失效
explain select * from teacher  where tname like 'x%';
explain select tname from teacher  where tname like '%x%';

如果必須使用like '%x%'進行模糊查詢,可以使用索引覆蓋挽救一部分。

5、儘量不要使用類型轉換(顯示、隱式),否則索引失效
explain select * from teacher where tname = 'abc' ;
explain select * from teacher where tname = 123 ;//程序底層將 123 -> '123',即進行了類型轉換,因此索引失效
6、儘量不要使用or,否則索引失效
explain select * from teacher where tname ='' or tcid >1 ; --將or左側的tname 失效。
7、一些其他的優化方法
  • (1)exist和in
    select ..from table where exist (子查詢) ;
    select ..from table where 字段 in  (子查詢) ;

如果主查詢的數據集大,則使用In ,效率高。
如果子查詢的數據集大,則使用exist,效率高。

  • (2)order by 優化
    using filesort 有兩種算法:雙路排序、單路排序 (根據IO的次數)
    MySQL4.1之前 默認使用 雙路排序;雙路:掃描2次磁盤(1:從磁盤讀取排序字段 ,對排序字段進行排序(在buffer中進行的排序) 2:掃描其他字段 )
    --IO較消耗性能

MySQL4.1之後 默認使用 單路排序 : 只讀取一次(全部字段),在buffer中進行排序。但種單路排序 會有一定的隱患 (不一定真的是“單路|1次IO”,有可能多次IO)。原因:如果數據量特別大,則無法 將所有字段的數據 一次性讀取完畢,因此 會進行“分片讀取、多次讀取”。
注意:單路排序 比雙路排序 會佔用更多的buffer。單路排序在使用時,如果數據大,可以考慮調大buffer的容量大小: set max_length_for_sort_data = 1024 單位byte

如果max_length_for_sort_data值太低,則mysql會自動從 單路->雙路 (太低:需要排序的列的總大小超過max_length_for_sort_data定義的字節數)

提高order by查詢的策略
a.選擇使用單路、雙路 ;調整buffer的容量大小;
b.避免select * ...
c.複合索引 不要跨列使用 ,避免using filesort
d.保證全部的排序字段 排序的一致性(都是升序 或 降序)

後記

最後SQL優化簡單記錄了,SQL優化也暫時學習到這,後期學習中會繼續完善。文章寫得不好,但是總比自己不梳理好。

在等2天就上班了,過年好像什麼都沒學到,新的一年也要加油呀。發現自己在目標規劃,以及拆解目標上面特別擅長,但是在落實到每天或每週上面執行有點差,新的一年得改掉這個習慣,自己還有很多事想去做。除了工作,還想學習其它的。

另外下個春節不這麼早回成都了,要在老家陪外婆,對爸媽也要像外婆一樣好好的

\color{red}{新的一年加油}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章