MySQL Join算法與調優白皮書(一)

正文
Inside君發現不多有人可以完成講明白My SQL的Join類型與 算法,網上流傳着的要提高Join性能,加大變量join_buffer_size的謬論更是隨處可見。固然,也有一些無知的PGer攻擊MySQL不支持Hash Join,因此不適合一些分析類的操做。MySQL的確不支持Hash Join,也不支持Sort Merge Join,可是MySQL在Join上也有本身的獨特的優化與處理,此外,分支版本MariaDB已支持Hash Join,所以拿MySQL來作一些「簡單」的分析查詢也是徹底可以接受的。固然,若是數據量真的上去了,那麼即便支持Hash Join的傳統MPP架構的關係型數據庫可能也是不合適的,這類分析查詢或許應該交給更爲專業的 Hadoop集羣來計算。
 
Join的成本
在講述MySQL的Join類型與算法前,看看兩張表的Join的過程:

上圖的Fetch階段是指當內表關聯的列是輔助索引時,可是須要訪問表中的數據,那麼這時就須要再訪問主鍵索引才能獲得數據的過程,不論表的存儲引擎是InnoDB存儲引擎仍是MyISAM,這都是沒法避免的,只是MyISAM的回錶速度要快點,由於其輔助索引存放的就是指向記錄的指針,而InnoDB存儲引擎是索引組織表,須要再次經過索引查找才能定位數據。
 
Fetch階段也不是必須存在的,若是是彙集索引連接,那麼直接就能獲得數據,無需回表,也就沒有Fetch這個階段。另外,上述給出了兩張表之間的Join過程,多張表的Join就是繼續上述這個過程。
 
接着計算兩張表Join的成本,這裏有下列幾種概念:
外表的掃描次數,記爲O。一般外表的掃描次數都是1,即Join時掃描一次驅動表的數據便可
內表的掃描次數,記爲I。根據不一樣Join算法,內表的掃描次數不一樣
讀取表的記錄數,記爲R。根據不一樣Join算法,讀取記錄的數量可能不一樣
Join的比較次數,記爲M。根據不一樣Join算法,比較次數不一樣
回表的讀取記錄的數,記爲F。若Join的是輔助索引,可能須要回表取得最終的數據
 
評判一個Join算法是否優劣,就是查看上述這些操做的開銷是否比較小。固然,這還要考慮I/O的訪問方式,順序仍是隨機,總之Join的調優也是門藝術,並不是想象的那麼簡單。
 
Simple Nested-Loop Join
 
網上大部分說MySQL只支持Nested-Loop Join,故性能差。可是Nested-Loop join必定差嗎?Hash Join比Nested-Loop Join強?Inside君感受這樣的理解都是片面的,Hash Join可能僅是Nested-Loop Join的一種變種。因此Inside君打算從算法的角度來分析MySQL支持的Join,並以此分析對於Join語句的優化。
 
首先來看Simple Nested-Loop Join(如下簡稱SNLJ),也就是最樸素的Nested-Loop Join,其算法僞代碼以下所示:
 
For each row r in R do 
  Foreach row s in S do 
    If r and s satisfy the join condition 
      Then output the tuple
 
下圖能更好地顯示整個SNLJ的過程:

SNLJ的算法至關簡單、直接。即外表(驅動表)中的每一條記錄與內表中的記錄進行判斷。可是這個算法也是至關粗暴的,粗暴的緣由在於這個算法的開銷其實很是大。假設外表的記錄數爲R,內表的記錄數位S,根據上一節Inside君對於Join算法的評判標準來看,SNLJ的開銷以下表所示:

能夠看到讀取記錄數的成本和比較次數的成本都是S*R,也就是笛卡兒積。假設外表內表都是1萬條記錄,那麼其讀取的記錄數量和Join的比較次數都須要上億。這樣的算法開銷,Inside君也只能:呵呵。
 
Index Nested-Loop Join
 
SNLJ算法雖然簡單明瞭,可是也是至關的粗暴。所以,在Join的優化時候,一般都會建議在內表創建索引,以此下降Nested-Loop Join算法的開銷,MySQL數據庫中使用較多的就是這種算法,如下稱爲INLJ。來看這種算法的僞代碼:
 
For each row r in R do 
 lookupr in S index 
   if found s == r
      Then output the tuple
 
因爲內表上有索引,因此比較的時候再也不須要一條條記錄進行比較,而能夠經過索引來減小比較,從而加速查詢。整個過程以下圖所示:

能夠看到外表中的每條記錄經過內表的索引進行訪問,由於索引查詢的成本是比較固定的,故優化器都傾向於使用記錄數少的表做爲外表(這裏是否又會存在潛在的問題呢?)。故INLJ的算法成本以下表所示:

上表Smatch表示經過索引找到匹配的記錄數量。同時能夠發現,經過索引能夠大幅下降內表的Join的比較次數,每次比較1條外表的記錄,其實就是一次indexlookup(索引查找),而每次index lookup的成本就是樹的高度,即IndexHeight。
 
INLJ的算法並不複雜,也算簡單易懂。可是效率是否能達到用戶的預期呢?其實若是是經過表的主鍵索引進行Join,即便是大數據量的狀況下,INLJ的效率亦是至關不錯的。由於索引查找的開銷很是小,而且訪問模式也是順序的(假設大多數彙集索引的訪問都是比較順序的)。
 
大部分人詬病MySQL的INLJ慢,主要是由於在進行Join的時候可能用到的索引並非主鍵的彙集索引,而是輔助索引,這時INLJ的過程又須要多一步Fetch的過程,並且這個過程開銷會至關的大:

因爲訪問的是輔助索引,若是查詢須要訪問彙集索引上的列,那麼必要須要進行回表取數據,看似每條記錄只是多了一次回表操做,但這纔是
因爲訪問的是輔助索引,若是查詢須要訪問彙集索引上的列,那麼必要須要進行回表取數據,看似每條記錄只是多了一次回表操做,但這纔是INLJ算法最大的弊端。首先,輔助索引的index lookup是比較隨機I/O訪問操做。其次,根據index lookup再進行回表又是一個隨機的I/O操做。因此說,INLJ最大的弊端是其可能須要大量的離散操做,這在SSD出現以前是最大的瓶頸。而即便SSD的出現大幅提高了隨機的訪問性能,可是對比順序I/O,其仍是慢了不少,依然不在一個數量級上。
相關文章
相關標籤/搜索