为什么MySQL 8.0删除了查询缓存?

前言

MySQL在其最新的8.0版本中,删除了查询缓存(Query Cache)区域,就此,MySQL的Query Cache彻底的退出了历史舞台。在5.7版本中,MySQL已经将Query Cache的选项(query_cache_type)的缺省值设为了关闭,并在5.7.20版本中,将该配置标记为了Deprecated。

那么MySQL经历这么多个版本的迭代后,为什么会取消该区域?Query Cache设计的初衷是什么?

本篇,让我们来一起了解MySQL Query Cache 。

什么是MySQL Query Cache

 查询缓存存储了SELECT语句的文本,以及发送给客户端的相应结果。如果后来收到一个相同的语句,服务器会从查询缓存中检索结果,而不是再次解析和执行该语句。查询缓存是在会话之间共享的,因此一个客户端产生的结果集可以被用来响应另一个客户端发出的相同查询。 查询缓存在这样的环境中很有用,即你有一些不经常变化的表,而服务器收到许多相同的查询。

以上是MySQL官方文档中对于Query Cache的定义,MySQL查询缓存是MySQL中比较独特的一个缓存区域,用来缓存特定Query的整个结果集信息,且共享给所有客户端。

将官网的话翻译过来就是:

MySQL在查询的时候首先会查询缓存,如果缓存命中的话就直接返回结果,不需要解析sql语句,也不会生成执行计划,更不会执行;如果没有命中缓存,则再进行SQL解析以及进行查询,并将结果返回(也同时将结果放入到缓存中)

图片

缓存查找是利用对大小写敏感的哈希查找来实现的,Hash查找只能进行全值查找(sql完全一致),如果缓存命中,检查用户权限,如果权限允许,直接返回,查询不被解析,也不会生成查询计划,由于在缓存更新的时候会对数据加锁,所以对于读写比较频繁的系统,建议关闭缓存

对于5.7.20版本之前,可以通过编辑my.cnf配置,通过如下参数开启Query Cache:​​​​​​​

各个参数含义如下:

图片

MySQL缓存发挥作用的情况

1、查询缓存可以降低查询执行的时间,但是却不能减少查询结果传输的网络消耗,如果网络传输消耗是整个查询过程的主要瓶颈,那么查询缓存的作用也很小。

2、对于那些需要消耗大量资源的查询通常都是非常适合缓存的,对于复杂的SELECT语句都可以使用查询缓存,不过需要注意的是,涉及表上的UPDATE、DELETE、INSERT操作相比SELECT来说要非常少才行。

3、查询缓存命中率:Qcache_hits/(Qcahce_hits+Com_select),查询缓存命中率多大才是好的命中率,需要具体情况具体分析。只要查询缓存带来的效率提升大于查询缓存带来的额外消耗,即使30%的命中率也是值得。另外,缓存了哪些查询也很重要,如果被缓存的查询本身消耗巨大,那么即使缓存命中率低,对系统性能提升仍然是有好处的。

4、任何SELECT语句没有从查询缓存中返回都称为“缓存未命中”,以如下列情况:

  • 查询语句无法被缓存,可能因为查询中包含一个不确定的函数,或者查询结果太大而无法缓存。

  • MySQL从未处理这个查询,所以结果也从不曾被缓存过。

  • 虽然之前缓存了查询结果,但由于查询缓存的内存用完了,MYSQL需要删除某些缓存,或者由于数据表被修改导致缓存失效。

如果服务器上有大量缓存缓存未命中,但是实际上绝大查询都被缓存了,那么一定是有如下情况发生:

  • 查询缓存还没有完成预热,即MySQL还没有机会将查询结果都缓存起来。

  • 查询语句之前从未执行过。如果应用程序不会重复执行一条查询语句,那么即使完成预热仍然会有很多缓存未命中。

  • 缓存失效操作太多,缓存碎片、内存不足、数据修改都会造成缓存失效。可以通过参数Com_*来查看数据修改的情况(包括Com_update,Com_delete等),还可以通过Qcache_lowmem_prunes来查看有多少次失效是由于内存不足导致的。

5、有一个直观的方法能够反映查询缓存是否对系统有好处,推荐一个指标:”命中和写入“的比率,即Qcache_hits和Qcache_inserts的比值。根据经验来看,当这个比值大于3:1时通常查询缓存是有效的,如果能达到10:1最好。

6、通常可以通过观察查询缓存内存的实际使用情况Qcache_free_memory,来确定是否需要缩小或者扩大查询缓存。

MySQL Query Cache对性能的影响

1>MySQL缓存相关的配置参数​​​​​​​

mysql> show variables like '%query_cache%';+------------------------------+---------+| Variable_name                | Value   |+------------------------------+---------+| have_query_cache             | YES     |      --查询缓存是否可用| query_cache_limit            | 1048576 |      --可缓存具体查询结果的最大值| query_cache_min_res_unit     | 4096    |      --查询缓存分配的最小块的大小(字节)| query_cache_size             | 599040  |      --查询缓存的大小| query_cache_type             | ON      |      --是否支持查询缓存| query_cache_wlock_invalidate | OFF     |      --控制当有写锁加在表上的时候,是否先让该表相关的 Query Cache失效+------------------------------+---------+6 rows in set (0.02 sec)

2>MySQL Query Cache的额外开销

图片

如上图所示: 在MySQL Server中打开Query Cache对数据库的读和写都会带来额外的消耗:

  • 1) 读查询开始之前必须检查是否命中缓存。

  • 2) 如果读查询可以缓存,那么执行完查询操作后,会查询结果和查询语句写入缓存。

  • 3) 当向某个表写入数据的时候,必须将这个表所有的缓存设置为失效,如果缓存空间很大,则消耗也会很大,可能使系统僵死一段时间,因为这个操作是靠全局锁操作来保护的。

  • 4) 对InnoDB表,当修改一个表时,设置了缓存失效,但是多版本特性会暂时将这修改对其他事务屏蔽,在这个事务提交之前,所有查询都无法使用缓存,直到这个事务被提交,所以长时间的事务,会大大降低查询缓存的命中

3>MySQL Query Cache碎片优化

图片

如上图所示, 没有什么办法能够完全避免碎片,但是选择合适的query_cache_min_res_unit可以帮你减少由碎片导致的内存空间浪费。这个值太小,则浪费的空间更少,但是会导致频繁的内存块申请操作;如果设置得太大,那么碎片会很多。调整合适的值其实是在平衡内存浪费和CPU消耗。可以通过内存实际消耗(query_cache_size - Qcache_free_memory)除以Qcache_queries_in_cahce计算单个查询的平均缓存大小。可以通过Qcahce_free_blocks来观察碎片。

通过FLUSH_QUERY_CAHCE完成碎片整理,这个命令将所有的查询缓存重新排序,并将所有的空闲空间都聚焦到查询缓存的一块区域上。

4>MySQL缓存状态查看​​​​​​​

mysql> SHOW STATUS LIKE 'Qcache%';+-------------------------+--------+| Variable_name           | Value  |+-------------------------+--------+| Qcache_free_blocks      | 1      | ----在查询缓存中的闲置块,如果该值比较大,则说明Query Cache中的内存碎片可能比较多。FLUSH QUERY CACHE会对缓存中的碎片进行整理,从而得到一个较大的空闲内存块。| Qcache_free_memory      | 382704 | ----剩余缓存的大小| Qcache_hits             | 198    | ----缓存命中次数| Qcache_inserts          | 131    | ----缓存被插入的次数,也就是查询没有命中的次数。| Qcache_lowmem_prunes    | 0      | ----由于内存低而被删除掉的缓存条数,如果这个数值在不断增长,那么一般是Query Cache的空闲内存不足(通过Qcache_free_memory判断),或者内存碎片较严重(通过Qcache_free_blocks判断)。| Qcache_not_cached       | 169    | ----没有被缓存的条数,有三种情况会导致查询结果不会被缓存:其一,由于query_cache_type的设置;其二,查询不是SELECT语句;其三,使用了now()之类的函数,导致查询语句一直在变化。| Qcache_queries_in_cache | 128    | ----缓存中有多少条查询语句| Qcache_total_blocks     | 281    | ----总块数+-------------------------+--------+8 rows in set (0.00 sec)
  • Query Cache碎片率Query Cache碎片率 = Qcache_free_blocks / Qcache_total_blocks * 100%

如果Query Cache碎片率超过20%,则可以用FLUSH QUERY CACHE整理内存碎片;如果你的查询都是小数据量的话,可以尝试减小query_cache_min_res_unit。

  • Query Cache利用率Query Cache利用率 = (query_cache_size - Qcache_free_memory) / query_cache_size * 100%

Query Cache利用率在25%以下的话,说明query_cache_size设置的过大,可适当减小;Query Cache利用率在80%以上,而且Qcache_lowmem_prunes > 50的话,说明query_cache_size可能有点小,或者就是内存碎片太多。

  • Query Cache命中率

  • 1>可缓存查询的Query Cache命中率 = Qcache_hits / (Qcache_hits + Qcache_inserts) * 100%

  • 2>涵盖所有查询的Query Cache命中率 = Qcache_hits / (Qcache_hits + Com_select) * 100%

若命中率在50-70%的范围之内,则表明Query Cache的缓存效率较高。如果命中率明显小于50%,那么建议禁用(将query_cache_type设置为0(OFF))或按需使用(将query_cache_type设置为2(DEMAND))Query Cache,节省的内存可以用作InnoDB的缓冲池。

  • 如何判断Query Cache是空闲内存不足,还是内存碎片太多?如果Qcache_lowmem_prunes值比较大,表示Query Cache的内存空间大小设置太小,需要增大。

    如果Qcache_free_blocks值比较大,表示内存碎片较多,需要使用FLUSH QUERY CACHE语句清理内存碎片。

Query Cache的优势

Query Cache设计之初,MySQL希望可以利用查询缓存,提升查询的效率,在执行每一次SELECT的时候,MySQL都会首先经过Query Cache区域,检查查询是否可以命中缓存,如果命中,则直接返回结果集,相较于从硬盘(Disk)检索数据,直接从内存(RAM)中获取数据集无疑是极为高效的,可以大大的节省查询执行的时间。

如果业务场景是只读不写的情况,且有大量重复的查询请求,开启Query Cache会带来巨大的性能提升,这意味着每一次查询请求,会有很大的概率直接命中缓存,而无需经过SQL解析优化,硬盘加载数据等一系列过程。

Query Cache的劣势

1、查询SQL的命中

Query Cache中对SQL语句的缓存,是基于字节级别的(The query must match byte-for-byte ),这意味着SQL语句发生任何一点变化,Query Cache都无法进行命中,这对于实际生产环境的查询,显得有些过于苛刻。

2、缓存过期

Query Cache的淘汰策略过于苛刻,任何对于表中数据的修改,都会使得缓存失效,这里的修改包括:INSERT, UPDATE, DELETE, TRUNCATE, ALTER TABLE, DROP TABLE, or DROP DATABASE,这个特性意味着,只有对于读远大于写的数据表,Query Cache才能发挥作用,对于读写均衡以及写多读少的场景,Query Cache基本上很难发挥作用。

3、分区表禁用

如果数据表使用了分区,Query Cache将会被自动的禁用,无法生效。

4、增加额外的负载

当开启Query Cache选项后,如果查询请求没有命中Query Cache时,MySQL会需要额外的性能开销去处理结果集,写入Query Cache中,最糟糕的情况下,这个额外的性能开销是13%,但实际场景中的情况会更加的复杂,通常情况下,额外的性能开销会低于该值,但这仍是一笔无谓的性能损耗。

总上,我们会发现,Query Cache好像非常的鸡肋,因为在我们大多数的场景中,很少情况下会出现只读不写的情况,更多的情况则是读多写少或读写均衡,这使得Query Cache很难对我们的实际业务产生正向的影响。

MySQL官方的抉择

经过上述对Query Cache的优缺点的了解,我们可以明白Query Cache适合于什么样的业务场景,但对于大多数场景下,Query Cache是比较鸡肋的,因此,从MySQL 5.6版本开始,将Query Cache设置为了默认关闭,并在MySQL 8.0版本中,彻底移除了Query Cache,并给出了解释:​​​​​​​

Assuming that scalability could be improved, the limiting factor of the query cache is that since only queries that hit the cache will see improvement; it is unlikely to improve predictability of performance. For user facing systems, reducing the variability of performance is often more important than improving peak throughput.
假设可扩展性可以得到改善,那么查询缓存的限制因素是,由于只有击中缓存的查询才能看到改善;它不太可能改善性能的可预测性。
对于面向用户的系统来说,减少性能的可变性往往比提高峰值吞吐量更重要。

图片

We considered what improvements we could make to query cache versus optimizations that we could make which provide improvements to all workloads.
While these choices themselves are orthogonal, engineering resources are finite. That is to say that we are shifting strategy to invest in improvements that are more generally applicable to all workloads.
我们考虑了对查询缓存的改进和对所有工作负载的优化,我们可以做哪些改进。
虽然这些选择本身是正交的,但工程资源是有限的。
这就是说,我们正在转变策略,投入于更普遍适用于所有工作负载的改进。

MySQL官方团队对于在8.0版本中彻底移除Query Cache的决策做出了如上的解释,并给出了所替代的解决方案建议——使用第三方工具客户端缓存ProxySQL 来代替Query Cache。

如下图所示,MySQL官方给出了使用ProxySQL 对比原生Query Cache 性能报告,从图中可以清晰的看到,ProxySQL 的查询性能完胜原生的Query Cache。

图片

对于ProxySQL的使用,在这里将不会过多的介绍,感兴趣的小伙伴可以自行查询相关资料。

以上为全部内容。

更多技术内容,欢迎扫码图片。

  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值