MySQL EXPLAIN 獨立子查詢dependent subquery 優化示例
阿新 • • 發佈:2019-02-11
(本例建立表指令碼在文章底部)
對於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》