缓存是对系统性能优化的重要手段。但是有经验的DBA都建议生产环境中把MySQL Query Cache关闭。MySQL8.0更是直接取消了查询缓存。
一键获取MySQL核心数据结构、底层实现原理、索引、事务、锁机制、MySQL性能优化案例、美团MySQL中间件等。
这是为什么?查询缓存在使用中遇到了什么坑?带着如下几个问题,我们正式进入本文。
MySQL体系架构
为了提高完全相同的Query语句的响应速度,MySQL Server会对查询语句进行Hash计算后,把得到的hash值与Query查询的结果集对应存放在Query Cache中。
如果没有匹配,将这个hash值存放在一个hash链表中,并将Query的结果集存放到cache中。
如果通过hash值匹配到了一样的Query,则直接将cache中相应的Query结果集返回给客户端。
目前MySQL Query Cache只会cache select语句,其他类似show ,use的语句不会被cache
have_query_cache: 该MySQL Server是否支持Query Cache。
query_cache_limit: MySQL能够缓存的最大查询结果,查询结果大于该值时不会被缓存。
query_cache_min_res_unit: 查询缓存分配的最小块的大小(字节)。
当查询进行的时候,MySQL把查询结果保存在qurey cache中,但如果要保存的结果比较大,超过query_cache_min_res_unit的值 ,这时候mysql将一边检索结果,一边进行保存结果,也就是说,有可能在一次查询中,mysql要进行多次内存分配的操作。适当的调节query_cache_min_res_unit可以优化内存。
query_cache_size: 为缓存查询结果分配的内存的数量,单位是字节,且数值必须是1024的整数倍。默认值是0,即禁用查询缓存。
query_cache_type: 设置查询缓存类型,默认为ON。设置GLOBAL值可以设置后面的所有客户端连接的类型。客户端可以设置SESSION值以影响他们自己对查询缓存的使用。
query_cache_wlock_invalidate:如果某个表被锁住,是否返回缓存中的数据,默认关闭,也是建议的。
MySQL缓存机制简单的说就是缓存sql文本及查询结果,如果运行完全相同的SQL,服务器直接从缓存中取到结果,而不需要再去解析和执行SQL。
如果表中任何数据或是结构发生改变,包括INSERT、UPDATE、DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等,那么使用这个表的所有缓存查询将不再有效,查询缓存中值相关条目被清空。
显然,这对于频繁更新的表,查询缓存是不适合的。
缓存规则:
将查询语句和结果集返回到内存,下次再查直接从内存中取;
sessions共享,一个client查询的缓存结果,另一个client也可以使用;
SQL必须完全一致才会导致cache命中;
不确定的函数将永远不会被cache, 比如current_date, now等;
太大的result set不会被cache (< query_cache_limit);
MySQL缓存在分库分表环境下是不起作用的;
执行SQL里有触发器,自定义函数时,MySQL缓存也是不起作用的;
在表的结构或数据发生改变时,基于该表相关cache立即全部失效。
缓存机制中的内存管理
MySQL Query Cache 使用内存池技术,自己管理内存释放和分配,而不是通过操作系统。内存池使用的基本单位是变长的block,
用来存储类型、大小、数据等信息;一个result
set的cache通过链表把这些block串起来。block最短长度为query_cache_min_res_unit。
当服务器启动的时候,会初始化缓存需要的内存,是一个完整的空闲块。当查询结果需要缓存的时候,先从空闲块中申请一个数据块为参数query_cache_min_res_unit配置的空间,即使缓存数据很小,申请数据块也是这个,因为查询开始返回结果的时候就分配空间,此时无法预知结果多大。
分配内存块需要先锁住空间块,所以操作很慢,MySQL会尽量避免这个操作,选择尽可能小的内存块,如果不够,继续申请,如果存储完时有空余则释放多余的。
但是如果并发的操作,余下的需要回收的空间很小,小于query_cache_min_res_unit,不能再次被使用,就会产生碎片。
优点:
Query Cache的查询,发生在MySQL接收到客户端的查询请求、查询权限验证之后和查询SQL解析之前。
也就是说,当MySQL接收到客户端的查询SQL之后,仅仅只需要对其进行相应的权限验证之后,就会通过Query Cache来查找结果,甚至都不需要经过Optimizer模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互。
由于Query Cache是基于内存的,直接从内存中返回相应的查询结果,因此减少了大量的磁盘I/O和CPU计算,导致效率非常高。
缺点:
MySQL会对每条接收到的SELECT类型的查询进行hash计算,然后查找这个查询的缓存结果是否存在。虽然hash计算和查找的效率已经足够高了,一条查询语句所带来的开销可以忽略,但一旦涉及到高并发,有成千上万条查询语句时,hash计算和查找所带来的开销就必须重视了。
Query Cache的失效问题。如果表的变更比较频繁,则会造成Query Cache的失效率非常高。表的变更不仅仅指表中的数据发生变化,还包括表结构或者索引的任何变化。
查询语句不同,但查询结果相同的查询都会被缓存,这样便会造成内存资源的过度消耗。查询语句的字符大小写、空格或者注释的不同,Query Cache都会认为是不同的查询(因为他们的hash值会不同)。
相关系统变量设置不合理会造成大量的内存碎片,这样便会导致Query Cache频繁清理内存。
对性能的影响
读查询开始之前必须检查是否命中缓存。
如果读查询可以缓存,那么执行完查询操作后,会查询结果和查询语句写入缓存。
当向某个表写入数据的时候,必须将这个表所有的缓存设置为失效,如果缓存空间很大,则消耗也会很大,可能使系统僵死一段时间,因为这个操作是靠全局锁操作来保护的。
对InnoDB表,当修改一个表时,设置了缓存失效,但是多版本特性会暂时将这修改对其他事务屏蔽,在这个事务提交之前,所有查询都无法使用缓存,直到这个事务被提交,所以长时间的事务,会大大降低查询缓存的命中。
生产如何设置MySQL Query Cache
MySQL中的Query Cache是一个适用较少情况的缓存机制。如果你的应用对数据库的更新很少,那么QC将会作用显著。比较典型的如博客系统,一般博客更新相对较慢,数据表相对稳定不变,这时候QC的作用会比较明显。
但是一个更新频繁系统。Query Cache缓存的作用是很微小的,如果应用层能够实现缓存,将可以忽略Query Cache的效果。所以,如果经常有更新的系统,想要获得较高tps的话,建议一开始就关闭Query Cache
查询缓存的替代方案MySQL查询缓存工作的原则是:执行查询最快的方式就是不去执行,但是查询仍然需要发送到服务器端,服务器也还需要做一点点工作,如果对于某些查询完全不需要与服务器通信效果会如何呢,这时客户端缓存可以很大程度上分担MySQL服务器的压力。