前言
研究SQL性能問題,其實本質就是優化索引,而優化索引,一個非常重要的工具就是執行計劃(explain),它可以模擬SQL優化器執行SQL陳述句,從而讓開發人員知道自己撰寫的SQL的運行情況,
執行計劃語法
執行計劃的語法非常簡單,就是在要執行的SQL陳述句前加上explain即可,
以我們在上一篇文章中創建的student表為例:
mysql> explain select * from student where id = 1;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | student | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
資料準備
為了更好的講明白執行計劃,我們將新建三張表,一張為employee表,一張為salary表,另一張為department表,其表結構以及資料如下:
employee表
| e_id | e_name | d_id |
|---|---|---|
| 1 | zhang | 1 |
| 2 | wang | 1 |
| 3 | song | 3 |
| 4 | liu | 2 |
| 5 | wang | 2 |
salary表
| s_id | s_salary |
|---|---|
| 1 | 11000 |
| 2 | 8000 |
| 3 | 6500 |
| 4 | 5000 |
| 5 | 7200 |
department 表
| d_id | d_name |
|---|---|
| 1 | tech |
| 2 | HR |
| 3 | PD |
三張表建表陳述句如下:
/* employee表創建 */
create table employee(
e_id int(4) auto_increment,
e_name varchar(20) default NULL,
d_id int(4),
primary key(e_id)
);
/* 創建索引 */
create unique index e_idx1 on employee(e_id);
create index e_idx2 on employee(e_name, d_id);
create index e_idx3 on employee(e_name);
/* salary表創建 */
create table salary(
s_id int(4),
s_salary decimal(15,2)
);
/* 創建索引 */
create unique index s_idx1 on salary(s_id);
create index s_idx2 on salary(s_salary);
/* department表創建 */
create table department(
d_id int(4),
d_name char(10) not NULL
);
/* 創建索引 */
create unique index d_idx1 on department(d_id);
create index d_idx2 on department(d_name);
/* employee表插入資料 */
insert into employee values(1, 'zhang', 1);
insert into employee values(2, 'wang', 1);
insert into employee values(3, 'song', 3);
insert into employee values(4, 'liu', 2);
insert into employee values(5, 'wang', 2);
/* salary表插入資料 */
insert into salary values(1, 11000);
insert into salary values(2, 8000);
insert into salary values(3, 65000);
insert into salary values(4, 5000);
insert into salary values(5, 7200);
/* department 表插入資料 */
insert into department values(1, 'tech');
insert into department values(2, 'HR');
insert into department values(3, 'PD');
如何去看執行計劃
看執行計劃,其實就是看explain所展示出來的列的含義,下面我們來逐一分析,
id
id用來表示SQL陳述句查詢的順序,它遵循三條原則:
| id | 值情況 | 執行順序 | 常見場景 |
|---|---|---|---|
| 1 | id相同 | 按順序執行,從上往下 | 關聯表查詢 |
| 2 | id不同 | id值越大,執行優先級越高 | 子查詢 |
| 3 | NULL | 表示為一個結果集,不需要用它來查詢 | union陳述句 |
為了說明id的情況,不妨做一個如下查詢:查詢HR部門,工資為5000的員工的名字,
我們很容易就能寫出SQL陳述句:
mysql> select e.e_name from employee e, salary s, department d where e.e_id = s.s_id and e.d_id = d.d_id and s.s_salary = 5000 and d.d_name = 'HR';
+--------+
| e_name |
+--------+
| liu |
+--------+
1 row in set (0.01 sec)
以上SQL陳述句沒有問題,但是我們現在要研究的并不是這個陳述句本身,而是執行計劃,所以加上執行計劃再執行一遍:
mysql> explain select e.e_name from employee e, salary s, department d where e.e_id = s.s_id and e.d_id = d.d_id and s.s_salary = 5000 and d.d_name = 'HR';
+----+-------------+-------+------------+--------+----------------+---------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+----------------+---------+---------+---------------+------+----------+-------------+
| 1 | SIMPLE | s | NULL | ref | s_idx1,s_idx2 | s_idx2 | 8 | const | 1 | 100.00 | Using where |
| 1 | SIMPLE | e | NULL | eq_ref | PRIMARY,e_idx1 | PRIMARY | 4 | testDB.s.s_id | 1 | 100.00 | Using where |
| 1 | SIMPLE | d | NULL | ref | d_idx1,d_idx2 | d_idx1 | 5 | testDB.e.d_id | 1 | 33.33 | Using where |
+----+-------------+-------+------------+--------+----------------+---------+---------+---------------+------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
從以上結果可以看到,三張表的id都為1,所以這三張表是按照從上往下的順序執行的,即 s->e->d的順序,不難看出,這個順序和我們撰寫SQL的表的順序是無關的,
注意:當id相同時,左連接和右連接可以破壞SQL的執行順序,
如果id相同,執行順序靠什么控制的?
答:如果id相同,和表中的資料條數有關,
如果我要查PD部門所有人的薪水情況,這次改用子查詢的方式:
mysql> select s.* from salary s where s.s_id = (select e.e_id from employee e where e.d_id = (select d.d_id from department d where d.d_name = 'PD'));
+------+----------+
| s_id | s_salary |
+------+----------+
| 3 | 65000.00 |
+------+----------+
1 row in set (0.00 sec)
其執行計劃如下所示:
mysql> explain select s.* from salary s where s.s_id = (select e.e_id from employee e where e.d_id = (select d.d_id from department d where d.d_name = 'PD'));
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
| 1 | PRIMARY | s | NULL | const | s_idx1 | s_idx1 | 5 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | e | NULL | index | NULL | e_idx2 | 68 | NULL | 5 | 20.00 | Using where; Using index |
| 3 | SUBQUERY | d | NULL | ref | d_idx2 | d_idx2 | 30 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
3 rows in set, 1 warning (0.00 sec)
可以看到,id為1,2,3,分別對應的表為s,e,d,根據id越大,執行優先級越高的原則,執行順序應該是d->e->s,至于原因,其實很好理解,按照常規思維,要查salary表,首先要從查employee表查出員工id,而要查employee表,則要先從department表查出部門id,因此,查詢順序就是先查department,再查employee,最后查salary,
接下來演示一個union查詢的例子,如:查詢employee表中id為1和5的員工資訊:
mysql> select * from employee where e_id = 1 union select * from employee where e_id = 5;
+------+--------+------+
| e_id | e_name | d_id |
+------+--------+------+
| 1 | zhang | 1 |
| 5 | wang | 2 |
+------+--------+------+
2 rows in set (0.01 sec)
其執行計劃如下:
mysql> explain select * from employee where e_id = 1 union select * from employee where e_id = 5;
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (0.01 sec)
上例很好的說明了這個問題,從id的值,很直觀就能看出SQL執行的順序,先執行union的表,再執行前面的表,結果集通過UNION RESULT顯示出來,
select_type
select_type按字面意思,就是查詢型別,常見的查詢型別有以下幾種:
| id | select_type | 描述 | 常見場景 |
|---|---|---|---|
| 1 | SIMPLE | 不包含任何子查詢或union查詢 | 簡單的單表查詢 |
| 2 | PRIMARY | 包含子查詢的最外層就是PRIMARY,意思為主查詢陳述句 | 子查詢 |
| 3 | SUBQUERY | select或where中包含的子查詢陳述句 |
子查詢 |
| 4 | DERIVED | from陳述句中包含的查詢(衍生查詢) | 臨時表 |
| 5 | UNION | union查詢的后一條查詢陳述句 |
union查詢 |
| 6 | UNION RESULT | union查詢的的結果集 | union查詢 |
SIMPLE
這個比較好舉例,如下面的SQL陳述句,查詢employee表中id為1的員工資訊:
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
出現SIMPLE的關鍵是,只能有當前一張表單表查詢,且不涉及任何子查詢、union查詢、臨時表查詢,
PRIMARY 和 SUBQUERY
這兩個都是子查詢中會出現的,仍然以上面那條子查詢的SQL拿來分析:
mysql> explain select s.* from salary s where s.s_id = (select e.e_id from employee e where e.d_id = (select d.d_id from department d where d.d_name = 'PD'));
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
| 1 | PRIMARY | s | NULL | const | s_idx1 | s_idx1 | 5 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | e | NULL | index | NULL | e_idx2 | 68 | NULL | 5 | 20.00 | Using where; Using index |
| 3 | SUBQUERY | d | NULL | ref | d_idx2 | d_idx2 | 30 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------+------+----------+--------------------------+
3 rows in set, 1 warning (0.00 sec)
e表和d表都是SUBQUERY,因為它們是子查詢陳述句,而s表則是PRIMARY,則是因為s表示select要輸出的表,所以屬于主查詢,
DERIVED
DERIVED一般出現在臨時表中,一般分兩種情況:
- 當from子查詢的衍生查詢只有一張表時,該臨時表就是
DERIVED; - 當from子查詢的衍生查詢中,有union查詢時,一般union的第一個查詢為
DERIVED.
如下例所示:
mysql> explain select t.* from (select e_name from employee where e_id = 1 union select e_name from employee where e_id = 5) t;
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
| 2 | DERIVED | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 3 | UNION | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
4 rows in set, 1 warning (0.00 sec)
UNION 和 UNION RESULT
仍然可以拿上面union查詢的例子來分析:
mysql> explain select * from employee where e_id = 1 union select * from employee where e_id = 5;
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
| 1 | PRIMARY | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | UNION | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+----------------+---------+---------+-------+------+----------+-----------------+
3 rows in set, 1 warning (0.01 sec)
前面第一部分查詢:select * from employee where e_id = 1,它給的是PRIMARY,第二張表的查詢select * from employee where e_id = 5就是UNION,而它們的結果集則是UNION RESULT,
table
table就是用到的表名,當有別名的時候,顯示的是別名,
| id | table | 描述 | 常見場景 |
|---|---|---|---|
| 1 | 原表名 | 當表沒有別名時,顯示的就是表名本身 | 表沒有別名 |
| 2 | 別名 | 當表有別名時,顯示的就是別名 | 表定義有別名 |
| 3 | union<m,n> | UNION查詢時id為m和n的聯表查詢結果集的顯示結果,m和n為id值 | UNION查詢 |
在前例中可以很明確的看到這點的演示,
如顯示原表名:
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
顯示別名:
mysql> explain select e.* from employee e where e.e_id = 1;
+----+-------------+-------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | e | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
從以上兩個例子可以很明顯的看出來,SQL陳述句一模一樣,第二個陳述句只是加了一個別名,所以table列顯示的就變成了別名,
partitions
partions指的是查詢涉及到的磁區,如果不涉及磁區,則顯示為NULL;如果有磁區,則顯示的是磁區情況,
要講這個,需要先說一下表磁區的概念,表磁區指的是在物理上不是一塊記憶體,但是在邏輯上仍然是一張表,這樣的好處是可以合理利用硬碟空間,從而提高效率,
查詢mysql服務是否支持表磁區:
mysql> show plugins;
創建磁區表:
mysql> create table tb_partition(
-> id int(4) auto_increment,
-> name varchar(20),
-> passwd char(20),
-> primary key(id)
-> )PARTITION BY HASH(id)
-> PARTITIONS 4
-> ;
Query OK, 0 rows affected (0.59 sec)
注意,按Hash磁區時,磁區的欄位一定要是int型,且為主鍵,如果不是,則要將其轉為主鍵才能磁區成功,
關于表磁區的更多內容,請參考這篇文章:MySQL磁區表
partitions欄位可以有以下取值:
| id | partitions | 描述 |
|---|---|---|
| 1 | NULL | 沒有表磁區,或有表磁區但是查詢資料不存在時 |
| 2 | 所有表磁區均顯示出來 | 查詢所有資料,或所查詢出來的資料覆寫到了所有的磁區 |
| 3 | 顯示具體表磁區 | 表里有資料,顯示為當前資料所在的表磁區 |
示例1:沒有表磁區,顯示為NULL,
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
示例2:有表磁區,但是查詢的結果為空,
mysql> explain select * from tb_partition where id = 10;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
| 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+
1 row in set, 1 warning (0.00 sec)
注意此時,它所展示的table也為NULL,這點在前文沒有講到,說明當使用到磁區表,且查詢資料不存在時,table取值為NULL,
示例3:查詢表中所有資料,顯示所有表磁區,
mysql> explain select * from tb_partition;
+----+-------------+--------------+-------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+-------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | tb_partition | p0,p1,p2,p3 | ALL | NULL | NULL | NULL | NULL | 4 | 100.00 | NULL |
+----+-------------+--------------+-------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
示例4:查詢結果存在,顯示資料所在的磁區,
先插入幾條資料:
insert into tb_partition values(1,'zhangsan', '123456');
insert into tb_partition values(2,'lisi', '123123');
insert into tb_partition values(3,'mayun', '123321');
insert into tb_partition values(4,'trump', '654321');
再執行查詢陳述句:
mysql> explain select * from tb_partition where id = 1;
+----+-------------+--------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | tb_partition | p1 | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+--------------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
此時顯示的磁區是p1,也就是id = 1那條資料所在的磁區,如果查詢的結果不止一條,則顯示所有資料的磁區,這點應該不難想象,就不示例了,
type
type在SQL優化中是一個很重要的概念,SQL陳述句好不好,和該欄位展示的值有很大關系,type的值有很多,常見的有以下這幾種:
| id | type | 描述 |
|---|---|---|
| 1 | SYSTEM | 連接型別的特例,表中只有一條資料,相當于系統表 |
| 2 | CONST | 根據主鍵或唯一索引的主鍵查詢查詢結果只有1條記錄 |
| 3 | eq_ref | 唯一索引掃描,對于每個索引鍵,只有一條記錄與之對應 |
| 4 | ref | 針對非唯一或非主鍵索引,查詢的結果可以有多潭訓0條 |
| 5 | range | 使用索引范圍查詢 |
| 6 | index | 遍歷索引,只查詢索引列,無須回表查詢 |
| 7 | ALL | 全域掃描,當表沒有索引或沒用到索引時會出現,基本上等于沒有任何優化 |
以上所列的順序,基本上就是性能效率從高到低的排列順序,即SYSTEM>CONST>eq_ref>ref>range>index>ALL,
需要注意的是,type欄位針對的是索引列,當表中不存在索引時,此時不管表中有多少資料,type都是ALL,實際的優化程序中,system和const級別都是可遇不可求的,能夠達到ref級別,就說明已經達到了優化的效果,
system
這種情況一般很難達到,只有當查詢系統表,衍生表只有一條資料的主查詢時能夠達到這個級別,
const
一般根據主鍵去做的單表查詢,type都是這個級別,
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
需要注意的是,當使用復合索引作為唯一索引的時候,必須復合索引中所有的列都用到,才能是const,
eq_ref
唯一性索引,對于每個索引鍵的查詢,回傳匹配唯一行資料(有且僅有1個,不能多個,不能0個),常見于唯一索引和主鍵索引,
mysql> explain select e.e_id from employee e, salary s where e.e_id = s.s_id;
+----+-------------+-------+------------+-------+----------------+--------+---------+---------------+------+----------+----------
---+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+-------+------------+-------+----------------+--------+---------+---------------+------+----------+----------
---+
| 1 | SIMPLE | e | NULL | index | PRIMARY,e_idx1 | e_idx1 | 4 | NULL | 5 | 100.00 | Using ind
ex |
| 1 | SIMPLE | s | NULL | ref | s_idx1 | s_idx1 | 5 | testDB.e.e_id | 1 | 100.00 | Using ind
ex |
+----+-------------+-------+------------+-------+----------------+--------+---------+---------------+------+----------+----------
---+
疑問:為啥出來的不是eq_ref?
ref
ref通常針對普通索引,通過索引查詢出多條資料或0條資料,
mysql> explain select * from employee where e_name = 'zhangsan';
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | ref | e_idx2,e_idx3 | e_idx2 | 63 | const | 1 | 100.00 | Using index |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
以上是查詢有結果的情況,接下來看查詢結果為0條的情況:
mysql> explain select * from employee where e_name = 'none';
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | ref | e_idx2,e_idx3 | e_idx2 | 63 | const | 1 | 100.00 | Using index |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
range
根據索引查詢的條件為一個范圍,如>,<,between ... and, like等,
我們仍然看以下幾個示例:
/*情形一:使用大于的情況*/
mysql> explain select * from employee where e_id > 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | range | PRIMARY,e_idx1 | PRIMARY | 4 | NULL | 4 | 100.00 | Using where |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
/*情形二: 使用between ... and*/
mysql> explain select * from employee where e_id between 1 and 5;
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | range | PRIMARY,e_idx1 | PRIMARY | 4 | NULL | 5 | 100.00 | Using where |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)
/*情形三: 使用like*/
mysql> explain select * from employee where e_name like 'zh%';
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | employee | NULL | range | e_idx2,e_idx3 | e_idx2 | 63 | NULL | 1 | 100.00 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.02 sec)
需要注意的是,不等于號<>(或 !=),in 語法在實際測驗中使用到的是index級別的索引,而非range,說明<> 和in實際上使索引級別下降了,因此,在上一篇文章中,在索引注意事項中,才會有盡量避免使用in和not in的說明,
同樣,like 的百分號%最好跟在后面,而不是前面,也是一樣的道理,在實際測驗中,當前面有%時,索引級別也會降為index,
/*不等號<>測驗*/
mysql> explain select * from employee where e_id <> 3;
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
----------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
----------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1 | e_idx2 | 68 | NULL | 5 | 80.00 | Using where; Us
ing index |
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
----------+
1 row in set, 1 warning (0.00 sec)
/*in 測驗*/
mysql> explain select * from employee where e_id in (1,2,3);
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1 | e_idx2 | 68 | NULL | 5 | 60.00 | Using where; Using index |
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
/* like 百分號測驗 */
mysql> explain select * from employee where e_name like '%san%';
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | employee | NULL | index | NULL | e_idx2 | 68 | NULL | 5 | 20.00 | Using where; Using index |
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
index
index指的是索引掃描樹,只要走到了索引,基本上都是這一級別,該級別僅僅比ALL高一點,
如下面這種情況:
mysql> explain select * from employee where d_id = 3;
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+-----------------
---------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+-----------------
---------+
| 1 | SIMPLE | employee | NULL | index | NULL | e_idx2 | 68 | NULL | 5 | 20.00 | Using where; Usi
ng index |
+----+-------------+----------+------------+-------+---------------+--------+---------+------+------+----------+-----------------
---------+
1 row in set, 1 warning (0.00 sec)
ALL
ALL就是全表掃描,這是最差的一種情況,等于沒有任何優化,一般當所查詢的欄位沒有索引時,使用到的就是該級別,
如:
mysql> explain select * from salary;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | salary | NULL | ALL | NULL | NULL | NULL | NULL | 5 | 100.00 | NULL |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
possible_keys 和 key
possible key和key可以放在一起來講,顧名思義,possible key就是可能用到的索引,而key則是實際用到的索引,這二者并不一定是相同的,舉一個例子:
mysql> explain select * from employee where e_id = 1 and e_name = 'zhang';
+----+-------------+----------+------------+-------+------------------------------+---------+---------+-------+------+----------+
-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered |
Extra |
+----+-------------+----------+------------+-------+------------------------------+---------+---------+-------+------+----------+
-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1,e_idx2,e_idx3 | PRIMARY | 4 | const | 1 | 100.00 |
NULL |
+----+-------------+----------+------------+-------+------------------------------+---------+---------+-------+------+----------+
-------+
可以看到,它列舉出的可能走到的索引,包括PRIMARY,e_idx1,e_idx2,e_idx3,而實際上,只使用到了PRIMARY,
為什么會這樣呢?我們先來看一下employee表的索引:
mysql> show index from employee;
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+-----------
-+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type
| Comment | Index_comment |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+-----------
-+---------+---------------+
| employee | 0 | PRIMARY | 1 | e_id | A | 5 | NULL | NULL | | BTREE
| | |
| employee | 0 | e_idx1 | 1 | e_id | A | 5 | NULL | NULL | | BTREE
| | |
| employee | 1 | e_idx2 | 1 | e_name | A | 4 | NULL | NULL | YES | BTREE
| | |
| employee | 1 | e_idx2 | 2 | d_id | A | 5 | NULL | NULL | YES | BTREE
| | |
| employee | 1 | e_idx3 | 1 | e_name | A | 4 | NULL | NULL | YES | BTREE
| | |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+-----------
-+---------+---------------+
5 rows in set (0.00 sec)
可以看到,where條件中,e_id欄位涉及到了PRIMARY和e_idx1兩個索引,e_name涉及到了e_idx2和e_idx3兩個索引,所以,由于這兩個欄位出現在了where條件中,理論上這四個索引都會出現,而事實上,因為根據PRIMARY索引查e_id就直接能查出結果,所以后面的索引自然就用不上了,
key_len
key_len代表的是索引欄位的長度,其計算方法是:
key_len = 索引欄位實際長度 + (可以為null)1 + (varchar)2
仍然以employee表為例加以說明,先看一下employee表的表結構:
mysql> desc employee;
+--------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+----------------+
| e_id | int(4) | NO | PRI | NULL | auto_increment |
| e_name | varchar(20) | YES | MUL | NULL | |
| d_id | int(4) | YES | | NULL | |
+--------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
可以看出,e_id要求是非null的,而e_name和d_id都可以是null,
因此,我們查詢以下sql陳述句的執行計劃:
mysql> show index from employee;
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+-----------
-+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type
| Comment | Index_comment |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+-----------
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
/*
* 該條SQL實際用到的是PRIMARY索引,也就是e_id,該欄位長度為int(4),要求not null,所以key_len = 4.
*/
mysql> explain select * from employee where e_name = 'zhang';
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | ref | e_idx2,e_idx3 | e_idx2 | 63 | const | 1 | 100.00 | Using index |
+----+-------------+----------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
/*
*該SQL實際使用到的索引為e_idx2,該索引的欄位是e_name,由于該欄位資料型別為varchar,且可以為空,所以key_len = 20*3(utf8字符長度) + 2(varchar) + 1(可以為null) = 63,
注意:字符長度關系為:
utf8每個字符3位元組
gbk每個字符2位元組
latin1每個字符1位元組
*/
接下來看一個索引欄位資料型別為char的例子:
mysql> show index from department;
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+---------
---+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_ty
pe | Comment | Index_comment |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+---------
---+---------+---------------+
| department | 0 | d_idx1 | 1 | d_id | A | 3 | NULL | NULL | YES | BTREE
| | |
| department | 1 | d_idx2 | 1 | d_name | A | 3 | NULL | NULL | | BTREE
| | |
+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+---------
---+---------+---------------+
2 rows in set (0.00 sec)
mysql> desc department;
+--------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+----------+------+-----+---------+-------+
| d_id | int(4) | YES | UNI | NULL | |
| d_name | char(10) | NO | MUL | NULL | |
+--------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
查詢SQL如下:
mysql> explain select * from department where d_name = 'HR';
+----+-------------+------------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| 1 | SIMPLE | department | NULL | ref | d_idx2 | d_idx2 | 30 | const | 1 | 100.00 | NULL |
+----+-------------+------------+------------+------+---------------+--------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
由于d_name欄位要求not null,非變長,所以只需要計算字符長度即可,即:key_len = 20*3 = 60.
觀察key_len,通常可以用于判斷表走到了哪個索引,尤其對于復合索引,可以非常直觀的看出其是否走了復合索引的全欄位,
為了說明該問題,我們重新建一張表test01:
mysql> create table test01(
-> id int(4),
-> name varchar(20),
-> passwd char(20),
-> inf char(50));
Query OK, 0 rows affected (0.19 sec)
--創建復合索引
mysql> create index t_idx1 on test01(id, name, passwd);
Query OK, 0 rows affected (0.16 sec)
Records: 0 Duplicates: 0 Warnings: 0
--插入1條資料
mysql> insert into test01 values(1,'zz', '123456', 'asdfgh');
Query OK, 1 row affected (0.04 sec)
通過觀察,我們知道,如果走到該索引的所有欄位,該索引長度應為: (4 + 1) + (20 * 3 + 2 + 1) + (20 * 3 + 1) = 129,
我們先來看兩個正常走到全索引的例子:
mysql> explain select * from test01 where id = 1 and name = 'zz' and passwd = '123';
+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 129 | const,const,const | 1 | 100.00 | NULL
|
+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select passwd from test01 where name = 'zz' and passwd = '123';
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
| 1 | SIMPLE | test01 | NULL | index | NULL | t_idx1 | 129 | NULL | 1 | 100.00 | Using where; Using
index |
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select passwd from test01 where passwd = '123';
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
| 1 | SIMPLE | test01 | NULL | index | NULL | t_idx1 | 129 | NULL | 1 | 100.00 | Using where; Using
index |
+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------
-------+
1 row in set, 1 warning (0.00 sec)
以上三條SQL,無論是id = 1 and name = 'zz' and passwd = '123', 還是name = 'zz' and passwd = '123',或者passwd = '123',實際在查詢中,都要按順序將三個欄位全部查到,因此都是129,
但是如果把SQL改成如下寫法:
mysql> explain select passwd from test01 where id = 1 and name = 'zz';
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------
+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------
+
| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 68 | const,const | 1 | 100.00 | Using index
|
+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------
+
1 row in set, 1 warning (0.00 sec)
發現雖然type的級別仍然是ref,走到的索引也仍然是t_idx1,但是key_len 卻只有68,也就是id和name的長度,passwd欄位雖然也在索引里,但是由于不在條件里,因此就沒有走到,
同理,下面的SQL也是一樣的道理,因為只用到了id,所以key_len只有5.
mysql> explain select passwd from test01 where id = 1;
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using index |
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
但是我們需要注意的是下面這種情況:
mysql> explain select passwd from test01 where id = 1 and passwd = '123';
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------
-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------
-------+
| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using where; Using
index |
+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------
-------+
我們在where條件里帶了id和passwd,但并不如我們想象中的key_len = 66,而是等于5,也就是說,它實際只用到了id欄位,而并沒有用到passwd,
造成這種情況的原因在于,復合索引是嚴格按照復合索引中欄位的先后順序執行的,因此要求我們寫SQL的時候,也要按照復合索引的順序去書寫(參見上一篇文章SQL優化初探-索引)
ref
注意此處的ref和前面type里出現的ref并不是同一個意思,這里的ref代表的是索引關聯了哪個欄位,
常用取值有:
| id | ref | 說明 |
|---|---|---|
| 1 | NULL | 沒有用到任何欄位 |
| 2 | const | 某個具體的值 |
| 3 | 具體某張表的欄位值 | 一般用于關聯陳述句中 |
下面仍然以例子來說明:
-- 具體的數值:const
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
--不等于任何值
mysql> explain select * from employee where e_id < 5;
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | range | PRIMARY,e_idx1 | PRIMARY | 4 | NULL | 4 | 100.00 | Using where |
+----+-------------+----------+------------+-------+----------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
--某個具體欄位
mysql> explain select * from employee where e_id in (select s_id from salary);
+----+-------------+----------+------------+-------+----------------+--------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+--------+---------+----------------------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1 | e_idx2 | 68 | NULL | 5 | 100.00 | Using index |
| 1 | SIMPLE | salary | NULL | ref | s_idx1 | s_idx1 | 5 | testDB.employee.e_id | 1 | 100.00 | Using index |
+----+-------------+----------+------------+-------+----------------+--------+---------+----------------------+------+----------+-------------+
2 rows in set, 1 warning (0.02 sec)
rows
通過索引回傳的資料條數,
filtered
回傳結果的行數占讀取行數的百分比,該數值越大越好,
如:
mysql> explain select * from employee where e_id = 1;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from employee where e_id = 1;
+------+--------+------+
| e_id | e_name | d_id |
+------+--------+------+
| 1 | zhang | 1 |
+------+--------+------+
1 row in set (0.00 sec)
查詢結果為1條,而rows也為1條,因此filtered = 1/1 = 100%.
再看下面這個例子:
mysql> explain select * from employee where e_id < 3;
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1 | e_idx2 | 68 | NULL | 5 | 40.00 | Using where; Using index |
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from employee where e_id < 3;
+------+--------+------+
| e_id | e_name | d_id |
+------+--------+------+
| 2 | wang | 1 |
| 1 | zhang | 1 |
+------+--------+------+
2 rows in set (0.00 sec)
實際查詢結果為2條,rows = 5條,因此filtered = 2/5 = 40%,
Extra
Extra是額外資訊的意思,常見的值如下:
| id | Extra | 說明 | 常見場景 |
|---|---|---|---|
| 1 | use filesort | MySQL會對資料使用非索引進行排序 | 通常見于order by |
| 2 | use temporary | 使用臨時中間表保存資料 | 通常見于group by |
| 3 | use index | select陳述句中使用了索引覆寫,避免回表訪問 | 常見于select的欄位只有索引欄位 |
| 4 | use where | 需要回表查詢 | 常見于where子句 |
以上四種情形,use filesort 和 use temporary 是比較糟糕的情況,一般出現這兩種,意味著SQL需要優化;
而如果出現use index,則說明SQL性能比較好,通常意味著效率比較高,
下面仍然以例子來說明:
mysql> explain select e_id from employee where e_id < 3 order by d_id;
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
--------------------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1 | e_idx2 | 68 | NULL | 5 | 40.00 | Using where; Us
ing index; Using filesort |
+----+-------------+----------+------------+-------+----------------+--------+---------+------+------+----------+----------------
--------------------------+
1 row in set, 1 warning (0.00 sec)
以上SQL中出現了Using filesort,探究其原因,是因為查詢的where條件是e_id,而order by的欄位卻是d_id,
在上一篇文章中提到了SQL的決議程序為:
from ... on ... join ... where ... group by ... having ... select [distinct] ... order by ... limit ...;
這就意味著,在根據e_id查詢出e_id后,還需要根據d_id進行排序,而d_id是未知的,這也就意味著有另外一次額外的查詢,
再來看第二個例子:
mysql> explain select d_id from employee where e_id < 3 group by d_id;
+----+-------------+----------+------------+-------+-----------------------+--------+---------+------+------+----------+-----------------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
|
+----+-------------+----------+------------+-------+-----------------------+--------+---------+------+------+----------+-----------------------------------------------------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1,e_idx2 | e_idx2 | 68 | NULL | 5 | 40.00 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+----------+------------+-------+-----------------------+--------+---------+------+------+----------+-----------------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
上句出現了Using temporary,原因就是因為查詢時使用的索引是e_id,但group by分組時,使用的卻是d_id,因此,需要額外的臨時空間來進行分組操作,所以就出現了Using temporary,
如果把上面陳述句改一下:
mysql> explain select d_id from employee where e_id < 3 group by e_id;
+----+-------------+----------+------------+-------+------------------------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered |
Extra |
+----+-------------+----------+------------+-------+------------------------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | index | PRIMARY,e_idx1,e_idx2,e_idx3 | PRIMARY | 4 | NULL | 5 | 40.00 |
Using where |
+----+-------------+----------+------------+-------+------------------------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
此時出現的是Using where,而沒有了之前的Using temporary,正是因為不再使用額外空間了的緣故,
最后來看這樣一個例子:
mysql> explain select e_id from employee where e_id = 3;
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | employee | NULL | const | PRIMARY,e_idx1 | PRIMARY | 4 | const | 1 | 100.00 | Using index |
+----+-------------+----------+------------+-------+----------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
此時出現的是Using index,說明在索引樹里就能查詢到所需要的結果,不需要回表查詢,效率當然會很高了,
小結
關于執行計劃,由于MySQL版本的不同,展示的欄位也有所不同,比如MySQL5.5就沒有partitions和filtered欄位的展示,對于某些欄位的含義也不盡相同,如MySQL5.5中,根據唯一索引查詢到的記錄為0條,type值為ref,但是在MySQL5.7中,type為eq_ref,這些細微的區別其實并不影響對執行計劃的解讀,只需要在使用的程序中稍加注意就行了,于實際SQL的優化并沒有太大的影響,
總之,執行計劃只是一個分析性能的工具,掌握該工具并不在于死記硬背,而在于探索和實踐,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/62720.html
標籤:MySQL
上一篇:MySQL優化之索引
