[玩轉MySQL之四]MySQL快取機制

沈歐邦發表於2018-09-30

一、前言

在當今的各種系統中,快取是對系統效能優化的重要手段。MySQL Query Cache(MySQL查詢快取)在MySQL Server中是預設開啟的,但是網上各種資料以及有經驗的DBA都建議生產環境中把MySQL Query Cache關閉。按道理,MySQL Server預設開啟,是鼓勵使用者使用快取,但是大拿們卻建議關閉此功能,並且國內各個雲廠商提供的MySQL雲服務中預設都是關閉這個功能,這是為什麼?他們在使用中遇到了什麼坑?本文將會從以下幾方面來詳解MySQL Query Cache。1.MySQL查詢快取是什麼?2. MySQL快取規則是什麼?3. 如何配置和快取MySQL快取4. MySQL快取的優缺點5. 生產要不要開啟MySQL快取

二、 MySQL查詢快取簡介

MySQL查詢快取是MySQL中比較獨特的一個快取區域,用來快取特定Query的整個結果集資訊,且共享給所有客戶端。為了提高完全相同的Query語句的響應速度,MySQL Server會對查詢語句進行Hash計算後,把得到的hash值與Query查詢的結果集對應存放在Query Cache中。當MySQL Server開啟Query Cache之後,MySQL Server會對接收到的每一個SELECT 語句通過特定的Hash演算法計算該Query的Hash值,然後通過該hashi值到Query Cache中去匹配。

  • 如果沒有匹配,將這個hash值存放在一個hash連結串列中,並將Query的結果集存放到cache中,存放hashi值連結串列的每個hash節點存放了相應Quey結果集在cache中的地址,以及該query所涉及到一些table相關資訊;
  • 如果通過hash值匹配到了一樣的Query,則直接將cache中相應的Query結果集返回給客戶端。

目前MySQL Query Cache只會cache select語句,其他類似show ,use的語句不會被cache MySQL 的每個Query Cache都是以SQL文字作為key來儲存的,在應用Query Cache之前,SQL文字不會做任何處理。也就是說,兩個SQL語句,只要相差哪怕一個字元(例如大小寫不一樣,多一個空格,多註釋),那麼這兩個SQL將使用不同的Cache地址。如: 下面三條SQL將會被儲存在三個不同的快取裡,雖然他們的結果都是一樣的。select * FROM people where name=`surfchen`; select * FROM people where /*hey~*/ name=`surfchen`; SELECT * FROM people where name=`surfchen`;

三、MySQL快取機制

MySQL快取機制簡單的說就是快取sql文字及查詢結果,如果執行相同的SQL,伺服器直接從快取中取到結果,而不需要再去解析和執行SQL。如果表更改了,那麼使用這個表的所有快取查詢將不再有效,查詢快取中值相關條目被清空。這裡的更改指的是表中任何資料或是結構發生改變,包括INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些對映到改變了的表使用MERGE表的查詢。顯然,這對於頻繁更新的表,查詢快取是不適合的,而對於一些不常改變資料且有大量相同SQL查詢的表,查詢快取會節約很大的效能。

查詢必須是完全相同(逐位元組相同)才能夠被認為是相同的。另外,同樣的查詢字串由於其它原因可能認為是不同的。使用不同的資料庫、不同的協議版本或者不同 預設字符集的查詢被認為是不同的查詢並且分別進行快取。

需要注意的是MySQL Query Cache 是對大小寫敏感的,因為Query Cache 在記憶體中是以 HASH 結構來進行對映,HASH 演算法基礎就是組成 SQL 語句的字元,所以 任何SQL語句的改變重新cache. 

3.1 快取規則

  • 開啟了快取,MySQL Server會自動將查詢語句和結果集返回到記憶體,下次再查直接從記憶體中取;
  • 快取的結果是通過sessions共享的,所以一個client查詢的快取結果,另一個client也可以使用
  • MySQL Query Cache內容為 select 的結果集, cache 使用完整的SQL字串做 key, 並區分大小寫,空格等。即兩個SQL必須完全一致才會導致cache命中。即檢查查詢快取時,MySQL Server不會對SQL做任何處理,它精確的使用客戶端傳來的查詢,只要字元大小寫或註釋有點不同,查詢快取就認為是不同的查詢;
  • prepared statement永遠不會cache到結果,即使引數完全一樣。在 5.1 之後會得到改善。
  • where條件中如包含任何一個不確定的函式將永遠不會被cache, 比如current_date, now等。
  • date 之類的函式如果返回是以小時或天級別的,最好先算出來再傳進去。
select * from foo where date1=current_date -- 不會被 cache
select * from foo where date1=`2008-12-30` -- 被cache, 正確的做法
  • 太大的result set不會被cache (< query_cache_limit)
  • MySQL快取在分庫分表環境下是不起作用的
  • 執行SQL裡有觸發器,自定義函式時,MySQL快取也是不起作用的

3.2 快取失效

  • 在表的結構或資料發生改變時,查詢快取中的資料不再有效。如INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE會導致快取資料失效。所以查詢快取適合有大量相同查詢的應用,不適合有大量資料更新的應用。
  • 一旦表資料進行任何一行的修改,基於該表相關cache立即全部失效。

3.3 手動清理快取手動清理快取可以使用下面三個SQL

  • FLUSH QUERY CACHE; #清理查詢快取記憶體碎片
  • RESET QUERY CACHE;#從查詢快取中移除所有查詢
  • FLUSH TABLES; #關閉所有開啟的表,同時該操作會清空查詢快取中的內容

3.4 快取機制中的記憶體管理MySQL Query Cache 使用記憶體池技術,自己管理記憶體釋放和分配,而不是通過作業系統。記憶體池使用的基本單位是變長的block, 用來儲存型別、大小、資料等資訊;一個result set的cache通過連結串列把這些block串起來。block最短長度為query_cache_min_res_unit。

當伺服器啟動的時候,會初始化快取需要的記憶體,是一個完整的空閒塊。當查詢結果需要快取的時候,先從空閒塊中申請一個資料塊為引數query_cache_min_res_unit配置的空間,即使快取資料很小,申請資料塊也是這個,因為查詢開始返回結果的時候就分配空間,此時無法預知結果多大。

分配記憶體塊需要先鎖住空間塊,所以操作很慢,MySQL會盡量避免這個操作,選擇儘可能小的記憶體塊,如果不夠,繼續申請,如果儲存完時有空餘則釋放多餘的。MySQL_Query_Cache_memory_0.png

但是如果併發的操作,餘下的需要回收的空間很小,小於query_cache_min_res_unit,不能再次被使用,就會產生碎片。如圖:MySQL_Query_Cache_memory_1.png

四、MySQL快取發揮作用的情況

1、查詢快取可以降低查詢執行的時間,但是卻不能減少查詢結果傳輸的網路消耗,如果網路傳輸消耗是整個查詢過程的主要瓶頸,那麼查詢快取的作用也很小。

2、對於那些需要消耗大量資源的查詢通常都是非常適合快取的,對於複雜的SELECT語句都可以使用查詢快取,不過需要注意的是,涉及表上的UPDATE、DELETE、INSERT操作相比SELECT來說要非常少才行。

3、查詢快取命中率:Qcache_hits/(Qcahce_hits+Com_select),查詢快取命中率多大才是好的命中率,需要具體情況具體分析。只要查詢快取帶來的效率提升大於查詢快取帶來的額外消耗,即使30%的命中率也是值得。另外,快取了哪些查詢也很重要,如果被快取的查詢本身消耗巨大,那麼即使快取命中率低,對系統效能提升仍然是有好處的。

4、任何SELECT語句沒有從查詢快取中返回都稱為“快取未命中”,以如下列情況:

  • 查詢語句無法被快取,可能因為查詢中包含一個不確定的函式,或者查詢結果太大而無法快取。
  • MySQL從未處理這個查詢,所以結果也從不曾被快取過。
  • 雖然之前快取了查詢結果,但由於查詢快取的記憶體用完了,MYSQL需要刪除某些快取,或者由於資料表被修改導致快取失效。

如果伺服器上有大量快取快取未命中,但是實際上絕大查詢都被快取了,那麼一定是有如下情況發生:

  • 查詢快取還沒有完成預熱,即MySQL還沒有機會將查詢結果都快取起來。
  • 查詢語句之前從未執行過。如果應用程式不會重複執行一條查詢語句,那麼即使完成預熱仍然會有很多快取未命中。
  • 快取失效操作太多,快取碎片、記憶體不足、資料修改都會造成快取失效。可以通過引數Com_*來檢視資料修改的情況(包括Com_update,Com_delete等),還可以通過Qcache_lowmem_prunes來檢視有多少次失效是由於記憶體不足導致的。

5、有一個直觀的方法能夠反映查詢快取是否對系統有好處,推薦一個指標:”命中和寫入“的比率,即Qcache_hits和Qcache_inserts的比值。根據經驗來看,當這個比值大於3:1時通常查詢快取是有效的,如果能達到10:1最好。

6、通常可以通過觀察查詢快取記憶體的實際使用情況Qcache_free_memory,來確定是否需要縮小或者擴大查詢快取。

五、MySQL快取管理和配置

5.1 MySQL快取相關的配置引數


mysql> show variables like `%query_cache%`;
+------------------------------+---------+
| Variable_name                | Value   |
+------------------------------+---------+
| have_query_cache             | YES     |      --查詢快取是否可用
| query_cache_limit            | 1048576 |      --可快取具體查詢結果的最大值
| query_cache_min_res_unit     | 4096    |      --查詢快取分配的最小塊的大小(位元組)
| query_cache_size             | 599040  |      --查詢快取的大小
| query_cache_type             | ON      |      --是否支援查詢快取
| query_cache_wlock_invalidate | OFF     |      --控制當有寫鎖加在表上的時候,是否先讓該表相關的 Query Cache失效
+------------------------------+---------+
6 rows in set (0.02 sec)
  • have_query_cache

該MySQL Server是否支援Query Cache。

  • query_cache_limit

MySQL能夠快取的最大查詢結果,查詢結果大於該值時不會被快取。預設值是1048576(1MB)如果某個查詢的結果超出了這個值,Qcache_not_cached的值會加1,如果某個操作總是超出,可以考慮在SQL中加上SQL_NO_CACHE來避免額外的消耗。

  • query_cache_min_res_unit

查詢快取分配的最小塊的大小(位元組)。 預設值是4096(4KB)。當查詢進行的時候,MySQL把查詢結果儲存在qurey cache中,但如果要儲存的結果比較大,超過query_cache_min_res_unit的值 ,這時候mysql將一邊檢索結果,一邊進行儲存結果,所以,有時候並不是把所有結果全部得到後再進行一次性儲存,而是每次分配一塊query_cache_min_res_unit大小的記憶體空間儲存結果集,使用完後,接著再分配一個這樣的塊,如果還不不夠,接著再分配一個塊,依此類推,也就是說,有可能在一次查詢中,mysql要進行多次記憶體分配的操作。適當的調節query_cache_min_res_unit可以優化記憶體如果你的查詢結果都是一些small result,預設的query_cache_min_res_unit可能會造成大量的記憶體碎片如果你的查詢結果都是一些larger resule,你可以適當的把query_cache_min_res_unit調大

  • query_cache_size

為快取查詢結果分配的記憶體的數量,單位是位元組,且數值必須是1024的整數倍。預設值是0,即禁用查詢快取。請注意如果設定了該值,即使query_cache_type設定為0也將分配此數量的記憶體。

  • query_cache_type

設定查詢快取型別,預設為ON。設定GLOBAL值可以設定後面的所有客戶端連線的型別。客戶端可以設定SESSION值以影響他們自己對查詢快取的使用。下面的表顯示了可能的值:query_cache_type.png

  • query_cache_wlock_invalidate

如果某個表被鎖住,是否返回快取中的資料,預設關閉,也是建議的。一般情況,當客戶端對MyISAM表進行WRITE鎖定時,如果查詢結果位於查詢快取中,則其它客戶端未被鎖定,可以對該表進行查詢。將該變數設定為1,則可以對錶進行WRITE鎖定,使查詢快取內所有對該表進行的查詢變得非法。這樣當鎖定生效時,可以強制其它試圖訪問表的客戶端來等待。

5.2 開啟關閉快取

  • 開啟快取
mysql> set global query_cache_size = 600000; --設定快取記憶體大小
mysql> set global query_cache_type = ON;     --開啟查詢快取
  • 關閉快取

mysql> set global query_cache_size = 0; --設定快取記憶體大小為0, 即初始化是不分配快取記憶體
mysql> set global query_cache_type = OFF;     --關閉查詢快取

set global時需要有SUPER許可權

六、MySQL Query Cache對效能的影響

6.1 MySQL Query Cache的額外開銷MySQL_Query.png

如上圖所示: 在MySQL Server中開啟Query Cache對資料庫的讀和寫都會帶來額外的消耗:

  • 1) 讀查詢開始之前必須檢查是否命中快取。
  • 2) 如果讀查詢可以快取,那麼執行完查詢操作後,會查詢結果和查詢語句寫入快取。
  • 3) 當向某個表寫入資料的時候,必須將這個表所有的快取設定為失效,如果快取空間很大,則消耗也會很大,可能使系統僵死一段時間,因為這個操作是靠全域性鎖操作來保護的。
  • 4) 對InnoDB表,當修改一個表時,設定了快取失效,但是多版本特性會暫時將這修改對其他事務遮蔽,在這個事務提交之前,所有查詢都無法使用快取,直到這個事務被提交,所以長時間的事務,會大大降低查詢快取的命中

6.2 MySQL Query Cache碎片優化MySQL_Query_Cache_memory_flu.png如上圖所示, 沒有什麼辦法能夠完全避免碎片,但是選擇合適的query_cache_min_res_unit可以幫你減少由碎片導致的記憶體空間浪費。這個值太小,則浪費的空間更少,但是會導致頻繁的記憶體塊申請操作;如果設定得太大,那麼碎片會很多。調整合適的值其實是在平衡記憶體浪費和CPU消耗。可以通過記憶體實際消耗(query_cache_size – Qcache_free_memory)除以Qcache_queries_in_cahce計算單個查詢的平均快取大小。可以通過Qcahce_free_blocks來觀察碎片。

通過FLUSH_QUERY_CAHCE完成碎片整理,這個命令將所有的查詢快取重新排序,並將所有的空閒空間都聚焦到查詢快取的一塊區域上。

6.3 MySQL快取狀態檢視

mysql> SHOW STATUS LIKE `Qcache%`;
+-------------------------+--------+
| Variable_name           | Value  |
+-------------------------+--------+
| Qcache_free_blocks      | 1      | ----在查詢快取中的閒置塊,如果該值比較大,則說明Query Cache中的記憶體碎片可能比較多。FLUSH QUERY CACHE會對快取中的碎片進行整理,從而得到一個較大的空閒記憶體塊。
| Qcache_free_memory      | 382704 | ----剩餘快取的大小
| Qcache_hits             | 198    | ----快取命中次數
| Qcache_inserts          | 131    | ----快取被插入的次數,也就是查詢沒有命中的次數。
| Qcache_lowmem_prunes    | 0      | ----由於記憶體低而被刪除掉的快取條數,如果這個數值在不斷增長,那麼一般是Query Cache的空閒記憶體不足(通過Qcache_free_memory判斷),或者記憶體碎片較嚴重(通過Qcache_free_blocks判斷)。
| Qcache_not_cached       | 169    | ----沒有被快取的條數,有三種情況會導致查詢結果不會被快取:其一,由於query_cache_type的設定;其二,查詢不是SELECT語句;其三,使用了now()之類的函式,導致查詢語句一直在變化。
| Qcache_queries_in_cache | 128    | ----快取中有多少條查詢語句
| Qcache_total_blocks     | 281    | ----總塊數
+-------------------------+--------+
8 rows in set (0.00 sec)

6.4 Query Cache碎片率Query Cache碎片率 = Qcache_free_blocks / Qcache_total_blocks * 100%

如果Query Cache碎片率超過20%,則可以用FLUSH QUERY CACHE整理記憶體碎片;如果你的查詢都是小資料量的話,可以嘗試減小query_cache_min_res_unit。

6.5 Query Cache利用率Query Cache利用率 = (query_cache_size – Qcache_free_memory) / query_cache_size * 100%

Query Cache利用率在25%以下的話,說明query_cache_size設定的過大,可適當減小;Query Cache利用率在80%以上,而且Qcache_lowmem_prunes > 50的話,說明query_cache_size可能有點小,或者就是記憶體碎片太多。

6.6 Query Cache命中率

  • 可快取查詢的Query Cache命中率 = Qcache_hits / (Qcache_hits + Qcache_inserts) * 100%
  • 涵蓋所有查詢的Query Cache命中率 = Qcache_hits / (Qcache_hits + Com_select) * 100%

若命中率在50-70%的範圍之內,則表明Query Cache的快取效率較高。如果命中率明顯小於50%,那麼建議禁用(將query_cache_type設定為0(OFF))或按需使用(將query_cache_type設定為2(DEMAND))Query Cache,節省的記憶體可以用作InnoDB的緩衝池。

6.7 如何判斷Query Cache是空閒記憶體不足,還是記憶體碎片太多?如果Qcache_lowmem_prunes值比較大,表示Query Cache的記憶體空間大小設定太小,需要增大。

如果Qcache_free_blocks值比較大,表示記憶體碎片較多,需要使用FLUSH QUERY CACHE語句清理記憶體碎片。

6.8 系統變數query_cache_min_res_unit應當設定為多大?query_cache_min_res_unit的計算公式如下所示:

query_cache_min_res_unit = (query_cache_size – Qcache_free_memory) / Qcache_queries_in_cache

其中,一般不建議將Query Cache的大小(也就是query_cache_size系統變數)設定超過256MB。

七、MySQL Query Cache優缺點

7.1. 優點Query Cache的查詢,發生在MySQL接收到客戶端的查詢請求、查詢許可權驗證之後和查詢SQL解析之前。也就是說,當MySQL接收到客戶端的查詢SQL之後,僅僅只需要對其進行相應的許可權驗證之後,就會通過Query Cache來查詢結果,甚至都不需要經過Optimizer模組進行執行計劃的分析優化,更不需要發生任何儲存引擎的互動。由於Query Cache是基於記憶體的,直接從記憶體中返回相應的查詢結果,因此減少了大量的磁碟I/O和CPU計算,導致效率非常高。

7.2. 缺點Query Cache的優點很明顯,但是也不能忽略它所帶來的一些缺點:

  • 查詢語句的hash計算和hash查詢帶來的資源消耗。如果將query_cache_type設定為1(也就是ON),那麼MySQL會對每條接收到的SELECT型別的查詢進行hash計算,然後查詢這個查詢的快取結果是否存在。雖然hash計算和查詢的效率已經足夠高了,一條查詢語句所帶來的開銷可以忽略,但一旦涉及到高併發,有成千上萬條查詢語句時,hash計算和查詢所帶來的開銷就必須重視了。
  • Query Cache的失效問題。如果表的變更比較頻繁,則會造成Query Cache的失效率非常高。表的變更不僅僅指表中的資料發生變化,還包括表結構或者索引的任何變化。
  • 查詢語句不同,但查詢結果相同的查詢都會被快取,這樣便會造成記憶體資源的過度消耗。查詢語句的字元大小寫、空格或者註釋的不同,Query Cache都會認為是不同的查詢(因為他們的hash值會不同)。
  • 相關係統變數設定不合理會造成大量的記憶體碎片,這樣便會導致Query Cache頻繁清理記憶體。

八、 生產如何設定MySQL Query Cache

query_cache_no_beatiful.png

MySQL中的Query Cache是一個適用較少情況的快取機制。如上圖所示,如果快取命中率非常高的話,有測試表明在極端情況下可以提高效率238%。但實際情況如何?Query Cache有如下規則,如果資料表被更改,那麼和這個資料表相關的全部Cache全部都會無效,並刪除之。這裡“資料表更改”包括: INSERT, UPDATE, DELETE, TRUNCATE, ALTER TABLE, DROP TABLE, or DROP DATABASE等。舉個例子,如果資料表posts訪問頻繁,那麼意味著它的很多資料會被QC快取起來,但是每一次posts資料表的更新,無論更新是不是影響到了cache的資料,都會將全部和posts表相關的cache清除。如果你的資料表更新頻繁的話,那麼Query Cache將會成為系統的負擔。有實驗表明,糟糕時,QC會降低系統13%的處理能力。

如果你的應用對資料庫的更新很少,那麼QC將會作用顯著。比較典型的如部落格系統,一般部落格更新相對較慢,資料表相對穩定不變,這時候QC的作用會比較明顯。

但是一個更新頻繁的BBS系統。下面是一個實際執行的論壇資料庫的狀態引數:QCache_hit 5280438QCache_insert 8008948Qcache_not_cache 95372Com select 8104159可以看到,資料庫一共往Query Cache中寫入了約800W次快取,但是實際命中的只有約500W次。也就是說,每一個快取的使用率約為0.66次。很難說,該快取的作用是否大於Query Cache系統所帶來的開銷。但是有一點是很肯定的,Query Cache快取的作用是很微小的,如果應用層能夠實現快取,將可以忽略Query Cache的效果。

所以,如果經常有更新的系統,想要獲得較高tps的話,建議一開始就關閉Query Cache

九、 查詢快取的替代方案MySQL查詢快取工作的原則是:執行查詢最快的方式就是不去執行,但是查詢仍然需要傳送到伺服器端,伺服器也還需要做一點點工作,如果對於某些查詢完全不需要與伺服器通訊效果會如何呢,這時客戶端快取可以很大程度上分擔MySQL伺服器的壓力。

參考檔案

啟用MySQL查詢快取MySQL查詢快取設定提高MySQL查詢效能MySQL快取之Qcache與buffer pool對比Mysql快取技術線上環境到底要不要開啟query cache《高效能MySQL》讀書筆記--查詢快取

更多內容請關注公眾號



相關文章