同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)

《數據庫容許空值,每每是悲劇的開始》一文經過explain來分析SQL的執行計劃,來分析null對索引命中狀況的影響,有很多朋友留言,問explain結果中的type字段,ref,ALL等不同的值到底是什麼含義。mysql

今天花1分鐘簡單說下,常見的type結果及表明的含義,而且經過同一個SQL語句的性能差別,說明建對索引多麼重要。sql

explain結果中的type字段表明什麼意思?

同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
MySQL的官網解釋很是簡潔,只用了3個單詞:鏈接類型(the join type)。它描述了找到所需數據使用的掃描方式。數據庫

最爲常見的掃描方式有:架構

  • system:系統表,少許數據,每每不須要進行磁盤IO;
  • const:常量鏈接;
  • eq_ref:主鍵索引(primary key)或者非空惟一索引(unique not null)等值掃描;
  • ref:非主鍵非惟一索引等值掃描;
  • range:範圍掃描;
  • index:索引樹掃描;
  • ALL:全表掃描(full table scan);
    畫外音:這些是最多見的,你們去explain本身工做中的SQL語句,95%都是上面這些類型。

上面各種掃描方式由快到慢:
system > const > eq_ref > ref > range > index > ALL
下面一一舉例說明。ide

1、system

同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)

explain select * from mysql.time_zone;性能

上例中,從系統庫mysql的系統表time_zone裏查詢數據,掃碼類型爲system,這些數據已經加載到內存裏,不須要進行磁盤IO。測試

這類掃描是速度最快的。優化

同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)

explain select from (select from user where id=1) tmp;blog

再舉一個例子,內層嵌套(const)返回了一個臨時表,外層嵌套從臨時表查詢,其掃描類型也是system,也不須要走磁盤IO,速度超快。索引

2、const

數據準備:

create table user (
id int primary key,
name varchar(20)
)engine=innodb;

insert into user values(1,'shenjian');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');

同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
const掃描的條件爲:
(1)命中主鍵(primary key)或者惟一(unique)索引;
(2)被鏈接的部分是一個常量(const)值;

explain select * from user where id=1;

如上例,id是PK,鏈接部分是常量1。
畫外音:別搞什麼類型轉換的幺蛾子。

這類掃描效率極高,返回數據量少,速度很是快。

3、eq_ref

數據準備:

create table user (
id int primary key,
name varchar(20)
)engine=innodb;

insert into user values(1,'shenjian');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');

create table user_ex (
id int primary key,
age int
)engine=innodb;

insert into user_ex values(1,18);
insert into user_ex values(2,20);
insert into user_ex values(3,30);
insert into user_ex values(4,40);
insert into user_ex values(5,50);
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
eq_ref掃描的條件爲,對於前表的每一行(row),後表只有一行被掃描。

再細化一點:
(1)join查詢;
(2)命中主鍵(primary key)或者非空惟一(unique not null)索引;
(3)等值鏈接;

explain select * from user,user_ex where user.id=user_ex.id;
如上例,id是主鍵,該join查詢爲eq_ref掃描。

這類掃描的速度也異常之快。

4、ref

數據準備:
create table user (
id int,
name varchar(20) ,
index(id)
)engine=innodb;

insert into user values(1,'shenjian');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');

create table user_ex (
id int,
age int,
index(id)
)engine=innodb;

insert into user_ex values(1,18);
insert into user_ex values(2,20);
insert into user_ex values(3,30);
insert into user_ex values(4,40);
insert into user_ex values(5,50);
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
若是把上例eq_ref案例中的主鍵索引,改成普通非惟一(non unique)索引。

explain select * from user,user_ex where user.id=user_ex.id;
就由eq_ref降級爲了ref,此時對於前表的每一行(row),後表可能有多於一行的數據被掃描。
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)

explain select * from user where id=1;
當id改成普通非惟一索引後,常量的鏈接查詢,也由const降級爲了ref,由於也可能有多於一行的數據被掃描。

ref掃描,可能出如今join裏,也可能出如今單表普通索引裏,每一次匹配可能有多行數據返回,雖然它比eq_ref要慢,但它仍然是一個很快的join類型。

5、range

數據準備:
create table user (
id int primary key,
name varchar(20)
)engine=innodb;

insert into user values(1,'shenjian');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');
insert into user values(4,'wangwu');
insert into user values(5,'zhaoliu');
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
range掃描就比較好理解了,它是索引上的範圍查詢,它會在索引上掃碼特定範圍內的值。

explain select from user where id between 1 and 4;
explain select
from user where idin(1,2,3);
explain select * from user where id>3;
像上例中的between,in,>都是典型的範圍(range)查詢。
畫外音:必須是索引,不然不能批量"跳過"。

6、index

同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
index類型,須要掃描索引上的所有數據。

explain count (*) from user;
如上例,id是主鍵,該count查詢須要經過掃描索引上的所有數據來計數。
畫外音:此表爲InnoDB引擎。

它僅比全表掃描快一點。

7、ALL

數據準備:
create table user (
id int,
name varchar(20)
)engine=innodb;

insert into user values(1,'shenjian');
insert into user values(2,'zhangsan');
insert into user values(3,'lisi');

create table user_ex (
id int,
age int
)engine=innodb;

insert into user_ex values(1,18);
insert into user_ex values(2,20);
insert into user_ex values(3,30);
insert into user_ex values(4,40);
insert into user_ex values(5,50);
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)
explain select * from user,user_ex where user.id=user_ex.id;
若是id上不建索引,對於前表的每一行(row),後表都要被全表掃描。

今天這篇文章中,這個相同的join語句出現了三次:
(1)掃描類型爲eq_ref,此時id爲主鍵;
(2)掃描類型爲ref,此時id爲非惟一普通索引;
(3)掃描類型爲ALL,全表掃描,此時id上無索引;

有此可見,創建正確的索引,對數據庫性能的提高是多麼重要。

另外,《類型轉換帶來的大坑》中,也提到不正確的SQL語句,可能致使全表掃描。

全表掃描代價極大,性能很低,是應當極力避免的,經過explain分析SQL語句,很是有必要。

總結

(1)explain結果中的type字段,表示(廣義)鏈接類型,它描述了找到所需數據使用的掃描方式;
(2)常見的掃描類型有:
system>const>eq_ref>ref>range>index>ALL
其掃描速度由快到慢;
(3)各種掃描類型的要點是:

  • system最快:不進行磁盤IO
  • const:PK或者unique上的等值查詢
  • eq_ref:PK或者unique上的join查詢,等值匹配,對於前表的每一行(row),後表只有一行命中
  • ref:非惟一索引,等值匹配,可能有多行命中
  • range:索引上的範圍掃描,例如:between/in/>
  • index:索引上的全集掃描,例如:InnoDB的count
  • ALL最慢:全表掃描(full table scan)
    (4)創建正確的索引(index),很是重要;
    (5)使用explain瞭解並優化執行計劃,很是重要;

思路比結論重要,但願你們有收穫。
畫外音:本文測試於MySQL5.6。
同一個SQL語句,爲啥性能差別咋就這麼大呢?(1分鐘系列)架構師之路-分享技術思路相關推薦:《緩衝池(buffer pool),此次完全懂了!!!》《寫緩衝(change buffer),此次完全懂了!!!》《兩類很是隱蔽的全表掃描 | 1分鐘系列》《MyISAM與InnoDB的索引差別 | 1分鐘系列》《數據庫容許null,悲劇的開始 | 1分鐘系列》

相關文章
相關標籤/搜索