Mysql查詢緩存研究


轉載聲明:本文為DBA+社群原創文章,轉載必須連同本訂閱號二維碼全文轉載,並注明作者名字及來源:DBA+社群(dbaplus)。

http://mp.weixin.qq.com/s?__biz=MzI4NTA1MDEwNg==&mid=401573120&idx=1&sn=ccb988eebf1c4339eed68807a0ea490b&scene=21#wechat_redirect

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

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

 

1.1 工作流程

 

A):服務器接收SQL,以SQL+DB+Query_cache_query_flags作為hash查找鍵;

B):找到了相關的結果集就將其返回給客戶端;

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

D):執行完SQL之后,將結果集保存到緩存

 

1.2 相關參數及命令

 

與緩存相關的主要參數如下表所示。可以使用命令SHOW VARIABLES LIKE '%query_cache%'查看

 

 

與緩存相關的狀態變量如下表所示。可以使用命令SHOW STATUS LIKE '%Qcache%'查看

 

 

與緩存相關的命令如下:

  • FLUSH QUERY CACHE。用於清理查詢緩存碎片以提高內存使用性能。該語句不從緩存中移出任何查詢。

  • RESET QUERY CACHE。用於清空查詢緩存所有內容。

 

 

2

查詢緩存策略

 

2.1 在查詢緩存中查找結果

 

當服務器端接收到查詢包后,系統就會檢查查詢緩存中是否有該查詢,因此利用查詢緩存可以省去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()函數中。

 

2.2何時插入查詢緩存

 

MySQL得到結果集后,將結果集以包的形式發送到客戶端,在將包發送到客戶端之前會將包保存到查詢緩存中。是否將結果集插入到查詢緩存取決於查詢SQL,能夠插入到查詢緩存的對象如前所述。

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

 

2.3查找空閑塊

 

查詢緩存初始化時,整個查詢緩存被視為1個空閑塊。當有新的查詢需要緩存時,就需要分配一個新的緩存塊。MySQL會嘗試從所有空閑塊中找出最適合大小的內存塊(即大於所需塊大小的最小緩存塊)分配給新的查詢。如果沒找到這種塊,則查找小於所需塊大小的最大緩存塊。如果沒有空閑塊,就將最老的查詢移出緩存,而后再次分配內存,重復之前的步驟,直到找出合適的塊。

 

2.4空閑塊分割與合並

 

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

 

2.5空閑塊存儲

 

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

 

空閑塊存儲步驟如下所述,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字節。

 

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

 

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

 

2.6緩存整理(FLUSH QUERY CACHE)

 

隨着查詢緩存內容的增加,緩存中會產生內存碎片,MySQL雖然有碎片合並的機制,但仍不能完全保證碎片的生成,因此在必要的時候需要手工輸入命令整理緩存。

 

緩存整理操作對應於FLUSH QUERY CACHE命令,其內部實現分為兩種操作:合並空閑塊、合並結果集。

 

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

 

 

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

 

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

 

 

如果結果集合並的操作中分配了新塊,那么就需要再次執行空閑塊合並操作。

 

 

3

數據結構

 

3.1 Query_cache類

 

查詢緩存(Query_cache類)是MySQL查詢緩存的入口,query_cache是該類的一個全局實例,用於描述查詢緩存。

 

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

 

 

緩存塊是緩存內容組織的基本單位,每個緩存塊的結構如下:

 

 

塊頭信息(Query_cache_block)由如下成員組成:

 

 

3.4查詢、結果集塊

 

查詢塊用於存儲查詢SQL,其內存結構如下圖所示:

 

 

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

 

 

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

 

結果集塊是一個雙向鏈表,查找某SQL的結果集可以遍歷該鏈表。

 

query_block鏈表的next順序表示該query的新舊。最新被命中的query被放到鏈表的最后。緩存的替換策略是替換最舊的查詢塊。

 

3.5表鏈表

 

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

 

 

4

簡單實驗驗證

 

 

 

 

 

第一次的sql語句執行過程

 

 

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

 

 

 

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

 

 

 

 

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操作也不會被緩存:

 

 

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

 

(10)緩存碎片。隨着緩存量的增多,查詢緩存會產生碎片,這將降低緩存性能。狀態變量Qcache_free_blocks描述了緩存中的空閑塊,該值越大表示碎片越多。可以用FLUSH QUERY CACHE命令來整理碎片,而對於大緩存,該操作會長時間阻塞查詢緩存。

 

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


免責聲明!

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



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