mysql 子查询优化一例
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
寫(xiě)在前面的話:——MySQL 的子查詢?yōu)槭裁从袝r(shí)候很糟糕——
引子:這樣的子查詢?yōu)槭裁催@么慢?
下面的例子是一個(gè)慢查,線上執(zhí)行時(shí)間相當(dāng)夸張。為什么呢?
SELECT gid,COUNT(id) as count?
FROM shop_goods g1
WHERE status =0 and gid IN (?
SELECT gid FROM shop_goods g2 WHERE sid IN ?(1519066,1466114,1466110,1466102,1466071,1453929)
)
GROUP BY gid;
它的執(zhí)行計(jì)劃如下,請(qǐng)注意看關(guān)鍵詞“DEPENDENT SUBQUERY”:
??? id? select_type???????? table?? type??????????? possible_keys?????????????????????????? key?????????? key_len? ref?????? rows? Extra???? ?
------? ------------------? ------? --------------? --------------------------------------? ------------? -------? ------? ------? -----------
???? 1? PRIMARY?????????????g1????? index?????????? (NULL)????????????????????????????????? idx_gid? 5??????? (NULL)??850672?Using where
???? 2??DEPENDENT SUBQUERY??g2????? index_subquery? id_shop_goods,idx_sid,idx_gid? idx_gid? 5??????? func???????? 1? Using where
?
基礎(chǔ)知識(shí):Dependent Subquery意味著什么
官方含義為:
SUBQUERY:子查詢中的第一個(gè)SELECT;
DEPENDENT SUBQUERY:子查詢中的第一個(gè)SELECT,取決于外面的查詢?。
換句話說(shuō),就是?子查詢對(duì) g2 的查詢方式依賴于外層 g1 的查詢。
什么意思呢?它意味著兩步:
第一步,MySQL 根據(jù)?select gid,count(id) from shop_goods where?status=0 group by gid;?得到一個(gè)大結(jié)果集 t1,其數(shù)據(jù)量就是上圖中的 rows=850672 了。
第二步,上面的大結(jié)果集 t1 中的每一條記錄,都將與子查詢 SQL 組成新的查詢語(yǔ)句:select gid from shop_goods where sid in (15...blabla..29) and gid=%t1.gid%。等于說(shuō),子查詢要執(zhí)行85萬(wàn)次……即使這兩步查詢都用到了索引,但不慢才怪。
如此一來(lái),子查詢的執(zhí)行效率居然受制于外層查詢的記錄數(shù),那還不如拆成兩個(gè)獨(dú)立查詢順序執(zhí)行呢。
?
優(yōu)化策略1:
你不想拆成兩個(gè)獨(dú)立查詢的話,也可以與臨時(shí)表聯(lián)表查詢,如下所示:
SELECT g1.gid,count(1)
FROM shop_goods g1,(select gid from shop_goods WHERE sid in (1519066,1466114,1466110,1466102,1466071,1453929)) g2
where g1.status=0 and?g1.gid=g2.gid
GROUP BY g1.gid;
也能得到同樣的結(jié)果,且是毫秒級(jí)。
它的執(zhí)行計(jì)劃為:
??? id? select_type? table?????????? type??? possible_keys????????????? key??????????? key_len? ref??????????? rows? Extra???????????????????????? ?
------? -----------? --------------? ------? -------------------------? -------------? -------? -----------? ------? -------------------------------
???? 1? PRIMARY??????<derived2>????? ALL???? (NULL)???????????????????? (NULL)???????? (NULL)?? (NULL)?????????? 30? Using temporary; Using filesort
???? 1? PRIMARY????? g1????????????? ref???? idx_gid?????????????? idx_gid?? 5??????? g2.gid?????? 1? Using where?????????????????? ?
???? 2??DERIVED??????shop_goods? range?? id_shop_goods,idx_sid? id_shop_goods? 5??????? (NULL)?????????? 30? Using where; Using index??????
DERIVED 的官方含義為:
DERIVED:用于 from 子句里有子查詢的情況。MySQL 會(huì)遞歸執(zhí)行這些子查詢,把結(jié)果放在臨時(shí)表里。
?
DBA觀點(diǎn)引用:MySQL 子查詢的弱點(diǎn)
hidba 論述道(參考資源3):
mysql 在處理子查詢時(shí),會(huì)改寫(xiě)子查詢。
通常情況下,我們希望由內(nèi)到外,先完成子查詢的結(jié)果,然后再用子查詢來(lái)驅(qū)動(dòng)外查詢的表,完成查詢。
例如:
select * from test where tid in(select fk_tid from sub_test where gid=10)
通常我們會(huì)感性地認(rèn)為該 sql 的執(zhí)行順序是:
sub_test 表中根據(jù) gid 取得 fk_tid(2,3,4,5,6)記錄,
然后再到 test 中,帶入 tid=2,3,4,5,6,取得查詢數(shù)據(jù)。
但是實(shí)際mysql的處理方式為:
select * from test where exists (
select * from sub_test where gid=10 and sub_test.fk_tid=test.tid
)
mysql 將會(huì)掃描 test 中所有數(shù)據(jù),每條數(shù)據(jù)都將會(huì)傳到子查詢中與 sub_test 關(guān)聯(lián),子查詢不會(huì)先被執(zhí)行,所以如果 test 表很大的話,那么性能上將會(huì)出現(xiàn)問(wèn)題。
?
《高性能MySQL》一書(shū)的觀點(diǎn)引用
《高性能MySQL》的第4.4節(jié)“MySQL查詢優(yōu)化器的限制(Limitations of the MySQL Query Optimizer)”之第4.4.1小節(jié)“關(guān)聯(lián)子查詢(Correlated Subqueries)”也有類似的論述:
MySQL有時(shí)優(yōu)化子查詢很糟,特別是在WHERE從句中的IN()子查詢。……
比如在sakila數(shù)據(jù)庫(kù)sakila.film表中找出所有的film,這些film的actoress包括Penelope Guiness(actor_id = 1)。可以這樣寫(xiě):
mysql> SELECT * FROM sakila.film
-> WHERE film_id IN(
-> SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);
mysql> EXPLAIN SELECT * FROM sakila.film ...;
+----+--------------------+------------+--------+------------------------+
| id | select_type ? ? ? ?| table ? ? ?| type ? | possible_keys ? ? ? ? ?|
+----+--------------------+------------+--------+------------------------+
| 1 ?| PRIMARY ? ? ? ? ? ?| film ? ? ? | ALL ? ?| NULL ? ? ? ? ? ? ? ? ? |
| 2 ?|?DEPENDENT SUBQUERY?| film_actor | eq_ref | PRIMARY,idx_fk_film_id |
+----+--------------------+------------+--------+------------------------+
根據(jù)EXPLAIN的輸出,MySQL將全表掃描film表,對(duì)找到的每行執(zhí)行子查詢,這是很不好的性能。幸運(yùn)的是,很容易改寫(xiě)為一個(gè)join查詢:
mysql> SELECT film.* FROM sakila.film
-> INNER JOIN sakila.film_actor USING(film_id)
-> WHERE actor_id = 1;
另外一個(gè)方法是通過(guò)使用GROUP_CONCAT()執(zhí)行子查詢作為一個(gè)單獨(dú)的查詢,手工產(chǎn)生IN()列表。有時(shí)候比join還快。(注:你不妨在我們的庫(kù)上試試看?SELECT goods_id,GROUP_CONCAT(cast(id as char))
FROM bee_shop_goods
WHERE shop_id IN (1519066,1466114,1466110,1466102,1466071,1453929)
GROUP BY goods_id;)
MySQL已經(jīng)因?yàn)檫@種特定類型的子查詢執(zhí)行計(jì)劃而被批評(píng)。
?
何時(shí)子查詢是好的
MySQL并不總是把子查詢優(yōu)化得很糟。有時(shí)候還是很優(yōu)化的。下面是個(gè)例子:
mysql> EXPLAIN SELECT film_id, language_id FROM sakila.film
-> WHERE NOT EXISTS(
-> SELECT * FROM sakila.film_actor
-> WHERE film_actor.film_id = film.film_id
-> )G
……(注:具體文字還是請(qǐng)閱讀《高性能MySQL》吧)
是的,子查詢并不是總是被優(yōu)化得很糟糕,具體問(wèn)題具體分析,但別忘了 explain 。
轉(zhuǎn)載于:https://my.oschina.net/linland/blog/407102
總結(jié)
以上是生活随笔為你收集整理的mysql 子查询优化一例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java计时代码
- 下一篇: MySQL中文全文检索