mysql查詢過程(一)

2022-01-23 11:27:34 字數 3624 閱讀 7240

mysql查詢過程 :

客戶端/服務端通訊協議

mysql客戶端/服務端通訊協議是「半雙工」的:在任一時刻,要麼是伺服器向客戶端傳送資料,要麼是客戶端向伺服器傳送資料,這兩個動作不能同時發生。一旦一端開始傳送訊息,另一端要接收完整個訊息才能響應它,所以我們無法也無須將乙個訊息切成小塊獨立傳送,也沒有辦法進行流量控制。

客戶端用乙個單獨的資料報將查詢請求傳送給伺服器,所以當查詢語句很長的時候,需要設定max_allowed_packet引數。但是需要注意的是,如果查詢實在是太大,服務端會拒絕接收更多資料並丟擲異常。

與之相反的是,伺服器響應給使用者的資料通常會很多,由多個資料報組成。但是當伺服器響應客戶端請求時,客戶端必須完整的接收整個返回結果,而不能簡單的只取前面幾條結果,然後讓伺服器停止傳送。因而在實際開發中,盡量保持查詢簡單且只返回必需的資料,減小通訊間資料報的大小和數量是乙個非常好的習慣,這也是查詢中盡量避免使用select *以及加上limit限制的原因之一。

查詢快取

在解析乙個查詢語句前,如果查詢快取是開啟的,那麼mysql會檢查這個查詢語句是否命中查詢快取中的資料。如果當前查詢恰好命中查詢快取,在檢查一次使用者許可權後直接返回快取中的結果。這種情況下,查詢不會被解析,也不會生成執行計畫,更不會執行。

mysql將快取存放在乙個引用表(不要理解成table,可以認為是類似於hashmap的資料結構),通過乙個雜湊值索引,這個雜湊值通過查詢本身、當前要查詢的資料庫、客戶端協議版本號等一些可能影響結果的資訊計算得來。所以兩個查詢在任何字元上的不同(例如:空格、注釋),都會導致快取不會命中。

如果查詢中包含任何使用者自定義函式、儲存函式、使用者變數、臨時表、mysql庫中的系統表,其查詢結果

都不會被快取。比如函式now()或者current_date()會因為不同的查詢時間,返回不同的查詢結果,再比如包含current_user或者connecion_id()的查詢語句會因為不同的使用者而返回不同的結果,將這樣的查詢結果快取起來沒有任何的意義。

既然是快取,就會失效,那查詢快取何時失效呢?mysql的查詢快取系統會跟蹤查詢中涉及的每個表,如果這些表(資料或結構)發生變化,那麼和這張表相關的所有快取資料都將失效。正因為如此,在任何的寫操作時,mysql必須將對應表的所有快取都設定為失效。如果查詢快取非常大或者碎片很多,這個操作就可能帶來很大的系統消耗,甚至導致系統僵死一會兒。而且查詢快取對系統的額外消耗也不僅僅在寫操作,讀操作也不例外:

任何的查詢語句在開始之前都必須經過檢查,即使這條sql語句永遠不會命中快取

如果查詢結果可以被快取,那麼執行完成後,會將結果存入快取,也會帶來額外的系統消耗

基於此,我們要知道並不是什麼情況下查詢快取都會提高系統效能,快取和失效都會帶來額外消耗,只有當快取帶來的資源節約大於其本身消耗的資源時,才會給系統帶來效能提公升。但要如何評估開啟快取是否能夠帶來效能提公升是一件非常困難的事情,也不在本文討論的範疇內。如果系統確實存在一些效能問題,可以嘗試開啟查詢快取,並在資料庫設計上做一些優化,比如:

用多個小表代替乙個大表,注意不要過度設計

批量插入代替迴圈單條插入

合理控制快取空間大小,一般來說其大小設定為幾十兆比較合適

可以通過sql_cache和sql_no_cache來控制某個查詢語句是否需要進行快取

最後的忠告是不要輕易開啟查詢快取,特別是寫密集型應用。如果你實在是忍不住,可以將query_cache_type設定為demand,這時只有加入sql_cache的查詢才會走快取,其他查詢則不會,這樣可以非常自由地控制哪些查詢需要被快取。

語法解析和預處理

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

查詢優化

經過前面的步驟生成的語法樹被認為是合法的了,並且由優化器將其轉化成查詢計畫。多數情況下,一條查詢可以有很多種執行方式,最後都返回相應的結果。優化器的作用就是找到這其中最好的執行計畫。

mysql使用基於成本的優化器,它嘗試**乙個查詢使用某種執行計畫時的成本,並選擇其中成本最小的乙個。在mysql可以通過查詢當前會話的last_query_cost的值來得到其計算當前查詢的成本。

mysql> select * from t_message limit 10; ...省略結果集 mysql> show status like 'last_query_cost'; +-----------------+-------------+ | variable_name | value | +-----------------+-------------+ | last_query_cost | 6391.799000 | +-----------------+-------------+

示例中的結果表示優化器認為大概需要做6391個資料頁的隨機查詢才能完成上面的查詢。這個結果是根據一些列的統計資訊計算得來的,這些統計資訊包括:每張表或者索引的頁面個數、索引的基數、索引和資料行的長度、索引的分布情況等等。

有非常多的原因會導致mysql選擇錯誤的執行計畫,比如統計資訊不準確、不會考慮不受其控制的操作成本(使用者自定義函式、儲存過程)、mysql認為的最優跟我們想的不一樣(我們希望執行時間盡可能短,但mysql值選擇它認為成本小的,但成本小並不意味著執行時間短)等等。

mysql的查詢優化器是乙個非常複雜的部件,它使用了非常多的優化策略來生成乙個最優的執行計畫:

查詢執行引擎

在完成解析和優化階段以後,mysql會生成對應的執行計畫,查詢執行引擎根據執行計畫給出的指令逐步執行得出結果。整個執行過程的大部分操作均是通過呼叫儲存引擎實現的介面來完成,這些介面被稱為handler api。查詢過程中的每一張表由乙個handler例項表示。實際上,mysql在查詢優化階段就為每一張表建立了乙個handler例項,優化器可以根據這些例項的介面來獲取表的相關資訊,包括表的所有列名、索引統計資訊等。儲存引擎介面提供了非常豐富的功能,但其底層僅有幾十個介面,這些介面像搭積木一樣完成了一次查詢的大部分操作。

返回結果給客戶端

查詢執行的最後乙個階段就是將結果返回給客戶端。即使查詢不到資料,mysql仍然會返回這個查詢的相關資訊,比如該查詢影響到的行數以及執行時間等等。

如果查詢快取被開啟且這個查詢可以被快取,mysql也會將結果存放到快取中。

結果集返回客戶端是乙個增量且逐步返回的過程。有可能mysql在生成第一條結果時,就開始向客戶端逐步返回結果集了。這樣服務端就無須儲存太多結果而消耗過多記憶體,也可以讓客戶端第一時間獲得返回結果。需要注意的是,結果集中的每一行都會以乙個滿足①中所描述的通訊協議的資料報傳送,再通過tcp協議進行傳輸,在傳輸過程中,可能對mysql的資料報進行快取然後批量傳送。

回頭總結一下mysql整個查詢執行過程,總的來說分為6個步驟:

客戶端向mysql伺服器傳送一條查詢請求

伺服器首先檢查查詢快取,如果命中快取,則立刻返回儲存在快取中的結果。否則進入下一階段

伺服器進行sql解析、預處理、再由優化器生成對應的執行計畫

mysql根據執行計畫,呼叫儲存引擎的api來執行查詢

將結果返回給客戶端,同時快取查詢結果

mysql查詢過程 MySQL查詢執行過程

mysql查詢執行路徑 1.客戶端傳送一條查詢給伺服器 2.伺服器先會檢查查詢快取,如果命中了快取,則立即返回儲存在快取中的結果。否則進入下一階段 3.伺服器端進行sql解析 預處理,再由優化器生成對應的執行計畫 4.mysql根據優化器生成的執行計畫,呼叫儲存引擎的api來執行查詢 5.將結果返回...

Elasticsearch查詢過程

客戶端向集群中的某個節點 假設節點1 發起查詢請求,節點1會建立乙個from size大小的佇列 from 偏移量,size 要取回的文件個數 節點1向集群中所有其他的分片 主或從 發起查詢請求,每個分片也會建立乙個from size大小的佇列,並將查詢結果新增到佇列中。其他分片將查詢到的文件id和...

屬性查詢過程

import numbers class intfield 資料屬性描述符 實現了get和set def get self,instance,owner return self.value def set self,instance,value if not isinstance value,num...