返回 ProxySQL 系列文章: http://www.cnblogs.com/f-ck-need-u/p/7586194.html
ProxySQL 支持查询缓存的功能, 可以将后端返回的结果集缓存在自己的内存中, 在某查询的缓存条目被清理 (例如过期) 之前, 前端再发起同样的查询语句, 将直接从缓存中取数据并返回给前端. 如此一来, ProxySQL 处理的性能会大幅提升, 也会大幅减轻后端 MySQL Server 的压力.
1. 开启 query cache 功能
ProxySQL 的查询缓存功能由 mysql_query_rules 表中的 cache_ttl 字段控制, 该字段设置每个规则对应的缓存时长, 时间单位为毫秒.
当前端发送的 SQL 语句命中了某规则后 (严格地说, 是最后应用的那条规则, 因为链式规则下会操作多个规则), 如果这个规则同时还设置了 "cache_ttl" 字段的值, 则这个 SQL 语句返回的结果将会被缓存一定时间, 过期后将等待专门的线程(purge 线程) 来清理.
例如:
- delete from mysql_query_rules;
- select * from stats_mysql_query_digest_reset where 1=0;
- insert into mysql_query_rules(rule_id,active,apply,destination_hostgroup,match_pattern,cache_ttl)
- values(1,1,1,10,"^select .* test1.t1",20000);
- load mysql query rules to runtime;
- save mysql query rules to disk;
- select rule_id,destination_hostgroup,match_pattern,cache_ttl from mysql_query_rules;
这表示匹配上述规则 (查询 test1.t1 表) 的查询结果集将在 ProxySQL 上缓存 20 秒.
可以执行下面的语句进行测试, 每个语句循环执行 10 次:
- # 在 bash 下执行
- proc="mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e"
- for ((i=0;i<10;i++));do
- $proc "select * from test1.t1;"
- $proc "select * from test1.t2;"
- $proc "select * from test2.t1;"
- $proc "select * from test2.t2;"
- done
再去查看规则统计表:
- Admin> select * from stats_mysql_query_rules;
- +---------+------+
- | rule_id | hits |
- +---------+------+
| 1 | 10 | <--rule_id=1 的规则命中 10 次
- +---------+------+
- Admin> select hostgroup,count_star,sum_time,digest_text from stats_mysql_query_digest;
- +-----------+------------+----------+------------------------+
- | hostgroup | count_star | sum_time | digest_text |
- +-----------+------------+----------+------------------------+
| -1 | 9 | 0 | select * from test1.t1 | <-- 9 次, hg="-1"
- | 10 | 10 | 3640 | select * from test2.t1 |
- | 10 | 10 | 3686 | select * from test2.t2 |
- | 10 | 10 | 3986 | select * from test1.t2 |
| 10 | 1 | 1236 | select * from test1.t1 | <-- 1 次, hg="10"
+-----------+------------+----------+------------------------+
其中, hostgroup=-1 表示从缓存中取数据.
从上面的结果中, 可以看出
select * from test1.t1;
语句除了被正常路由到后端执行的一次外, 其它 9 次都是直接从缓存中获取数据的, 且缓存取数据的语句执行总时间 sum_time=0.
并不是所有的查询结果都应该缓存, ProxySQL 的缓存空间毕竟有限. 所以很有必要去
stats_mysql_query_digest
表中找出哪些语句应该缓存: 执行次数多, 总执行时间长, 平均执行时间长的语句都应该缓存. 这些指标也是合理判断读, 写分离的指标.
2. 查询缓存相关的统计数据
ProxySQL 虽然实现了查询缓存功能, 但是查询缓存设计的还不够完整, 缺少一些锦上添花的功能, 例如能查询到的缓存类指标比较少, 控制缓存的方式也比较缺乏. 不过作者一直在努力改进, 不断完善.
以下是和查询缓存有关的状态变量:
- Admin> SELECT * FROM stats_mysql_global WHERE Variable_Name LIKE '%Cache%';
- +--------------------------+----------------+
- | Variable_Name | Variable_Value |
- +--------------------------+----------------+
- | Stmt_Cached | 0 |
- | Query_Cache_Memory_bytes | 0 |
- | Query_Cache_count_GET | 0 |
- | Query_Cache_count_GET_OK | 0 |
- | Query_Cache_count_SET | 0 |
- | Query_Cache_bytes_IN | 0 |
- | Query_Cache_bytes_OUT | 0 |
- | Query_Cache_Purged | 0 |
- | Query_Cache_Entries | 0 |
- +--------------------------+----------------+
各变量的意义如下:
Query_Cache_Memory_bytes: 查询结果集已成功缓存在内存中的总大小, 不包含那些元数据;
Query_Cache_count_GET: 从查询缓存中取数据的请求总次数(GET requests);
Query_Cache_count_GET_OK: 成功从缓存中 GET 到缓存的请求总次数(即命中缓存且缓存未过期);
Query_Cache_count_SET: 缓存到查询缓存中的结果集总数(即有多少个查询的结果集进行了缓存);
Query_Cache_bytes_IN: 写入到查询缓存的总数据量;
Query_Cache_bytes_OUT: 从查询缓存中取出的总数据量;
Query_Cache_Purged: 从缓存中移除 (purged) 的缓存结果集 (缓存记录) 数量;
Query_Cache_Entries: 当前查询缓存中还有多少个缓存记录.
无法查询当前缓存空间中的具体的缓存记录信息.
3.ProxySQL 查询缓存的细节
如下图:
ProxySQL 通过变量
mysql_query_cache_size_MB
控制为查询缓存开辟多大的空间, 通过变量
mysql-threshold_resultset_size
定义 ProxySQL 能缓存的单个最大结果集大小.
- Admin> show variables like '%size%';
- +--------------------------------+-----------+
- | Variable_name | Value |
- +--------------------------------+-----------+
- | mysql-eventslog_filesize | 104857600 |
- | mysql-threshold_resultset_size | 4194304 | <<<<
- | mysql-query_cache_size_MB | 256 | <<<<
- | mysql-stacksize | 1048576 |
- +--------------------------------+-----------+
ProxySQL 通过 SET 调用将后端返回的结果集放进查询缓存空间, 通过 GET 调用从查询缓存空间取出缓存数据返回给前端. 当 GET 调用发现缓存记录已过期, 会将其放进 purge 队列中.
purge 线程会定时清理 purge 队列中的过期记录, 还会定时扫描缓存空间以找出过期记录. 当查询缓存空间已经满了(
mysql-query_cache_size_MB
), 下一次 SET 调用也会触发 purge 线程来清理已经过期的缓存记录. 所以, SET 调用永远不会失败. 但需要注意的是, 如果已缓存内容占用的缓存空间低于 3%, 则 purge 线程不会清理任何内容, 即使它们已经过期了.
缓存记录的数据结构如下:
key:value:length:expire_ms:access_ms:ref_count
其中:
key/value 分别是缓存的 id 和实际缓存的数据, key 是根据
username + schemaname + SQL statement
做 hash 运算得到的值, 这可以保证用户只会访问到它自己的, 指定 schema 的结果集.
length 用于记录缓存记录的长度. 因为
mysql-threshold_resultset_size
变量的原因, 超出该变量阈值的那部分额外结果集不会放进缓存.
expire_ms: 用来记录每个缓存记录还有多长时间过期.
access_ms: 记录每个缓存记录最近一次被访问离现在已多久.
ref_count: 记录各缓存记录当前被引用数量. 每次 GET 调用某缓存记录, 该缓存记录的 ref_count 都会加 1, 调用完成后减 1. 这是为了避免正在调用的缓存记录正好过期又正好被 purge 线程发现而被清理.
所以, 后面的 3 个字段都是为了让缓存记录过期而设计的. 不过, 目前 ProxySQL 还不支持根据 access_ms 来判断是否清理, 例如缓存空间已满, 且 access_ms 的时间已过去很久, 但只要未过期, 就不会被清理. 这个字段可能是为以后的 LRU(或其它类似缓存清理算法)做准备的.
4. 容易误解的查询缓存
每个查询缓存记录的 key 是根据
username + schemaname +SQL
做 hash 运算出来的, 这里的 SQL 是完整的包含参数 SQL 语句, 而非参数化后的语句, 如果 SQL 语句进行了重写, 则使用重写后的完整的 SQL 语句参与 hash 运算. 这很重要.
例如:
- select * from tab where id between 10 and 20;
- select * from tab where id between 10 and 19;
- select * from tab where id=15;
- select * from tab where id between 10 and 19;
第一个语句会缓存 id=10 到 id=20 之间的结果集, 但第二个语句和第三个语句不会从这个缓存记录中取数据, 而是从后端查询后设置自己的缓存记录, 即使第二个语句参数化后的语句和第一个完全一样(它们是同一类语句, 如下代码所示), 所需要的数据也已经被第一个语句缓存. 第四个语句会命中第二个语句的缓存.
select * from tab where id between ? and ?
但因为第二个语句和第一个语句命中的规则是同一个, 所以这类语句的执行总次数会递增.
如下图:
来源: https://www.cnblogs.com/f-ck-need-u/p/9314459.html