MySQL查询缓存的内存使用和碎片管理

MySQL的查询缓存是完全存储在内存中,那么在配置和使用之前,我们看看MySQL是如何使用内存的。


当服务器启动的时候,需要初始化查询缓存需要的内存。这时候内存池是一个完整的空闲块,而这个空闲块的大小就是我们配置的查询缓存大小(query_cache_size的值)减去用于维护元数据的数据结构所消耗的空间(约40KB)。


当有查询结果需要缓存的时候,MySQL先从大的空闲块中申请一个数据块用于存储结果。由于需要在查询开始返回结果的时候就分配空间,而此时无法预知查询结果到底有多大,所以MySQL在申请数据块的时候至少要申请query_cache_min_res_unit配置的值,这样就无法为查询结果精确的分配缓存空间。在申请了数据块之后,MySQL将逐步向数据块写入数据,若数据块全部使用完成后,仍然有剩余的数据需要存储,那么将再次向空闲空间申请一个数据块,直到数据全部存储完成。当存储完成后申请的数据块还有部分剩余空间,那么这部分将被释放,并入到空闲内存部分。


MySQL在申请数据块的时候,需要先锁住空间块,然后找到合适大小的数据块,所以相对来说,分配内存块是一个非常慢的操作。


下面我们结合一张图来看看查询结果存储的过程

MySQL查询缓存的内存使用和碎片管理

当有查询结果需要缓存的时候,先从空闲空间A申请一个数据块B,然后逐步向数据块B写入数据,当数据块B全部用完后,再次向空闲空间A申请一个数据块C,然后又向C写入数据,如此往复,值到查询结果全部写入缓存。在查询结果全部写入完成后,数据块C还剩余部分空间D,这个剩余的空间D将被释放,并入到空闲空间A,而此时不会产生碎片,那么碎片是如何产生的呢?


在上面我们可以看到,当只有一条查询结果需要缓存时,即使分配的内存块在最后仍然有剩余,也不会产生碎片,而是并入到了空闲空间中。我们应当注意到这是在没有并发的情况下,不会产生碎片,而在并发查询的时候就可能产生碎片了。


现在我们假设查询结果都很小,小于query_cache_min_res_unit设置的值,而此时并发产生了两条查询结果,都需要缓存结果。那么此时会有两个数据块在写入数据,写入完成后,MySQL开始回收剩余的数据块空间时会发现,第一个数据块和第二个数据块中间会有一个空隙(这个空隙是由第一个数据块的剩余空间产生),而空隙又小于query_cache_min_res_unit的值不能被再次使用,从而产生了碎片。如下图所示:

MySQL查询缓存的内存使用和碎片管理

我们可以看到在并发的时候,查询1的数据块的剩余空间无法并入到空闲空间中,而又小于query_cache_min_res_unit的值无法再次使用,产生了碎片。查询2的数据块的剩余空间则并入到了空闲空间中,得到了释放。


另外由于缓存失效,可能导致留下太小的数据块无法再后续的缓存中使用,也会产生碎片。


使用查询缓存产生的碎片无法避免,但是我们可以运用以下方式减少碎片,从而减少内存空间的浪费。


1.通过设置合理的query_cache_min_res_unit可以减少碎片的产生,也可平衡每个数据块的大小和每次存储结果时内存块的申请次数。也需要注意,这个值太小可以使浪费的空间更少,但同时也会是内存块的申请次数更频繁,导致系统消耗。这个值太大的话又会导致碎片产生很多,所以设置合理的query_cache_min_res_unit很重要。


2.通过命令FLUSH QUERY CACHE 完成碎片整理,这个命令会将所有的查询缓存重新排序,并将所有的空闲空间聚集到一块区域上。需要特别注意,在执行此命令期间,任何其它的链接都无法访问查询缓存,从而导致服务器僵死一段时间,所以我们使用要特别小心。当然也可以将查询的缓存空间保持足够小,从而使僵死的时间控制在很短的时间内。


3.使用RESET QUERY CACHE命令清空缓存。