MySQL 執行優化查詢


查詢執行的基礎

當希望MySQL能夠以更高的性能運行查詢時,最好的辦法就是弄清楚MySQL是如何優化和執行查詢的。當向MySQL發送一個請求的時候,MySQL執行過程如圖1-1所示:

圖1-1 查詢執行路徑

  1. 客戶端發送一條查詢給服務器。
  2. 服務器先檢查查詢緩存,如果命中了緩存,則立刻返回存儲在緩存中的結果。否則進入下一階段。
  3. 服務器端進行SQL解析、預處理,再由優化器生成對應的執行計划。
  4. MySQL根據優化器生成的執行計划,調用存儲引擎的API來執行查詢。
  5. 將結果返回給客戶端。

MySQL客戶端/服務器通信協議

一般來說,不需要去理解MySQL通信協議的內部實現細節,只需要大致理解通信協議是如何工作的。MySQL客戶端和服務器之間的通信協議是“半雙工”的,這意味着,在任何一個時刻,要么是由服務器向客戶端發送數據,要么是由客戶端向服務器發送數據,這兩個動作不能同時發生。所以,我們無法也無須將一個消息切成小塊獨立來發送。

查詢緩存

在解析一個查詢語句之前,如果查詢緩存是打開的,那么MySQL會優先檢查這個查詢是否命中查詢緩存中的數據。這個檢查是通過一個對大小寫敏感的哈希查找實現的。如果當前的查詢恰好命中了查詢緩存,那么在返回查詢結果之前
MySQL會檢查一次用戶權限。

查詢優化處理

語法解析器和預處理

首先,MySQL通過關鍵字將SQL語句進行解析,並生成一棵對應的“解析樹”。MySQL解析器將使用MySQL語法規則驗證和解析查詢。例如,它將驗證是否使用錯誤的關鍵字,或者使用關鍵字的順序是否正確等,再或者它還會驗證引號是否能前后正確匹配。預處理器則根據一些MySQL規則進一步檢查解析樹是否合法,例如,這里將檢查數據表和數據列是否存在,還會解析名字和別名,看看它們是否有歧義。

查詢優化器

現在語法樹被認為是合法的了,並且由優化器將其轉化成執行計划。一條查詢可以有很多種執行方式,最后都返回相同的結果。優化器的作用就是找到這其中最好的執行計划。MySQL使用基於成本的優化器,它將嘗試預測一個查詢使用某種執行計划時的成本,並選擇其中成本最小的一個(並不是最快)。
優化策略可以簡單地分為兩種,一種是靜態優化,一種是動態優化。靜態優化可以直接對解析樹進行分析,並完成優化。相反,動態優化則和查詢的上下文有關,也可能和很多其他因素有關,例如WHERE 條件中的取值、索引中條目對應的數據行數等。MySQL能夠處理的優化類型如下:

  • 重新定義關聯表的順序,數據表的關聯並不總是按照在查詢中指定的順序進行。
  • 將外連接轉化成內連接,並不是所有的OUTER JOIN 語句都必須以外連接的方式執行。諸多因素,例如WHERE 條件、庫表結構都可能會讓外連接等價於一個內連接。
  • 使用等價變換規則,MySQL可以使用一些等價變換來簡化並規范表達式。例如,(5=5 AND a>5 )將被改寫為a>5。
  • 優化COUNT()、MIN()和MAX(),索引和列是否可為空通常可以幫助MySQL優化這類表達式,B-Tree索引的最左最右端可分別對應最大值和最小值。
  • 預估並轉化為常數表達式,當MySQL檢測到一個表達式可以轉化為常數的時候,就會一直把該表達式作為常數進行優化處理。
  • 覆蓋索引掃描,當索引中的列包含所有查詢中需要使用的列的時候,MySQL就可以使用索引返回需要的數據。
  • 子查詢優化,MySQL在某些情況下可以將子查詢轉換一種效率更高的形式,從而減少多個查詢多次對數據進行訪問。
  • 提前終止查詢,在發現已經滿足查詢需求的時候,MySQL能夠立刻終止查詢,一個典型的例子就是當使用了LIMIT。
  • 等值傳播,如果兩個列的值通過等式關聯,那么MySQL能夠把其中一個列的WHERE 條件傳遞到另一列上。例如,我們看下面的查詢:其中,salese_region 也會應用到Where語句。
    select order.* from sales_order order inner join sales_region region on order.region_no = region.region_no where order.region_no = 86;
  • 列表IN()的比較,MySQL將IN() 列表中的數據先進行排序,然后通過二分查找的方式來確定列表中的值是否滿足條件,這是一個O(log n )復雜度的操作,等價地轉換成OR 查詢的復雜度為O(n ),對於IN() 列表中有大量
    取值的時候,MySQL的處理速度將會更快。

數據和索引的統計信息

MySQL架構由多個層次組成。在服務器層有查詢優化器,卻沒有保存數據和索引的統計信息。統計信息由存儲引擎實現,不同的存儲引擎可能會存儲不同的統計信息。因為服務器層沒有任何統計信息,所以MySQL查詢優化器在生成查詢的執行計划時,需要向存儲引擎獲取相應的統計信息。存儲引擎則提供給優化器對應的統計信息,包括:每個表或者索引有多少個頁面、每個表的每個索引的基數是多少、數據行和索引長度、索引的分布信息等。優化器根據這些信息來選擇一個最優的執行計划。

MySQL如何執行關聯查詢

MySQL認為任何一個查詢都是一次“關聯”——並不僅僅是一個查詢需要到兩個表匹配才叫關聯。當前MySQL關聯執行的策略很簡單:MySQL對任何關聯都執行嵌套循環關聯操作,即MySQL先在一個表中循環取出單條數據,然后再嵌套循環到下一個表中尋找匹配的行,依次下去,直到找到所有表中匹配的行為止。然后根據各個表匹配的行,返回查詢中需要的各個列。
按照這樣的方式查找第一個表記錄,再嵌套查詢下一個關聯表,然后回溯到上一個表,在MySQL中是通過嵌套循環的方式實現——正如其名“嵌套循環關聯”。

執行計划

MySQL生成查詢的一棵指令樹,然后通過存儲引擎執行完成這棵指令樹並返回結果。任何多表查詢都可以使用一棵樹表示,四表關聯查詢可如下圖表示:

圖1-2 多表關聯的一種方式

MySQL的執行計划總是如圖1-3所示,是一棵左測深度優先的樹。

圖1-3 MySQL實現多表關聯

關聯查詢優化器

MySQL優化器最重要的一部分就是關聯查詢優化,它決定了多個表關聯時的順序。通常多表關聯的時候,可以有多種不同的關聯順序來獲得相同的執行結果。關聯查詢優化器則通過評估不同順序時的成本來選擇一個代價最小的關聯順序。由於MySQL對關聯執行'嵌套循環關聯操作',所以在進行多表關聯查詢的時候,會將數據少的關聯表放在前面,這樣子會讓查詢進行更少的嵌套循環和回溯操作。在我們自己寫SQL時也能手動選擇,即'小表Join大表'。

排序優化

無論如何排序都是一個成本很高的操作,所以從性能角度考慮,應盡可能避免排序或者盡可能避免對大量數據進行排序。
當不能使用索引生成排序結果的時候,MySQL需要自己進行排序,如果數據量小則在內存中進行,如果數據量大則需要使用磁盤,MySQL將這個過程統一稱為文件排序(filesort )。如果需要排序的數據量小於“排序緩沖區”,MySQL使用內存進行“快速排序”操作。如果內存不夠排序,那么MySQL會先將數據分塊,對每個獨立的塊使用“快速排序”進行排序,並將各個塊的排序結果存放在磁盤上,然后將各個排好序的塊進行合並(merge),最后返回排序結果。
MySQL有如下兩種算法:
兩次傳輸排序:讀取行指針和需要排序的字段,對其進行排序,然后再根據排序結果讀取所需要的數據行。第二次讀取數據的時候,因為是讀取排序列進行排序后的所有記錄,這會產生大量的隨機I/O,所以兩次數據傳輸的成本非常高。
單次傳輸排序(新版本采用):先讀取查詢所需要的所有列,然后再根據給定列進行排序,最后直接返回排序結果。不再需要從數據表中讀取兩次數據,對於I/O密集型的應用,這樣做的效率高了很多。缺點是,如果需要返回的列非常多、非常大,會額外占用大量的空間。

MySQL在排序時,對每一個排序記錄都會分配一個足夠長的定長空間來存放。這個定長空間必須足夠長以容納其中最長的字符串,例如,如果是VARCHAR 列則需要分配其完整長度;如果使用UTF-8字符集,那么MySQL將會為每個字符預留三個字節。在庫表設計不合理的案例中,排序消耗的臨時空間可能比磁盤上的原表要大很多倍。

在關聯查詢的時候如果需要排序,MySQL會分兩種情況來處理這樣的文件排序。

  • ORDER BY 子句中的所有列都來自關聯的第一個表:MySQL在關聯處理第一個表的時候就進行文件排序,
  • ORDER BY 子句中的所有列不全來自關聯的第一個表:MySQL都會先將關聯的結果存放到一個臨時表中,然后在所有的關聯都結束后,再進行文件排序。
    當只需要返回部分排序結果的時候,例如使用了LIMIT 子句,MySQL不再對所有的結果進行排序,而是根據實際情況,選擇拋棄不滿足條件的結果,然后再進行排序。

查詢執行引擎

在解析和優化階段,MySQL將生成查詢對應的執行計划,MySQL的查詢執行引擎則根據這個執行計划來完成整個查詢。在查詢執行階段,MySQL只是簡單地根據執行計划給出的指令逐步執行。在根據執行計划逐步執行的過程中,有大量的操作需要通過調用存儲引擎實現的接口來完成,查詢中的每一個表由一個handler 的實例表示。MySQL在優化階段就為每個表創建了一個handler 實例,優化器根據這些實例的接口可以獲取表的相關信息,包括表的所有列名、索引統計信息等。

返回結果給客戶端

MySQL將結果集返回客戶端是一個增量、逐步返回的過程。例如,我們回頭看看前面的關聯操作,一旦服務器處理完最后一個關聯表,開始生成第一條結果時,MySQL就可以開始向客戶端逐步返回結果集了。
這樣處理有兩個好處:服務器端無須存儲太多的結果,也就不會因為要返回太多結果而消耗太多內存。另外,這樣的處理也讓MySQL客戶端第一時間獲得返回的結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM