MySQL查詢優化

首先看兩幅圖,MySQL的邏輯架構和查詢過程數據庫

   

客戶端/服務端通訊協議:緩存

   MySQL客戶端/服務端通訊協議是「半雙工」的:在任一時刻,要麼是服務器向客戶端發送數據,要麼是客戶端向服務器發送數據,這兩個動做不能同時發生。一旦一端開始發送消息,另外一端要接收完整個消息才能響應它,因此咱們沒法也無須將一個消息切成小塊獨立發送,也沒有辦法進行流量控制。服務器

  客戶端用一個單獨的數據包將查詢請求發送給服務器,因此當查詢語句很長的時候,須要設置max_allowed_packet參數。可是須要注意的是,若是查詢實在是太大,服務端會拒絕接收更多數據並拋出異常。數據結構

  與之相反的是,服務器響應給用戶的數據一般會不少,由多個數據包組成。可是當服務器響應客戶端請求時,客戶端必須完整的接收整個返回結果,而不能簡單的只取前面幾條結果,而後讓服務器中止發送。於是在實際開發中,儘可能保持查詢簡單且只返回必需的數據,減少通訊間數據包的大小和數量是一個很是好的習慣,這也是查詢中儘可能避免使用SELECT *以及加上LIMIT限制的緣由之一。架構

查詢緩存:函數

  在解析一個查詢語句前,若是查詢緩存是打開的,那麼MySQL會檢查這個查詢語句是否命中查詢緩存中的數據。若是當前查詢剛好命中查詢緩存,在檢查一次用戶權限後直接返回緩存中的結果。這種狀況下,查詢不會被解析,也不會生成執行計劃,更不會執行。性能

  MySQL將緩存存放在一個引用表(非 table,能夠認爲是相似於HashMap的數據結構),經過一個哈希值索引,這個哈希值經過查詢自己、當前要查詢的數據庫、客戶端協議版本號等一些可能影響結果的信息計算得來。因此兩個查詢在任何字符上的不一樣(例如:空格、註釋),都會致使緩存不會命中。優化

  若是查詢中包含任何用戶自定義函數、存儲函數、用戶變量、臨時表、MySQL庫中的系統表,其查詢結果都不會被緩存。好比函數NOW()或者CURRENT_DATE()會由於不一樣的查詢時間,返回不一樣的查詢結果,再好比包含CURRENT_USER或者CONNECION_ID()的查詢語句會由於不一樣的用戶而返回不一樣的結果,將這樣的查詢結果緩存起來沒有任何的意義。3d

  既然是緩存,就會失效,那查詢緩存什麼時候失效呢?MySQL的查詢緩存系統會跟蹤查詢中涉及的每一個表,若是這些表(數據或結構)發生變化,那麼和這張表相關的全部緩存數據都將失效。因此在任何的寫操做時,MySQL必須將對應表的全部緩存都設置爲失效。若是查詢緩存很是大或者碎片不少,這個操做就可能帶來很大的系統消耗,甚至致使系統僵死一下子。並且查詢緩存對系統的額外消耗也不只僅在寫操做,讀操做也不例外:指針

  1. 任何的查詢語句在開始以前都必須通過檢查,即便這條SQL語句永遠不會命中緩存

  2. 若是查詢結果能夠被緩存,那麼執行完成後,會將結果存入緩存,也會帶來額外的系統消耗

  因此並非什麼狀況下查詢緩存都會提升系統性能,緩存和失效都會帶來額外消耗,只有當緩存帶來的資源節約大於其自己消耗的資源時,纔會給系統帶來性能提高。

語法解析和預處理:

  MySQL經過關鍵字將SQL語句進行解析,並生成一顆對應的解析樹。這個過程解析器主要經過語法規則來驗證和解析。好比SQL中是否使用了錯誤的關鍵字或者關鍵字的順序是否正確等等。預處理則會根據MySQL規則進一步檢查解析樹是否合法。好比檢查要查詢的數據表和數據列是否存在等。

查詢優化:

  通過一系列操做步驟生成的語法樹被認爲是合法的了,而且由優化器將其轉化成查詢計劃。多數狀況下,一條查詢能夠有不少種執行方式,最後都返回相應的結果。優化器的做用就是找到這其中最好的執行計劃。

   MySQL使用基於成本的優化器,它嘗試預測一個查詢使用某種執行計劃時的成本,並選擇其中成本最小的一個。在MySQL能夠經過查詢當前會話的last_query_cost的值來獲得其計算當前查詢的成本。

  有很是多的緣由會致使MySQL選擇錯誤的執行計劃,好比統計信息不許確、不會考慮不受其控制的操做成本(用戶自定義函數、存儲過程)、MySQL認爲的最優跟咱們想的不同(咱們但願執行時間儘量短,但MySQL值選擇它認爲成本小的,但成本小並不意味着執行時間短)等等。

   MySQL的查詢優化器是一個很是複雜的部件,它使用了很是多的優化策略來生成一個最優的執行計劃:

  • 從新定義表的關聯順序(多張表關聯查詢時,並不必定按照SQL中指定的順序進行,但有一些技巧能夠指定關聯順序)

  • 優化MIN()和MAX()函數(找某列的最小值,若是該列有索引,只須要查找B+Tree索引最左端,反之則能夠找到最大值,具體原理見下文)

  • 提早終止查詢(好比:使用Limit時,查找到知足數量的結果集後會當即終止查詢)

  • 優化排序(在老版本MySQL會使用兩次傳輸排序,即先讀取行指針和須要排序的字段在內存中對其排序,而後再根據排序結果去讀取數據行,而新版本採用的是單次傳輸排序,也就是一次讀取全部的數據行,而後根據給定的列排序。對於I/O密集型應用,效率會高不少)

查詢執行引擎:

  在完成解析和優化階段之後,MySQL會生成對應的執行計劃,查詢執行引擎根據執行計劃給出的指令逐步執行得出結果。整個執行過程的大部分操做均是經過調用存儲引擎實現的接口來完成,這些接口被稱爲handler API。查詢過程當中的每一張表由一個handler實例表示。實際上,MySQL在查詢優化階段就爲每一張表建立了一個handler實例,優化器能夠根據這些實例的接口來獲取表的相關信息,包括表的全部列名、索引統計信息等。存儲引擎接口提供了很是豐富的功能,但其底層僅有幾十個接口,這些接口像搭積木同樣完成了一次查詢的大部分操做。

返回結果給客戶端:

  查詢執行的最後一個階段就是將結果返回給客戶端。即便查詢不到數據,MySQL仍然會返回這個查詢的相關信息,好比該查詢影響到的行數以及執行時間等。

  若是查詢緩存被打開且這個查詢能夠被緩存,MySQL也會將結果存放到緩存中。

  結果集返回客戶端是一個增量且逐步返回的過程。有可能MySQL在生成第一條結果時,就開始向客戶端逐步返回結果集了。這樣服務端就無須存儲太多結果而消耗過多內存,也可讓客戶端第一時間得到返回結果。須要注意的是,結果集中的每一行都會以一個知足①中所描述的通訊協議的數據包發送,再經過TCP協議進行傳輸,在傳輸過程當中,可能對MySQL的數據包進行緩存而後批量發送。

MySQL整個查詢執行過程:

  • 客戶端向MySQL服務器發送一條查詢請求

  • 服務器首先檢查查詢緩存,若是命中緩存,則馬上返回存儲在緩存中的結果。不然進入下一階段

  • 服務器進行SQL解析、預處理、再由優化器生成對應的執行計劃

  • MySQL根據執行計劃,調用存儲引擎的API來執行查詢

  • 將結果返回給客戶端,同時緩存查詢結果

相關文章
相關標籤/搜索