1. 程式人生 > >MySQL EXPLAIN 獨立子查詢dependent subquery 優化示例

MySQL EXPLAIN 獨立子查詢dependent subquery 優化示例

(本例建立表指令碼在文章底部)

對於mysql的出現的子查詢語句,大部分都是不太好的,尤其 in() 的子查詢語句,如下:

select * from test.tabname where id in(select id from test.tabname2 where name='love');

一般會認為mysql會先執行子查詢,返回所有包含 test.tabname2 中所有符合條件的 id,外層查詢則搜尋內層的結果集。一般認為是這樣執行的。
select group_concat(id) from test.tabname2 where name='love';
--內層查詢結果:1,3,5,7,9,11,13,15,17,1

select * from test.tabname where id in(1,3,5,7,9,11,13,15,17,19);

很不幸,MySQL 不是這樣做的。MySQL 會將相關的外層表壓縮到子查詢中,它認為這樣可以更高效地查詢到資料行。也就是說,MySQL 會將查詢改寫成下面的樣子:
select * from test.tabname 
where exists(
	select * from test.tabname2 
	where tabname.id=tabname2.id 
	and tabname2.name='love');

這時,子查詢先根據 id 來關聯外部表 tabname ,因為需要 id 欄位,所以mysql 無法先執行這個子查詢,通過 explain 我們可以看到子查詢是一個相關子查詢(DEPENDENT SUBQUERY )。
mysql> explain select * from test.tabname where id in(select id from test.tabname2 where name='love');
+----+--------------------+----------+-----------------+---------------+---------+---------+------+------+-------------+
| id | select_type        | table    | type            | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+--------------------+----------+-----------------+---------------+---------+---------+------+------+-------------+
|  1 | PRIMARY            | tabname  | ALL             | NULL          | NULL    | NULL    | NULL |   30 | Using where | 
|  2 | DEPENDENT SUBQUERY | tabname2 | unique_subquery | PRIMARY       | PRIMARY | 4       | func |    1 | Using where | 
+----+--------------------+----------+-----------------+---------------+---------+---------+------+------+-------------+
2 rows in set (0.00 sec)

mysql> explain select * from test.tabname 
    -> where exists(select * from test.tabname2 where tabname.id=tabname2.id and tabname2.name='love');
+----+--------------------+----------+--------+---------------+---------+---------+-----------------+------+-------------+
| id | select_type        | table    | type   | possible_keys | key     | key_len | ref             | rows | Extra       |
+----+--------------------+----------+--------+---------------+---------+---------+-----------------+------+-------------+
|  1 | PRIMARY            | tabname  | ALL    | NULL          | NULL    | NULL    | NULL            |   30 | Using where | 
|  2 | DEPENDENT SUBQUERY | tabname2 | eq_ref | PRIMARY       | PRIMARY | 4       | test.tabname.id |    1 | Using where | 
+----+--------------------+----------+--------+---------------+---------+---------+-----------------+------+-------------+
2 rows in set (0.00 sec)

根據explain 的輸出我們可以看到,mysql 先選擇對  tabname 表進行全表掃描 30 次,然後根據返回的 id 逐個執行子查詢。如果是一個很小的表,這個查詢糟糕的效能可能還不會引起注意。如果是一個非常大的表,這個查詢的效能會非常糟糕。

解決方法:使用連線查詢

select tabname.* from test.tabname,test.tabname2 where tabname.id=tabname2.id and tabname2.name='love';
select tabname. * from test.tabname inner join test.tabname2 using(id) where tabname2.name='love';

執行計劃:
mysql> explain select tabname.* from test.tabname,test.tabname2 where tabname.id=tabname2.id and tabname2.name='love';
mysql> explain select tabname. * from test.tabname inner join test.tabname2 using(id) where tabname2.name='love';
+----+-------------+----------+--------+---------------+---------+---------+------------------+------+-------------+
| id | select_type | table    | type   | possible_keys | key     | key_len | ref              | rows | Extra       |
+----+-------------+----------+--------+---------------+---------+---------+------------------+------+-------------+
|  1 | SIMPLE      | tabname2 | ALL    | PRIMARY       | NULL    | NULL    | NULL             |   20 | Using where | 
|  1 | SIMPLE      | tabname  | eq_ref | PRIMARY       | PRIMARY | 4       | test.tabname2.id |    1 |             | 
+----+-------------+----------+--------+---------------+---------+---------+------------------+------+-------------+
2 rows in set (0.00 sec)

這次可以看到,select_type 變為 簡單查詢。首先訪問的是 tabname2 ,因為表 tabname2 的記錄比較少,只需該表全表掃描,再查詢子查詢,這省去了較多的IO。

環境指令碼:

drop table if exists tabname,tabname2;

create table tabname (  
id int auto_increment not null primary key,  
name varchar(10) null,  
indate datetime null,  
tid int null,  
key(tid),  
key(indate)  
)engine=innodb;  
  
  
create table tabname2 (  
id int auto_increment not null primary key,  
name varchar(10) null,  
indate datetime null,  
tid int null,  
key(tid),  
key(indate)  
)engine=innodb;  
  


drop procedure if exists inserttab;

delimiter //
create procedure inserttab()
begin
declare i int default 1 ;
while i<= 10 do
insert into tabname(name,indate,tid) values('love',now(),2),('lucky',now(),3),('passion',now(),4);  
insert into tabname2(name,indate,tid) values('love',now(),2),('lucky',now(),3);
set i= i + 1;
end while;
end;//
delimiter ;

call inserttab;



select count(*) from test.tabname;
select count(*) from test.tabname2;



參考:《高效能 MySQL》