ProxySQL官檔翻譯__18_Query_Cache

18_Query_Cache

備註:文章編寫時間201904-201905期間,後續官方在github的更新沒有被寫入

~
~
查詢緩存[Query Cache]

一、前言[Preface]

從歷史上看,在MySQL環境中有兩種使用緩存的方法:
1)啓用MySQL的查詢緩存:嵌入在MySQL服務器本身,沒有外部依賴。但它會成爲任何寫入密集型環境的瓶頸,因爲當相關表收到寫入時,緩存條目無效。
2)使用外部緩存:允許很多靈活性,但也需要應用程序做出大量的邏輯更改,因爲應用程序必須連接到數據庫和緩存系統,並負責保持更新。
雖然外部緩存非常有效,但它需要開發一定量的投入,而DBA無法控制數據流。

二、在線上緩存[Caching on the wire]

ProxySQL引入了一種新的範例來查詢緩存。根據相關配置(下面會做詳細說明),當執行查詢後結果集會被緩存在線上,並將結果集返回給應用程序。
如果應用程序將重新執行相同的查詢,則結果集將由內部的查詢緩存來返回。

識別由非最佳SELECT語句引起的數據庫負載是一種非常常見的情況,並應對這些語句的結果集進行數秒的緩存。然而,實現應用程序的代碼更改可能是一個漫長的過程(開發人員應編寫新代碼,構建代碼,測試分段,然後在生產中進行部署),這在緊急情況下通常不是合適的選擇。由於數據庫代理層(在本例中爲ProxySQL)的配置是屬於DBA的責任,因此DBA啓用緩存是不需要開發人員對應用程序進行更改。

因此,這是一個賦予DBA權力的功能。

三、定義需要緩存的流量[Define traffic that needs to be cached]

如果想要對指定的(查詢)流量進行結構集緩存,我們需要在定義匹配傳入流量的查詢規則時爲其定義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;如果不設置,則爲每次執行都新建連接。

結果是:

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的查詢表示直接從查詢緩存中獲取結果集的流量,而不會命中任何後端。

四、度量值[Metrics]

目前可用的一些指標是在 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 ==>查詢緩存中當前的條目數。

五、查詢緩存調整[Query Cache tuning]

目前,只能使用變量 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可以緩衝的最大結果集大小,所以它也就定義了可以存儲在查詢緩存中的最大結果集大小。

六、實施細節[Implementation details]

查詢緩存中的每個元素都有幾個與之關聯的元數據:
1)key ==>該值唯一標識查詢緩存記錄:它是從username、schemaname和查詢本身派生的一個哈希。通過這些,它確保了用戶只訪問他們當前所在schema的結果集;
2)value ==>結果集(內容);
3)length ==>結果集的長度;
4)expire_ms ==>定義該結果集的到期時間;
5)access_ms ==>記錄上次訪問該記錄的時間;
6)ref_count ==>用於標識當前正在使用的結果集的引用計數。

1、GET calls

每次GET調用成功時,爲了提高性能,會在增加引用指針並釋放所有鎖之後,執行數據拷貝。當複製完成時,ref_count值便會減少。該策略可以確保在該結
果集仍在使用狀態時不會將其從查詢緩存中刪除條目(即使在次過程中它到期了)。當GET調用找到過期的條目時,該條目將被移動到清除隊列。

2、SET calls

SET調用永遠不會失敗!!
如果到達 mysql-query_cache_size_MB 指定值,則SET調用不會失敗。如果此時發現存在與SET操作的結果集具有相同key的條目,則將已有的條目移動到清除隊列中。

七、清除線程[Purging thread]

由Purging線程執行對查詢緩存中條目的清除工作。這確保了查詢緩存的任何維護都不是由訪問它的MySQL線程執行的,而是由後臺線程執行,從而提高了性能。
這就是爲什麼即使達到 mysql-query_cache_size_MB,SET調用也永遠不會失敗的原因:訪問查詢緩存的MySQL線程是不負責釋放空間的;而是由Purging線程來處理。

Purging線程不僅會負責清除'清除隊列'中的條目。它還負責掃描整個查詢緩存以查找過期的條目。作爲優化,如果當前內存使用率小於 mysql-query_cache_size_MB 的3%,則 Purging 線程不執行任何清除。

八、限制[Limitations]

查詢緩存中目前存在多個已知限制。
有些很容易實現,有些則很難。
它們是沒有定義優先級的:將根據用戶請求定義優先級。

目前已知的限制:
1)除了使用cache_ttl之外,無法使用其他所在來定義查詢緩存(條目)的失效;
2)沒有提供可以立即清除查詢緩存中全部內容的命令;
3)mysql-query_cache_size_MB 不是一個嚴格的容量限制,它只是使用指標來觸發自動清除過期的條目;
4)雖然記錄了 access_ms 值,但是當實現 mysql-query_cache_size_MB 時,它不用作使判定到期的度量值;
5)查詢緩存不支持MySQL的 PREPARE (預準備)語句。

完畢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章