MySQL
查询缓存是MySQL中比较独特的一个缓存区域 , 用来缓存特定Query的整个结果集信息 , 且共享给所有客户端。为了提高完全相同的Query语句的响应速度 , MySQL Server会对查询语句进行Hash计算后 , 把得到的hash值与Query查询的结果集对应存放在Query Cache中。当MySQL Server打开Query Cache之后 , MySQL Server 会对接收到的每一个SELECT 语句通过特定的Hash算法计算该QueryHash值 , 然后通过该hash值到Query Cache中去匹配。

  • 如果没有匹配 , 将这个hash值存放在一个hash链表中 , 并将Query的结果集存放到cache中 , 存放hash值链表的每个hash节点存放了相应Quey结果集在cache中的地址 , 以及该query所涉及到一些table相关信息;

  • 如果通过hash值匹配到了一样的Query ,则直接将cache中相应的Query结果集返回给客户端。

1. 查询缓存原理

查询执行路径

35b9d63c7e8e4e18ac6c8e7e7f093e00

查询缓存原理

MySQL Query Cache是用来缓存我们所执行的SELECT语句以及该语句的结果集 , MySql在实现Query Cache的具体技术细节上类似典型的KV存储 , 就是将SELECT语句和该查询语句的结果集做了一个HASH映射并保存在一定的内存区域中。当客户端发起SQL查询时 , Query Cache的查找逻辑是 , 先对SQL进行相应的权限验证 , 接着就通过Query Cache来查找结果(注意必须是完全相同 , 即使多一个空格或者大小写不同都认为不同 , 即使完全相同的SQL , 如果使用不同的字符集、不同的协议等也会被认为是不同的查询而分别进行缓存)。它不需要经过Optimizer 模块进行执行计划的分析优化 , 更不需要发生同任何存储引擎的交互 , 减少了大量的磁盘IOCPU运算 , 所以有时候效率非常高。

缓存SELECT操作或预处理查询的结果集和SQL语句 , 当有新的SELECT语句或预处理查询语句请求 , 先去查询缓存 ,判断是否存在可用的记录集 , 判断标准: 与缓存的SQL语句 , 是否完全一样 , 区分大小写。

优缺点

不需要对SQL语句做任何解析和执行 , 当然语法解析必须通过在先 , 直接从Query Cache中获得查询结果 , 提高查询性能查询缓存的判断规则 , 不够智能 , 也即提高了查询缓存的使用门槛 , 降低效率查询缓存的使用 , 会增加检查和清理Query Cache中记录集的开销。

哪些查询可能不会被缓存

  • 查询语句中加了SQL_NO_CACHE参数

  • 查询语句中含有获得值的函数 , 包含: 自定义函数 , 如 : NOW()CURDATE()GET_LOCK()RAND()CONVERT_TZ()

  • 对系统数据库的查询 : mysqlinformation_schema

  • 查询语句中使用SESSION级别变量或存储过程中的局部变量

  • 查询语句中使用了LOCK IN SHARE MODEFOR UPDATE的语句 , 查询语句中类似SELECT … INTO导出数据的语句

  • 对临时表的查询操作

  • 存在警告信息的查询语句

  • 不涉及任何表或视图的查询语句

  • 某用户只有列级别权限的查询语句

  • 事务隔离级别为Serializable时,所有查询语句都不能缓存

MySQL8.0 取消查询缓存的功能

尽管MySQL Query Cache旨在提高性能 , 但它存在严重的可伸缩性问题 , 并且很容易成为严重的瓶颈。 自MySQL 5.6(2013)以来 , 默认情况下已禁用查询缓存 , 其不能与多核计算机上在高吞吐量工作负载情况下进行扩展。 另外有时因为查询缓存往往弊大于利。比如 : 查询缓存的失效非常频繁 , 只要有对一个表的更新 , 这个表 上的所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来 , 还没使用呢 , 就被一个更新全清空了。对于更新压力大的数据库来说 , 查询缓存的命中率会非常低。除非你的业务有一张静态表 , 很长时间更新一次 , 比如系统配置表 , 那么这张表的查询才适合做查询缓存。目前大多数应用都把缓存做到了应用逻辑层 , 比如 : 使用redis或者memcache

2. 查询缓存相关服务器变量

  • query_cache_min_res_unit : 查询缓存中内存块的最小分配单位 , 默认4k

    • 该值较小会减少浪费 , 但会导致更频繁的内存分配操作

    • 该值较大会带来浪费,会导致碎片过多,内存不足

  • query_cache_limit : 单个查询结果能缓存的最大值容量 , 单位字节 , 默认为1M。对于查询结果过大而无法缓存的语句 , 建议使用SQL_NO_CACHE

  • query_cache_size : 查询缓存总共可用的内存空间。缺省情况下 , 查询缓存是关闭的。单位字节 , 必须是1024的整数倍 , 最小值40KB , 低于此值有警报 。

  • query_cache_wlock_invalidate : 如果某表被其它的会话锁定 , 是否仍然可以从查询缓存中返回结果 ,默认值为OFF , 表示可以在表被其它会话锁定的场景中继续从缓存返回数据 ; ON则表示不允许

  • query_cache_type : 是否开启缓存功能 . 取值为ON, OFF, DEMAND

    • 0(OFF) : 关闭Query Cache功能 , 任何情况下都不会使用Query Cache ;

    • 1(ON) : 开启Query Cache功能 , 查询缓存功能打开 , SELECT的结果符合缓存条件即会缓存 , 否则 , 不予缓存 。但是当SELECT语句中使用SQL_NO_CACHE提示后 , 将不使用Query Cache ;

    • 2(DEMAND) : 开启Query Cache 功能 , 查询缓存功能按需进行 , 显式指定SQL_CACHESELECT语句才会缓存 ; 其它均不予缓存

    如果query_cache_typeon而又不想利用查询缓存中的数据 , 可以用下面的SQL :

    SELECT SQL_NO_CACHE * FROM my_table WHERE condition;

    如果值为2 , 要使用缓存的话 , 需要使用SQL_CACHE开关参数 :

    SELECT SQL_CACHE * FROM my_table WHERE condition;

3. 查询缓存相关的状态变量

SHOW GLOBAL STATUS LIKE 'Qcache%';
​
MariaDB [(none)]> SHOW GLOBAL STATUS LIKE 'Qcache%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| Qcache_free_blocks      | 1       |
| Qcache_free_memory      | 1031320 |
| Qcache_hits             | 0       |
| Qcache_inserts          | 0       |
| Qcache_lowmem_prunes    | 0       |
| Qcache_not_cached       | 0       |
| Qcache_queries_in_cache | 0       |
| Qcache_total_blocks     | 1       |
+-------------------------+---------+
8 rows in set (0.002 sec)
​
  • Qcache_free_blocks : 处于空闲状态 Query Cache 中内存Block

  • Qcache_total_blocks : Query Cache中总Block , 当Qcache_free_blocks相对此值较大时 , 可能用内存碎片, 执行FLUSH QUERY CACHE清理碎片

  • Qcache_free_memory : 处于空闲状态的Query Cache内存总量

  • Qcache_hits : Query Cache命中次数

  • Qcache_inserts : 向Query Cache中插入新的Query Cache的次数 , 即没有命中的次数

  • Qcache_lowmem_prunes : 记录因为内存不足而被移除出查询缓存的查询数

  • Qcache_not_cached : 没有被CacheSQL数 , 包括无法被CacheSQL以及由于query_cache_type设置的不会被CacheSQL语句

  • Qcache_queries_in_cache : 在Query Cache中的SQL数量

4. 查询的优化

916c367079e6554708c16ad6726a3352

5. 命中率和内存使用率估算

  • 查询缓存中内存块的最小分配单位query_cache_min_res_unit :

    (query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache
  • 查询缓存命中率

    Qcache_hits / ( Qcache_hits + Qcache_inserts ) * 100%
  • 查询缓存内存使用率:

    (query_cache_size – qcache_free_memory) / query_cache_size * 100%
[root@centos7 ~]#vim /etc/my.cnf
[mysqld]
query_cache_type=ON
query_cache_size=10M
[root@centos8 ~]#systemctl restart mariadb
[root@centos8 ~]#mysql -uroot -p
MariaDB [(none)]> show variables like 'query_cache%';
+------------------------------+---------+
| Variable_name                | Value   |
+------------------------------+---------+
| query_cache_limit            | 1048576 |
| query_cache_min_res_unit     | 4096    |
| query_cache_size             | 1048576 |
| query_cache_strip_comments   | OFF     |
| query_cache_type             | ON      |
| query_cache_wlock_invalidate | OFF     |
+------------------------------+---------+
6 rows in set (0.001 sec)
MariaDB [(none)]> SHOW GLOBAL STATUS LIKE 'Qcache%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| Qcache_free_blocks      | 1       |
| Qcache_free_memory     | 1027736  |
| Qcache_hits             | 3       |
| Qcache_inserts         | 3        |
| Qcache_lowmem_prunes   | 0        |
| Qcache_not_cached       | 0       |
| Qcache_queries_in_cache | 3       |
| Qcache_total_blocks     | 8       |
+-------------------------+---------+
8 rows in set (0.003 sec)
​


熊熊