轉載聲明:本文為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查詢緩存功能對於讀多寫少的應用來說,會帶來一定性能的提高,而對很多寫入任務的應用,關閉查詢緩存功能也許能夠改善一定的性能。