天天看點

Mysql查詢緩存研究      

Mysql查詢緩存研究      

專家簡介

Mysql查詢緩存研究      

李季鵬

【dba+社群•開源資料庫使用者組】聯合發起人

5年+mysql經驗,主導營運商去ioe技術落地,精通mysql資料庫及相關解決方案,對mysql叢集架構,高可用方案有深入的研究。

mysql的查詢緩存并非緩存執行計劃,而是查詢及其結果集,這就意味着隻有相同的查詢操作才能命中緩存,是以mysql的查詢緩存命中率很低,另一方面,對于大結果集的查詢,其查詢結果可以從cache中直接讀取,有效的提升了查詢效率。

1

工作流程和相關參數及指令

1.1 工作流程

a):伺服器接收sql,以sql+db+query_cache_query_flags作為hash查找鍵;

b):找到了相關的結果集就将其傳回給用戶端;

c):如果沒有找到緩存則執行權限驗證、sql解析、sql優化等一些列的操作;

d):執行完sql之後,将結果集儲存到緩存

1.2 相關參數及指令

與緩存相關的主要參數如下表所示。可以使用指令show variables like '%query_cache%'檢視

Mysql查詢緩存研究      

與緩存相關的狀态變量如下表所示。可以使用指令show status like '%qcache%'檢視

Mysql查詢緩存研究      

與緩存相關的指令如下:

flush query cache。用于清理查詢緩存碎片以提高記憶體使用性能。該語句不從緩存中移出任何查詢。

reset query cache。用于清空查詢緩存所有内容。

2

查詢緩存政策

當伺服器端接收到查詢包後,系統就會檢查查詢緩存中是否有該查詢,是以利用查詢緩存可以省去sql解析和處理操作,該步驟被封裝在query_cache_send_result_to_client()函數,位于sql/sql_parse.cc 的mysql_parse()函數中,而query_cache_send_result_to_client()函數則宏定義于sql/sql_cache.h,詳細過程則在sql/sql_cache.cc的send_result_to_client()函數中。

mysql使用sql + db + query_cache_query_flags作為hash查找鍵,從緩存中查找sql的結果集,而sql語句的這一部分會先去掉首尾的空格,是以首尾有無空格不會被認為是不同的sql,該過程也在send_result_to_client()函數中。

mysql得到結果集後,将結果集以包的形式發送到用戶端,在将包發送到用戶端之前會将包儲存到查詢緩存中。是否将結果集插入到查詢緩存取決于查詢sql,能夠插入到查詢緩存的對象如前所述。

該過程封裝在query_cache_insert ()函數中,該函數位于sql/sql_cache.cc中

查詢緩存初始化時,整個查詢緩存被視為1個空閑塊。當有新的查詢需要緩存時,就需要配置設定一個新的緩存塊。mysql會嘗試從所有空閑塊中找出最适合大小的記憶體塊(即大于所需塊大小的最小緩存塊)配置設定給新的查詢。如果沒找到這種塊,則查找小于所需塊大小的最大緩存塊。如果沒有空閑塊,就将最老的查詢移出緩存,而後再次配置設定記憶體,重複之前的步驟,直到找出合适的塊。

如果找到了合适大小的緩存塊,并且該塊大于所需大小,則将它分割為兩個緩存塊。新塊不能小于min_allocation_unit_bytes。每當産生一個空閑塊,系統會檢查其臨近塊中是否包含空閑塊,如果包含則将它們合并為一個空閑塊。

根據空閑塊的大小,将其存儲到不同的區域中,各區域按照存儲塊的大小以降序排列,每個空閑塊按照大小成近似對數分布。

空閑塊存儲步驟如下所述,query_cache_size為使用者設定的查詢緩存大小。

區域1:第一個區域中存儲的空閑塊大小為size <= query_cache_size >> 4,即小于等于query_cache_size/16;

區域2:第二個區域中存儲(1 + 1) *1.2個空閑塊。該區域中最小空閑塊的大小為query_cache_size>>4 >> 2。

區域n:存儲(n + 1) *1.2個空閑塊。每個區域中的空閑塊大小都會遞減2^2倍,存儲的空閑塊數會增加。最小空閑塊的大小接近min_allocation_unit位元組。

查詢緩存空閑塊及空閑區域隻會在初始化緩存大小時計算。

當要查出恰當的空閑塊時,首先需要找到适當的區域,然後計算該區域中的空閑塊數。空閑塊大小是遞增的,因為較小的空閑塊會被經常使用。

随着查詢緩存内容的增加,緩存中會産生記憶體碎片,mysql雖然有碎片合并的機制,但仍不能完全保證碎片的生成,是以在必要的時候需要手工輸入指令整理緩存。

緩存整理操作對應于flush query cache指令,其内部實作分為兩種操作:合并空閑塊、合并結果集。

(1)合并空閑塊就是将cache前端的所有空閑塊移到後端并合并成一個空閑塊。合并空閑塊的操作會掃描所有塊,将非空閑塊前移,合并前後對比如下圖所示。

Mysql查詢緩存研究      

該操作會移除找到的所有空閑塊,所删除的空閑塊的長度會被記錄,所有非空閑塊會被上移到删除的位置。

(2)結果集合并操作會掃描查詢緩存中的所有查詢,如果查詢結果未被存儲在相同的塊中,系統就會嘗試将其移到同一塊中。合并前後對比如下圖所示。

Mysql查詢緩存研究      

如果結果集合并的操作中配置設定了新塊,那麼就需要再次執行空閑塊合并操作。

3

資料結構

查詢緩存(query_cache類)是mysql查詢緩存的入口,query_cache是該類的一個全局執行個體,用于描述查詢緩存。

查詢緩存(query_cache類)的主要成員如下:

Mysql查詢緩存研究      

緩存塊是緩存内容組織的基本機關,每個緩存塊的結構如下:

Mysql查詢緩存研究      

塊頭資訊(query_cache_block)由如下成員組成:

Mysql查詢緩存研究      

查詢塊用于存儲查詢sql,其記憶體結構如下圖所示:

Mysql查詢緩存研究      

結果集用于存儲某sql的查詢結果,它與查詢塊相關聯,結構如下:

Mysql查詢緩存研究      

一個查詢對應一個結果集,同樣一個查詢塊會對應一個或多個結果集塊(因為查詢結果 集可能分為多個包發往用戶端);

結果集塊是一個雙向連結清單,查找某sql的結果集可以周遊該連結清單。

query_block連結清單的next順序表示該query的新舊。最新被命中的query被放到連結清單的最後。緩存的替換政策是替換最舊的查詢塊。

當表内容被修改時,緩存中所有該表的查詢塊及其結果集塊都會被移除緩存。為了快速完成該操作,緩存中維護表連結清單,每個表塊都會指向其相關的查詢塊。

4

簡單實驗驗證 

Mysql查詢緩存研究      
Mysql查詢緩存研究      
Mysql查詢緩存研究      

第一次的sql語句執行過程

Mysql查詢緩存研究      

第二次的執行過程,與第一次相比,很明顯地看到少了很多的過程

Mysql查詢緩存研究      
Mysql查詢緩存研究      

通過源碼調式跟蹤發現,當執行一個insert/update/delete或其他使表資料變更的操作時,在傳回資訊給用戶端之前,會執行free_query_internal和free_memory_block操作(該函數位于sql/sql_cache.cc中),這裡會把相關表的緩存給清掉。而再一次執行前面已經驗證被緩存的語句時,就會發現該語句和結果集在緩存中已經沒有了,mysql緩存機制就會再一次将該sql和結果集緩存;

Mysql查詢緩存研究      

5

總結

(1)mysql的查詢緩存使用率很低,原因是每當有修改表内容操作時,緩存中所有與該表相關的内容全部要被清空。

(2)查詢緩存是一次申請query_cache_size大小的記憶體,而不是随查詢的插入動态申請,這樣提升了系統性能,因為申請、釋放記憶體的操作很慢。

(3)查詢緩存的空閑塊是有序的,因為較小的塊會被經常使用,同樣為了性能考慮。

(4)為充分利用記憶體,某緩存塊填充資料後,如果還有空閑空間,則将空閑空間回收。

(5)緩存替換政策是,當緩存沒有空閑塊時,系統将最老(最近沒有被使用)的查詢塊剔除。

(6)緩存命中率的計算:qcache_hits/(qcache_hits+com_select)

(7)完全相同的sql才會命中緩存。在查詢緩存中搜尋結果前,mysql不會對sql進行解析,是以,查詢緩存的查找方式是位元組比對。也就是說,如果sql中包含不确定内容(即大小寫不同、注釋不同)、多餘的空格都會被認為是不同的sql。

(8)mysql的緩存對象如下:

隻緩存select語句。show指令和存儲程式不會被緩存。

不能緩存預編譯語句(prepared statement)和遊标。查詢緩存中儲存的是查詢語句和結果集,而預編譯語句中存在替代符和額外的參數,遊标從塊中讀取結果,是以上述兩種情況不能被緩存。

查詢語句不能包含動态内容。多次執行某sql,必須能夠傳回相同的結果集,是以查詢中不能包含像uuid(), rand(), connection_id()這樣的函數。

sql中包含定義函數和自定義變量不會被緩存。

mysql> set @id=1;

mysql> select * from test where id=@id 像這種語句也不會緩存

對系統表的查詢不會被緩存。

mysql> select * from mysql.user where user=’root’

非自動送出(顯示使用begin…end)事務中的sql不會被緩存。

使用temporary表的sql不會被緩存。

不使用任何表的sql不會被緩存。

mysql> select @id;

在下面的select操作也不會被緩存:

Mysql查詢緩存研究      

  (9)表内容更改時緩存失效。修改表的所有操作(delete/insert/update等等),都會導緻緩存中該表的所有内容(sql和結果集)被一次清空。很有可能這些操作并沒有改變sql的結果集,但mysql無法驗證哪些sql會影響緩存而哪些sql不會影響。也是由于這點,mysql的緩存使用率不是很高。對于寫操作頻繁的應用,查詢緩存的命中率會相當的低。如果緩存中存在某表的大量sql,多少也會降低該表的更新速度。

(10)緩存碎片。随着緩存量的增多,查詢緩存會産生碎片,這将降低緩存性能。狀态變量qcache_free_blocks描述了緩存中的空閑塊,該值越大表示碎片越多。可以用flush query cache指令來整理碎片,而對于大緩存,該操作會長時間阻塞查詢緩存。

是以,開啟query_cachel查詢緩存功能對于讀多寫少的應用來說,會帶來一定性能的提高,而對很多寫入任務的應用,關閉查詢緩存功能也許能夠改善一定的性能。

<b></b>

<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2015-12-20</b>