查詢的生命周期的下一步是將一個SQL轉換成一個可執行計劃,MySQL再按照這個計劃和存儲引擎進行交互 ...
查詢的生命周期的下一步是將一個SQL轉換成一個可執行計劃,MySQL再按照這個計劃和存儲引擎進行交互
語法解析器和預處理
首先,MySQL通過關鍵詞將SQL語句進行解析,並生成一顆對應的“解析樹”。MySQL解析器將使用MySQL語法規則驗證和解析查詢。比如是否使用了錯誤的關鍵字,關鍵字的順序是否正確,或者引號是否能夠正確的前後匹配。
預處理器則會根據一些MySQL規則進一步檢查解析樹是否合法。它會檢查數據表和數據列是否存在,還會解析名字和別名,看看它們是否存在歧義。
查詢優化器
當語法樹被認為是合法的時候,將轉由優化器去轉化成執行計劃。一條查詢可以有很多種執行方式,最後都返回相同的結果。優化器的作用就是找到這其中最好的執行計劃。
MySQL使用的是基於成本的優化器,它將會嘗試預測一個查詢使用某種執行計劃時的成本,並選擇其中成本最小的那一個。可以通過查詢當前會話的“Last_query_cost”的值來的值MySQL計算的當前查詢的成本。
這個結果表示MySQL的優化器認為大概需要做1040個數據頁的隨機查找才能夠完成上面的查詢。這是根據一系列的統計信息計算的來的。優化器在評估成本時並不會考慮任何層面的緩存,它假設讀取任何數據都需要一次磁碟I/O。
有很多種情況會導致MySQL優化器選擇錯誤的執行計劃,如下所示:
- 統計信息不准確。 MySQL依賴存儲引擎提供的統計信息來評估成本,但是有的存儲引擎提供的信息偏差非常大。比如InnoDB因為使用了MVCC架構,並不能維護一個數據表的行數的精確統計信息。
- 執行計劃中的成本估算不等同於實際執行的成本。 所以及時統計信息精準,優化器給出的執行計劃也可能不是最優的。比如某個執行計劃雖然需要讀取很多的頁面,但是如果這些頁面都是順序讀取獲取已經在記憶體中了的話,那麼訪問它的成本將非常小。
- MySQL的最優可能和我們想的最優是不同的。 我們理解的最優是執行時間儘可能短,但是MySQL只是基於其成本模型選擇最優的執行計劃,而有些時候並不是最快的執行方式。
- MySQL從不考慮其他正在併發執行的查詢,這可能會影響當前查詢的速度。
- MySQL並不是任何時候都基於成本的優化。 有時候它也會基於一些固定的規則,比如存在全文搜索的MATCH()子句時,MySQL會選擇使用全文索引而不是使用其他更快的索引或者WHERE條件。
- MySQL不會考慮不受其控制的操作的成本,比如執行存儲過程或者用戶自定義函數的成本。
- 優化器有時候無法去估算所有可能的執行計劃,所以可能會錯過實際上最優的執行計劃。
MySQL的查詢優化器是一個非常複雜的不見,它使用了很多優化策略來生成一個最優的執行計劃。優化策略可以簡單的分為兩種,一種是靜態優化,一種是動態優化。
靜態優化可以直接對解析樹進行分析,並完成優化。例如通過一些簡單的袋鼠變換將WHERE條件轉換成另一種等價形式,可以認為是一種“編譯時優化”。
動態優化則是和查詢的上下文相關,也可能和很多其他因素有關,這些需要在每次查詢時重新評估,可以認為是“運行時優化”
。
在執行語句和存儲過程的時候,動態優化和靜態優化的區別很重要。MySQL對查詢的靜態優化只需要做一次,但是對查詢的動態優化則在每次執行時都需要重新評估。有時候甚至在查詢的執行過程中也會重新優化。
下麵是一些MySQL可以處理的優化類型:
- 重新定義關聯表的順序
- 將外連接轉化為內連接
- 使用等價變換規則。 它可以合併和減少一些比較,還可以移除一些恆成立和一些恆不成立的判斷。
- 優化COUNT()、MIN()和MAX()。 索引和列是否為空通常可以幫助MySQL優化這類表達式。比如需要找到某一列的最小值,只需要查詢對應B-Tree索引的最左端記錄即可。
- 預估並轉化為常量表達式。 當MySQL檢測到一個表達式可以轉化為常數時,就會一直把該表達式作為常數進行優化處理。
- 覆蓋索引掃描。 當索引中的列包含所有查詢中需要使用的列的時候,MySQL就可以使用索引返回需要的數據,而無需查詢對應的數據行。
- 子查詢優化
- 提前終止查詢。 當發現已經滿足查詢需求的時候,MySQL總是能夠立刻終止查詢。一個典型的例子就是當使用了LIMIT 子句的時候。
- 等值傳播。 如果兩個列的值通過等式關聯,那麼MySQL能夠把其中一個列的WHERE條件傳遞到另一列上。
- 列表IN()的比較。 在很多的資料庫系統中,IN()完全等同於多個OR條件的子句,因為這兩者是完全等價的。但是在MySQL中,它將IN()列表中的數據先進行排序,然後通過二分查找的方式來確定列表中的值是否滿足條件,這是一個O(log n)複雜度的操作,等價轉換為OR查詢的複雜度為O(n)。對於IN()列表中有大量取值的時候,MySQL的處理速度會更快。
上面列舉的並不是MySQL優化器的全部,MySQL還會做其他大量的優化,因此我們完全沒有必要嘗試“自己會比優化器更加聰明”,這樣不僅會讓查詢更加複雜而難以維護,並且最終收益可能為0.讓優化器按照自己的方式正常工作即可。
但是如果能夠確認優化器給出的並不是最佳選擇,並且清除背後的原理那麼也可以嘗試幫助優化器作進一步的優化。比如在查詢中添加hint提示,也可以重寫查詢或者重新設計庫表結構。
數據和索引的統計信息
MySQL在伺服器層有查詢優化器,但是沒有保存數據和索引的統計信息。統計信息由存儲引擎實現,不同的存儲引擎可能會存儲不同的統計信息或者按照不同的格式存儲統計信息。
MySQL如何執行關聯操作
MySQL中的“關聯(join)”比一般意義上理解的更加廣泛。總的來說,MySQL認為任何一個查詢都是一次“關聯”——並不僅僅是一個查詢需要用到兩張表的匹配才叫關聯。
以UNION查詢為例MySQL先將一系列的單個查詢結果放到一個臨時表中,然後再重新讀出臨時表數據來完成UNION查詢。
當前MySQL關聯執行的策略如下:MySQL對任何關聯都執行嵌套迴圈關聯操作,即MySQL現在一個表中迴圈取出單條數據,然後再嵌套迴圈到下一個表中尋找匹配的行,依次下去,直到找到所有表中匹配的行為止。然後根據各個表匹配的行,返回查詢中需要的各個列。MySQL會嘗試在最後一個關聯表中查找到所有匹配的行,如果最後一個關聯表無法找到更多的行以後,MySQL就會返回上一層次關聯表,看是否能夠找到更多的匹配記錄,以此類推迭代執行。
執行計劃
和很多其他關係型資料庫不同,MySQL並不會生成查詢位元組碼來執行查詢。MySQL生成查詢的一棵指令書,然後通過存儲引擎執行完成這棵指令書並返回結果。最終的執行計劃包含了重構查詢的全部信息
關聯查詢優化器
MySQL優化器最重要的一部分就是關聯查詢優化,它決定了多個表關聯時的順序。通常多表關聯時,可以有多種不同的關聯順序來獲得相同的結果。
關聯查詢優化器則通過評估不同順序時的成本來選擇一個代價最小的關聯順序。它會遍歷每一個表然後逐個做嵌套迴圈計算每一棵可能的執行計劃樹的成本,最後返回一個最優的執行計劃。
然而,如果有超過n個表的關聯,那麼需要檢查n的階乘種關聯順序。這被稱為可能的執行計劃的“搜索空間”,搜索空間的增長速度非常快,如果我們需要關聯10個表,那麼共有3628800種不同的關聯順序。
當搜索空間非常大時,優化器會選擇使用“貪婪”搜索的方式查找“最優”的關聯順序。有時候,各個查詢的順序是不能隨意安排的,比如左連接等,這時候關聯優化器就可以根據這些規則大大減少搜索空間。
排序優化
無論如何排序都是一個成本很高的操作,所以從性能上看,應該儘可能避免排序或者儘可能避免對大量數據進行排序。
當不能使用索引生成排序結果的時候,MySQL需要自己進行排序。如果數據量小則在記憶體中進行,如果數據量大則需要使用磁碟,不過MySQL將這個過程統一稱為文件排序(filesort),即使完全是記憶體排序不需要任何磁碟文件時也是如此。
如果需要排序的數據量小於“排序緩衝區”,MySQL使用記憶體進行“快速排序”操作。如果記憶體不夠排序,那麼MySQL會將數據分塊,對每個獨立的塊使用“快速排序”進行排序,將各個塊的排序結果存放在磁碟上然後將各個排好序的快進行合併,最終返回排序結果。