備註:文章編寫時間201904-201905期間,後續官方在github的更新沒有被寫入mysql
~
~
查詢緩存[Query Cache]git
從歷史上看,在MySQL環境中有兩種使用緩存的方法:
1)啓用MySQL的查詢緩存:嵌入在MySQL服務器自己,沒有外部依賴。但它會成爲任何寫入密集型環境的瓶頸,由於當相關表收到寫入時,緩存條目無效。
2)使用外部緩存:容許不少靈活性,但也須要應用程序作出大量的邏輯更改,由於應用程序必須鏈接到數據庫和緩存系統,並負責保持更新。
雖然外部緩存很是有效,但它須要開發必定量的投入,而DBA沒法控制數據流。github
ProxySQL引入了一種新的範例來查詢緩存。根據相關配置(下面會作詳細說明),當執行查詢後結果集會被緩存在線上,並將結果集返回給應用程序。
若是應用程序將從新執行相同的查詢,則結果集將由內部的查詢緩存來返回。sql
識別由非最佳SELECT語句引發的數據庫負載是一種很是常見的狀況,並應對這些語句的結果集進行數秒的緩存。然而,實現應用程序的代碼更改多是一個漫長的過程(開發人員應編寫新代碼,構建代碼,測試分段,而後在生產中進行部署),這在緊急狀況下一般不是合適的選擇。因爲數據庫代理層(在本例中爲ProxySQL)的配置是屬於DBA的責任,所以DBA啓用緩存是不須要開發人員對應用程序進行更改。數據庫
所以,這是一個賦予DBA權力的功能。後端
若是想要對指定的(查詢)流量進行結構集緩存,咱們須要在定義匹配傳入流量的查詢規則時爲其定義cache_ttl值。
如文檔(《09_ProxySQL配置之系統庫_01_main庫MEMORY層表和RUNTIME層表.txt》)中所述,有許多方法能夠定義對傳入流量的匹配。咱們緩存結果集所須要作的就是定義匹配條件和超時。緩存
緩存示例
說明如何配置緩存的最佳方法是使用示例。
假設咱們使用一個很是小的表對 ProxySQL 運行sysbench(1.0.14):服務器
$ sysbench oltp_common --mysql_storage_engine=innodb --db-driver=mysql --mysql-host=188.188.0.71 --mysql-port=6033 \ --mysql-user='msandbox' --mysql-password='123456' --mysql-db=sbtest --threads=4 --table_size=10000 --tables=1 prepare $ sysbench oltp_read_only --mysql_storage_engine=innodb --db-driver=mysql --mysql-host=188.188.0.71 --mysql-port=6033 \ --mysql-user='msandbox' --mysql-password='123456' --mysql-db=sbtest --threads=4 --table_size=10000 --tables=1 \ --skip_trx=on --point_selects=100 --simple_ranges=1 --sum_ranges=1 --order_ranges=1 --distinct_ranges=1 \ --time=120 --histogram --report-interval=10 --db-ps-mode=disable run
備註: 若是設置了--db-ps-mode=disable,則效果爲每一個線程始終在一個Session中執行全部SQL;若是不設置,則爲每次執行都新建鏈接。app
結果是:ide
SQL statistics: queries performed: read: 1285648 write: 0 other: 0 total: 1285648 transactions: 12362 (102.99 per sec.) queries: 1285648 (10710.93 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.)
在ProxySQL中咱們能夠看到如下結果:
Admin> SELECT count_star,sum_time,hostgroup,digest,digest_text FROM stats_mysql_query_digest_reset ORDER BY sum_time DESC; +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ | count_star | sum_time | hostgroup | digest | digest_text | +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ | 1236200 | 394819753 | 10 | 0xBF001A0C13781C1D | SELECT c FROM sbtest1 WHERE id=? | | 12362 | 13846586 | 10 | 0xC19480748AE79B4B | SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c | | 12362 | 7419887 | 10 | 0xAC80A5EA0101522E | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c | | 12362 | 5256720 | 10 | 0xDBF868B2AA296BC5 | SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ? | | 12362 | 5232686 | 10 | 0x290B92FD743826DA | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? | +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ 5 rows in set (0.00 sec)
毫無疑問,大多數執行時間來自單一類型的SELECT,執行不少次。讓咱們緩存它,爲它們建立匹配規則。在此示例中,咱們將使用摘要(digest)做
爲匹配條件,並使用2000ms的cache_ttl :
Admin> INSERT INTO mysql_query_rules (rule_id,active,digest,cache_ttl,apply) VALUES (5,1,'0xBF001A0C13781C1D',2000,1); Query OK, 1 row affected (0.00 sec) Admin> LOAD MYSQL QUERY RULES TO RUNTIME; Query OK, 0 rows affected (0.00 sec) Admin> SAVE MYSQL QUERY RULES TO DISK; Query OK, 0 rows affected (0.01 sec)
讓咱們從新運行測試基準:
$ sysbench oltp_read_only --mysql_storage_engine=innodb --db-driver=mysql --mysql-host=188.188.0.71 --mysql-port=6033 \ --mysql-user='msandbox' --mysql-password='123456' --mysql-db=sbtest --threads=4 --table_size=10000 --tables=1 \ --skip_trx=on --point_selects=100 --simple_ranges=1 --sum_ranges=1 --order_ranges=1 --distinct_ranges=1 \ --time=120 --histogram --report-interval=10 --db-ps-mode=disable run
本次結果爲:
SQL statistics: queries performed: read: 4655664 write: 0 other: 0 total: 4655664 transactions: 44766 (373.01 per sec.) queries: 4655664 (38793.21 per sec.) ignored errors: 0 (0.00 per sec.) reconnects: 0 (0.00 per sec.)
咱們能夠當即看到,吞吐量大幅增長:從1285648 (10710.93 per sec.)增長到了4655664 (38793.21 per sec.);由於一些查詢是由ProxySQL緩存的。
在ProxySQL中,咱們能夠看到stats_mysql_query_digest的如下結果:
Admin> SELECT count_star,sum_time,hostgroup,digest,digest_text FROM stats_mysql_query_digest_reset ORDER BY sum_time DESC; +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ | count_star | sum_time | hostgroup | digest | digest_text | +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ | 282956 | 164908854 | 10 | 0xBF001A0C13781C1D | SELECT c FROM sbtest1 WHERE id=? | | 44766 | 48693121 | 10 | 0xC19480748AE79B4B | SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c | | 44766 | 27466809 | 10 | 0xDBF868B2AA296BC5 | SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ? | | 44766 | 27065916 | 10 | 0xAC80A5EA0101522E | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c | | 44766 | 21306870 | 10 | 0x290B92FD743826DA | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? | | 4193644 | 0 | -1 | 0xBF001A0C13781C1D | SELECT c FROM sbtest1 WHERE id=? | +------------+-----------+-----------+--------------------+--------------------------------------------------------------------+ 6 rows in set (0.00 sec)
注意:hostgroup = -1的查詢表示直接從查詢緩存中獲取結果集的流量,而不會命中任何後端。
目前可用的一些指標是在 stats_mysql_query_digest 中使用 hostgroup = -1 報告的指標,如上面的例子中所示的那樣。
備註:上面使用的是 stats_mysql_query_digest_reset 表,它是stats_mysql_query_digest的清除模式表,它會清空表中內容。
經過stats表 stats_mysql_global 能夠得到與查詢緩存相關的其餘指標:
Admin> SELECT * FROM stats_mysql_global WHERE Variable_Name LIKE 'Query_Cache%'; +--------------------------+----------------+ | Variable_Name | Variable_Value | +--------------------------+----------------+ | Query_Cache_Memory_bytes | 6905080 | | Query_Cache_count_GET | 16034509 | | Query_Cache_count_GET_OK | 15035431 | | Query_Cache_count_SET | 999069 | | Query_Cache_bytes_IN | 194818455 | | Query_Cache_bytes_OUT | 2931909045 | | Query_Cache_Purged | 997109 | | Query_Cache_Entries | 1960 | +--------------------------+----------------+ 8 rows in set (0.00 sec)
它們表明的含義以下:
Query_Cache_Memory_bytes ==>存儲在查詢緩存中的結果集的總大小(單位:byte)。這不包括元數據;
Query_Cache_count_GET ==>針對查詢緩存執行的GET請求總數;
Query_Cache_count_GET_OK ==>針對查詢緩存執行成功的GET請求的總數,其中Query Cache中的結果集存在且未過時;
Query_Cache_count_SET ==>插入查詢緩存的結果集總數;
Query_Cache_bytes_IN ==>寫入查詢緩存的數據量(單位:byte);
Query_Cache_bytes_OUT ==>從查詢緩存中讀取的數據量(單位:byte);
Query_Cache_Purged ==>清除的條目數量;
Query_Cache_Entries ==>查詢緩存中當前的條目數。
目前,只能使用變量 mysql-query_cache_size_MB 來調整查詢緩存使用的內存總量:
Admin> SHOW VARIABLES LIKE 'mysql-query_cache_size_MB'; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | mysql-query_cache_size_MB | 256 | +---------------------------+-------+ 1 row in set (0.00 sec)
重要提示:mysql-query_cache_size_MB 的當前實現並未強加硬限制。而是將它做爲觸發 purging 線程的參數。
要更改查詢緩存使用的內存總量,可使用以下命令:
Admin> SET mysql-query_cache_size_MB=128; Query OK, 1 row affected (0.00 sec) Admin> SHOW VARIABLES LIKE 'mysql-query_cache_size_MB'; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | mysql-query_cache_size_MB | 128 | +---------------------------+-------+ 1 row in set (0.00 sec) Admin> LOAD MYSQL VARIABLES TO RUNTIME; Query OK, 0 rows affected (0.00 sec)
與查詢緩存非緊密相關但影響其行爲的另外一個變量是 mysql-threshold_resultset_size。
mysql-threshold_resultset_size ==>它定義了ProxySQL在開始將結果集發送到客戶端以前能緩衝的最大大小。
將此變量設置得過低將致使在從後端檢索結果集時執行重試查詢的失敗。
將此變量設置得過高可能會增長內存佔用,由於ProxySQL將嘗試緩衝更多數據。
因爲 mysql-threshold_resultset_size 定義了ProxySQL能夠緩衝的最大結果集大小,因此它也就定義了能夠存儲在查詢緩存中的最大結果集大小。
查詢緩存中的每一個元素都有幾個與之關聯的元數據:
1)key ==>該值惟一標識查詢緩存記錄:它是從username、schemaname和查詢自己派生的一個哈希。經過這些,它確保了用戶只訪問他們當前所在schema的結果集;
2)value ==>結果集(內容);
3)length ==>結果集的長度;
4)expire_ms ==>定義該結果集的到期時間;
5)access_ms ==>記錄上次訪問該記錄的時間;
6)ref_count ==>用於標識當前正在使用的結果集的引用計數。
每次GET調用成功時,爲了提升性能,會在增長引用指針並釋放全部鎖以後,執行數據拷貝。當複製完成時,ref_count值便會減小。該策略能夠確保在該結
果集仍在使用狀態時不會將其從查詢緩存中刪除條目(即便在次過程當中它到期了)。當GET調用找到過時的條目時,該條目將被移動到清除隊列。
SET調用永遠不會失敗!!
若是到達 mysql-query_cache_size_MB 指定值,則SET調用不會失敗。若是此時發現存在與SET操做的結果集具備相同key的條目,則將已有的條目移動到清除隊列中。
由Purging線程執行對查詢緩存中條目的清除工做。這確保了查詢緩存的任何維護都不是由訪問它的MySQL線程執行的,而是由後臺線程執行,從而提升了性能。
這就是爲何即便達到 mysql-query_cache_size_MB,SET調用也永遠不會失敗的緣由:訪問查詢緩存的MySQL線程是不負責釋放空間的;而是由Purging線程來處理。
Purging線程不只會負責清除'清除隊列'中的條目。它還負責掃描整個查詢緩存以查找過時的條目。做爲優化,若是當前內存使用率小於 mysql-query_cache_size_MB 的3%,則 Purging 線程不執行任何清除。
查詢緩存中目前存在多個已知限制。
有些很容易實現,有些則很難。
它們是沒有定義優先級的:將根據用戶請求定義優先級。
目前已知的限制:
1)除了使用cache_ttl以外,沒法使用其餘所在來定義查詢緩存(條目)的失效;
2)沒有提供能夠當即清除查詢緩存中所有內容的命令;
3)mysql-query_cache_size_MB 不是一個嚴格的容量限制,它只是使用指標來觸發自動清除過時的條目;
4)雖然記錄了 access_ms 值,可是當實現 mysql-query_cache_size_MB 時,它不用做使斷定到期的度量值;
5)查詢緩存不支持MySQL的 PREPARE (預準備)語句。
完畢!