天天看點

mysql驅動表優化_Mysql 驅動表查詢優化

SELECT post.*

FROMpost

STRAIGHT_JOIN post_tagON post.id =post_tag.post_idWHERE post.status = 1 AND post_tag.tag_id = 123

ORDER BY post.created DESCLIMIT100

試着用EXPLAIN查詢一下SQL執行計劃(篇幅所限,結果有删減):

+----------+----------------+--------+-------------+

| table | key | rows | Extra |

+----------+----------------+--------+-------------+

| post | status_created | 119340 | Using where |

| post_tag | post_id | 1 | Using where |

+----------+----------------+--------+-------------+

對比優化前後兩次EXPLAIN的結果來看,優化後的SQL雖然「rows」更大了,但是沒有了「Using filesort」,綜合來看,性能依然得到了提升。

提醒:注意兩次EXPLAIN結果中各個表出現的先後順序,稍後會解釋。

解釋:

對第一條SQL而言,為什麼MySQL優化器選擇了一個耗時的執行方案?對第二條SQL而言,為什麼把連接配接方式改成STRAIGHT_JOIN之後就提升了性能?

這一切還得從MySQL對多表連接配接的處理方式說起,首先MySQL優化器要确定以誰為驅動表,也就是說以哪個表為基準,在處理此類問題時,MySQL優化器采用了簡單粗暴的解決方法:哪個表的結果集小,就以哪個表為驅動表,當然MySQL優化器實際的處理方式會複雜許多。

說明:在EXPLAIN結果中,第一行出現的表就是驅動表。

繼續post連接配接post_tag的例子,MySQL優化器有如下兩個選擇,分别是:

以post為驅動表,通過status_created索引過濾,結果集119340行

以post_tag為驅動表,通過tag_id索引過濾,結果集71220行

顯而易見,post_tag過濾的結果集更小,是以MySQL優化器選擇它作為驅動表,可悲催的是我們還需要以post表中的created字段來排序,也就是說排序字段不在驅動表裡,于是乎不可避免的出現了「Using filesort」,甚至「Using temporary」。

知道了來龍去脈,優化起來就容易了,要盡可能的保證排序字段在驅動表中,是以必須以post為驅動表,于是乎必須借助「STRAIGHT_JOIN」強制連接配接順序。

實際上在某些特殊情況裡,排序字段可以不在驅動表裡,比如驅動表結果集隻有一行記錄,并且在連接配接其它表時,索引除了連接配接字段,還包含了排序字段,此時連接配接表後,索引中的資料本身自然就是排好序的。

四、msyql join語句執行原理

該章節是後續補充進來的,可對照上面的知識點來看。

首先,我建了一個表t2,裡面有1000條資料,有id,a,b三個字段,a字段加了索引

mysql驅動表優化_Mysql 驅動表查詢優化

然後我又建立一個t1表,裡面有100條資料,和t2表的前一百條資料一緻,也是隻有id,a,b三個字段,a字段加了索引

如下圖

mysql驅動表優化_Mysql 驅動表查詢優化

然後我們看這條語句,為了不影響效果,這裡我用了STRAIGHT_JOIN ,也就是在這條語句裡會把t1當做驅動表

select * from t1 STRAIGHT_JOIN t2 on t1.a=t2.a

那麼這條語句的執行流程就是這樣的

1.從t1表查詢出一行資料R

2.查出R這行資料的a字段的值到t2表中去查詢

3.查詢符合條件的資料和R組成一行,組裝成結果集傳回到用戶端

4.重複執行步驟1-3,知道查到t1表的末尾

總結:由于我們在t2表上的a字段建立了索引,是以在第二步的時候不需要做全表掃描,也就是說,我們執行這條語句的掃描行數是200行,

首先t1表是掃描了100行,當和t2表每行去比對的時候又掃描了t2表100行,是以這條語句總共掃描行數是200行,這種算法的掃描行數還是可以的。

對應的流程圖如下圖所示,這種算法叫作"Index Nested-Loop Join",簡稱NLJ

mysql驅動表優化_Mysql 驅動表查詢優化

select * from t1 STRAIGHT_JOIN t2 on t1.a=t2.b

然後我們在看這條語句,由于b字段沒有索引,是以在執行這條語句的時候,去t2表比對的時候就要進行全表掃描

是以這條語句執行後的掃描行數就是100*1000=10萬行

這個算法也有個名字叫做

Simple Nested-Loop Join

但是mysql沒有使用這個算法,而是使用了另一種算法,叫做

Block Nested-Loop Join,簡稱BNL

mysql驅動表優化_Mysql 驅動表查詢優化

記憶體中

2.掃描表t2,将t2表的每一行資料和join_buffer中的資料進行比對(全表掃描),符合條件的資料作為結果集的一部分傳回

這裡有個問題,如果join_buffer的大小不夠存儲t1表的資料怎麼辦呢?

其實也很簡單,就是分成多部分查詢放入join_buffer中

舉個例子:

比如說join_buffer中隻夠存儲50行資料,但是t1表有100行,那麼就先查出t1表的50行資料放入join_buffer中,然後和t2表進行比對

但是這樣就帶來了一個問題,也就是說我們要分兩次放入join_buffer中,那麼也就是說要對表t2進行兩次全表掃描

這樣掃描行數就是2200行了,不知道大家發現一個問題了沒有,這個時候影響掃描行數的因素有哪些??

第一個因素就是這個join_buffer_size這個參數,如果他足夠大,那麼我們就隻需要掃描表t2一次了,是以說有的時候我們發現了這個問題,

可以通過調大join_buffer_size這個參數來提高性能,當然不是說這個參數越大越好,要根據各方面情況來衡量。

第二個因素就是驅動表的大小,如果驅動表的資料小,那麼要麼不分段存入join_buffer中,那就隻掃描了一次表t2,要麼分段存入join_buffer中,這個時候,分段越少,那麼掃描次數就越少

也就是說驅動表的資料越小越好

是以我們要使用小表來做驅動表,小表不是說某個表的真實的資料,而是說通過目前執行的語句中條件以及查詢的字段而算出來的資料

例如

select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=100;

select t1.b,t2.* from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=100;

在這個例子中表t1隻查詢出b字段放入join_buffer中

而表t2要把所有字段都放入join_buffer中,是以這個時候表t1是小表

這兩種算法顯然第一種算法也就是NLJ的性能要好,是以我們在寫sql語句的時候要盡量讓mysql使用這種算法

也就是要對連接配接的字段加上索引,如果該字段确實不适合加索引,沒辦法隻能使用第二種算法,那麼這個時候我們就要盡量使用小表來當做驅動表。