1. 程式人生 > >mysql實戰優化之九:MySQL查詢快取總結

mysql實戰優化之九:MySQL查詢快取總結

mysql Query Cache 預設為開啟。從某種程度可以提高查詢的效果,但是未必是最優的解決方案,如果有的大量的修改和查詢時,由於修改造成的cache失效,會給伺服器造成很大的開銷。

mysql Query Cache 和 Oracle Query Cache 是不同的, oracle Query Cache 是快取執行計劃的,而MySql Query Cache 不快取執行計劃而是整個結果集。快取整個結果集的好處不言而喻,但由於快取的是結果集因此Query必須是完全一樣的,這樣帶來的後果就是平均 Hit Rate 命中率一般不會太高。 Query Cache 對於一些小型應用程式或者資料表的資料量不大的情況下效果是最為明顯的。

一、mysql Query Cache管理

1.1、query cache開關

可以通過query_cache_type來控制快取的開關, query_cache_type的狀態值有如下幾種:

  • 0(OFF):代表不使用緩衝;
  • 1(ON):代表使用緩衝;
  • 2(DEMAND):代表根據需要使用;
show variables like '%query_cache%';

1.2、query_cache_size 

    預設情況下query_cache_size為0,表示為查詢快取預留的記憶體為0,則無法使用查詢快取。所以我們需要設定query_cache_size的值:  SET GLOBAL query_cache_size = 134217728;     注意上面的值如果設得太小不會生效。比如我用下面的SQL設定query_cache_size大小:  SET GLOBAL query_cache_size = 4000;

預設情況下query_cache_size為0,表示為查詢快取預留的記憶體為0,則無法使用查詢快取。這個值必須是1024的整數倍。否則,mysql實際分配的資料會和你指定的不同。
所以我們需要設定query_cache_size的值:
SET GLOBAL query_cache_size = 134217728;
注意上面的值如果設得太小不會生效。比如我用下面的SQL設定query_cache_size大小:
SET GLOBAL query_cache_size = 4000;

query_cache_limit
mysql能夠快取的最大查詢結果。如果查詢結果大於這個值,則不會被快取。預設為1M。因為查詢快取在資料生成的時候就開始嘗試快取資料,所以只有當結果全部返回後,mysql才知道查詢結果是否超出限制。
如果超出,mysql則增加狀態值Qcache_not_cached,並將結果從查詢快取中刪除。
如果你事先知道有很多這樣的情況發生,那麼建議在查詢語句中加入SQL_NO_CACHE來避免查詢快取帶來的額外消耗。

query_cache_wlock_invalidate
如果某個資料表被其他的連線鎖住,是否還要從查詢快取中返回結果。這個引數預設是OFF,這可能在一定程度上回改變伺服器的行為,因為這使得資料庫可能返回其他執行緒鎖住的資料。
如果設定為NO,則不會從快取中讀資料,但是這可能會增加鎖等待。
query_cache_min_res_unit
是在4.1版本以後引入的,它指定分配緩衝區空間的最小單位,預設為4K。檢查狀態值Qcache_free_blocks,如果該值非常大,則表明緩衝區中碎片很多,這就表明查詢結果都比較小,此時需要減小 query_cache_min_res_unit。

1.3、SHOW WARNINGS;

    會返回下面的結果: 

二、mysql query cache規則

2.1、快取條件(規則)

需要注意的是mysql query cache 是對大小寫敏感的,因為Query Cache 在記憶體中是以 HASH 結構來進行對映,HASH 演算法基礎就是組成 SQL 語句的字元,所以 任何sql語句的改變重新cache,這也是專案開發中要建立sql語句書寫規範的原因吧。

a) mysql query cache內容為 select 的結果集, cache 使用完整的 sql 字串做 key, 並區分大小寫,空格等。即兩個sql必須完全一致才會導致cache命中。
b) prepared statement永遠不會cache到結果,即使引數完全一樣。據說在 5.1 之後會得到改善。
c) where條件中如包含了某些函式永遠不會被cache, 比如current_date, now等。
d) date 之類的函式如果返回是以小時或天級別的,最好先算出來再傳進去。

select * from foo where date1=current_date -- 不會被 cache
select * from foo where date1='2008-12-30' -- 被cache, 正確的做法

e) 太大的result set不會被cache (< query_cache_limit)

2.2、 快取資料何時失效(invalidate)

在表的結構或資料發生改變時,查詢快取中的資料不再有效。有這些INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE會導致快取資料失效。所以查詢快取適合有大量相同查詢的應用,不適合有大量資料更新的應用。

a) 一旦表資料進行任何一行的修改,基於該表相關cache立即全部失效。
b) 為什麼不做聰明一點判斷修改的是否cache的內容?因為分析cache內容太複雜,伺服器需要追求最大的效能。

可以使用下面三個SQL來清理查詢快取:  1、FLUSH QUERY CACHE; // 清理查詢快取記憶體碎片。 2、RESET QUERY CACHE; // 從查詢快取中移出所有查詢。 3、FLUSH TABLES; //關閉所有開啟的表,同時該操作將會清空查詢快取中的內容。

2.3、效能

a) cache 未必所有場合總是會改善效能
當有大量的查詢和大量的修改時,cache機制可能會造成效能下降。因為每次修改會導致系統去做cache失效操作,造成不小開銷。
另外系統cache的訪問由一個單一的全域性鎖來控制,這時候大量>的查詢將被阻塞,直至鎖釋放。所以不要簡單認為設定cache必定會帶來效能提升。
b) 大result set不會被cache的開銷
太大的result set不會被cache, 但mysql預先不知道result set的長度,所以只能等到reset set在cache新增到臨界值 query_cache_limit 之後才會簡單的把這個cache 丟棄。這並不是一個高效的操作。如果mysql status中Qcache_not_cached太大的話, 則可對潛在的大結果集的sql顯式新增 SQL_NO_CACHE 的控制。
query_cache_min_res_unit = (query_cache_size – Qcache_free_memory) / Qcache_queries_in_cache

2.4、記憶體池使用

mysql query cache 使用記憶體池技術,自己管理記憶體釋放和分配,而不是通過作業系統。記憶體池使用的基本單位是變長的block, 一個result set的cache通過連結串列把這些block串起來。因為存放result set的時候並不知道這個resultset最終有多大。block最短長度為 query_cache_min_res_unit, resultset 的最後一個block會執行trim操作。


Query Cache 在提高資料庫效能方面具有非常重要的作用。

其設定也非常簡單,僅需要在配置檔案寫入兩行: query_cache_type 和 query_cache _size,而且 MySQL 的 query cache 非常快!而且一旦命中,就直接傳送給客戶端,節約大量的 CPU 時間。

當然,非 SELECT 語句對緩衝是有影響的,它們可能使緩衝中的資料過期。一個 UPDATE 語句引起的部分表修改,將導致對該表所有的緩衝資料失效,這是 MySQL 為了平衡效能而沒有采取的措施。因為,如果每次 UPDATE 需要檢查修改的資料,然後撤出部分緩衝將導致程式碼的複雜度增加。

三、示例說明

3.1、如果query_cache_type為1而又不想利用查詢快取中的資料

可以用下面的SQL: 

SELECT SQL_NO_CACHE * FROM my_table WHERE condition;

 3.2、如果值為2,但想要使用快取

需要使用SQL_CACHE開關引數:
SELECT SQL_CACHE * FROM my_table WHERE condition;


用 SHOW STATUS 可以檢視緩衝的情況:

mysql> show status like 'Qca%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_queries_in_cache | 8 |
| Qcache_inserts | 545875 |
| Qcache_hits | 83951 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 2343256 |
| Qcache_free_memory | 33508248 |
| Qcache_free_blocks | 1 |
| Qcache_total_blocks | 18 |
+-------------------------+----------+
8 rows in set (0.00 sec)

如果需要計算命中率,需要知道伺服器執行了多少 SELECT 語句:

mysql> show status like 'Com_sel%';
+---------------+---------+
| Variable_name | Value |
+---------------+---------+
| Com_select | 2889628 |
+---------------+---------+
1 row in set (0.01 sec)

在本例中, MySQL 命中了 2,889,628 條查詢中的 83,951 條,而且 INSERT 語句只有 545,875 條。因此,它們兩者的和和280萬的總查詢相比有很大差距,因此,我們知道本例使用的緩衝型別是 2 。

而在型別是 1 的例子中, Qcache_hits 的數值會遠遠大於 Com_select。

SHOW STATUS LIKE 'Qcache_hits';     另外即使完全相同的SQL,如果使用不同的字符集、不同的協議等也會被認為是不同的查詢而分別進行快取。