MYSQL之查询缓存详解

 

在MySQL服务器高负载的情况下,必须采取一种措施给服务器减轻压力,减少服务器的I/O操作。一般采用的方法是优化sql操作语句,优化服务器的配置参数,从而提高服务器的性能。MySQL使用内存缓存数据的策略来提高性能。

 

MySQL缓存主要包括关键字缓存(key cache)和查询缓存(query cache)

 

Mysql查询缓存机制(query cache)简单的说就是,当MySQL服务器收到一个查询请求的时候,MySQL服务器首先检查用户对所有相关数据库和表的SELECT权限,如权限通过,然后以该SQL文本作为key,来从查询缓存中检索是否有相同的key(因为数据缓存都是L把SQL语句的hash值作为键来保存的),如果从查询缓冲中找到对应的key值,就返回一个对应的查询结果。服务器把Qcache_hits状态变量的值加一,而不需要解析器对sql语句进行解析,如果查找不到对应的key值,然后执行sql的解析,然后查询。(大小写不一样也不会缓存)

 

如果表更改了,那么使用这个表的所有缓存查询将不再有效,查询缓存值得相关条目被清空。更改的是表中任何数据或是结构的改变,包括insert、update、delete、truncate、alter table、drop table或drop database等,也包括哪些映射到改变了表的使用merge表的查询。显然,这对于频繁更改的表,查询缓存是不合适的,而对于一些不常改变的数据且有大量相同sql查询的表,查询缓存会节约很大的性能。

 

当传入的sql语句被认为是存在缓存的情况下,系统会修改mysql的一个状态变量Qcache_hits,并将其值增加1.可以通过show status like '%Qcache_hits%'; 来查看。

 

 

查询缓存工作原理

查询缓存是完全存储在内存中。除了查询结果之外,需要缓存的还有很多别的维护相关的数据,这些管理维护数据结构大概需要40KB的内存资源,除此之外,mysql 用于查询缓存的内存被分成一各个的数据块,数据块是变长的。每一个数据块中,存储了自己的类型,大小和存储的数据本身,还外加指向前一个和后一个数据块的指针。数据块的类型有:存储查询结果,存储查询和数据表的映射、存储查询文本,等等。

 

当服务器启动的时候,它先初始化查询缓存需要的内存。这个内存池初始是一个完整的空闲块。这个空闲块的大小就是你所配置的查询缓存大小再减去用于维护元数据的数据结构所消耗的空间。

 

当有查询结果需要缓存的时候,mysql先从大的空间块中申请一个数据块用于存储结果。这个数据块需要大于参数 query_cache_min_res_unit空间。因为需要在查询开始返回结果的时候就分配空间,而此时无法预知查询结果到底多大的,所以mysql无法为每个查询结果精确分配大小恰好匹配的缓存空间。

 

因为需要先锁住空间块,然后找到合适的大小数据块,所以分配内存块是一个非常慢的操作。

MySQL尽量避免这个操作的次数。当需要缓存一个查询结果的时候,它先选择一个尽可能小的内存块,然后将结果存入其中。如果数据块全部用完,但仍有剩余数据需要存储,那么MySQL会申请一块新的数据块;仍然是尽可能小的数据块,继续存储结果数据。当查询完成时,如果申请的内存空间还有剩余,MySQL会将其释放,并放入空闲内存部分。

 

如下图:

 

image.png

 

 

我们上面说的分配内存块,并不是指通过函数malloc()向操作系统申请内存,这个操作只是初次创建查询缓存的时候执行一次。这里分配内存块是指在空闲块列表中找到一个合适的内存块,或者从正在使用的、待淘汰的内存块中回收在使用。也就是说,这里MySQL自己管理一大块内存,而不依赖操作系统的内存管理。

 

至此一切看起来都很简单。不过实际情况很复杂。例如,我们假设平均查询结果非常小,服务器在并发的想不通的两个链接返回结果,返回结果后MySQL回收剩余数据块空间时会发现,回收的数据块小于query_cache_min_res_unit,所以不能够直接在后续的内存块分配中使用。那么分配就更复杂一些。

 

在收缩第一个查询结果使用的缓存空间时,就会在第二个查询结果之间留下一个空隙,因为小于query_cache_min_res_unit而不能再次被查询缓存使用。这类空隙就成为了内存空间碎片,这在内存管理、文件系统管理上都是经典问题。有很多种情况都会导致碎片,例如缓存失效时,可能导致留下太小的数据块无法在后续缓存中使用。

image.png

 

 

 

MYSQL查询缓存怎么配置

 

mysql的查询缓存是否支持 show variables like 'have_query_cache';//YES 默认是YES

 

查看缓存的相关状态:

show status like '%Qcache_';

 

Qcache_free_blocks 22286  缓存空闲的内存块,如果该值非常大,则表明缓冲区中碎片很多。

Qcache_free_memory 464552560   在query_cache_size设置的缓存中的空闲的内存

Qcache_hits 253167563  缓存的命中次数,如果这值非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小Qcache_hits的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。

 

Qcache_inserts 150590817 查询缓存区此前总共缓存过多少条查询命令的结果

Qcache_lowmem_prunes 224370  查询缓存区已满而从其中溢出和删除的查询结果的个数

如果这个值非常大,则表明经常出现缓冲不够的情况。

Qcache_not_cached 10556187   不适合进行缓存的查询的数量,通常是由于这些查询不是 SELECT 语句或者用了now()之类的函数。

Qcache_queries_in_cache 46656 当前缓存的查询(和响应)的数量。

Qcache_total_blocks 116374  缓存总的内存块

 

show status like '%com_select%';

其中的com_select等于qcache_inserts(失效) + qcache_not_cache + 权限检查错误的。也就是说qcache_inserts这个计数不是表示没被缓存而进行的读,而是缓存失效而进行的读,没被缓存和缓存失效是两个概念,分别计数,但都会引起com_select

com_select  未命中的

缓存命中率=Qcache_hits /(Qcache_hits + com_select)

 

查看缓存的变量

 

show variables like ’%query_cache%‘;

 

have_query_cache YES

query_cache_limit 1048576  //

 

MySQL能够缓存的最大查询结果。如果查询结果大于这个值,则不会被缓存。因为查询缓存在数据生成的时候就开始尝试缓存数据,所以只有当结果全部返回后,MySQL才知道查询结果是否超出限制。

 

如果超出,MySQL则增加状态值qcache_not_cached,并将结果从查询缓存中删除。如果你实现知道有很多这样的情况发生,那么建议在查询语句中加入sql_no_cache来避免查询缓存带来的额外消耗。

 

query_cache_min_res_unit 4096  //每个被缓存的结果集要占用的最小内存

 

当要缓存查询时,其结果(发送给客户机的数据)在结果检索期间存储在查询缓存中。因此,数据通常不是在一个大块中处理的。查询缓存按需分配用于存储该数据的块,因此当一个块被填满时,将分配一个新的块。由于内存分配操作的成本很高(在时间上),查询缓存以query_cache_min_res_unit系统变量给出的最小大小分配块。当执行查询时,最后一个结果块被修剪成实际的数据大小,以便释放未使用的内存。

 

 

query_cache_size 1048576  //查询缓存使用的总内存空间,单位是字节。这个值必须是1024的整倍数,否则实际分配的数据会和指定的大小有区别。

 

query_cache_wlock_invalidate OFF

 

通常,当一个客户机获得MyISAM表上的写锁时,如果查询结果出现在查询缓存中,则不会阻止其他客户机发出从表中读取的语句, 将此变量设置为1将导致获取表的写锁,从而使查询缓存中引用该表的任何查询无效。这迫使试图访问表的其他客户机在锁定生效时等待

 

query_cache_type OFF 这个系统变量控制着查询缓存功能的开启关闭

 

query_cache_type=1; 可以是0,1,2,

0(OFF)代表不使用缓存,

1(ON)代表使用缓存,

2(DEMAND)代表根据需要使用。SELECT SQL_CACHE

 

query_cache_size=268435456; 查询缓存大小,设置为0表示禁用查询缓存。

默认缓存大小设置为0;也就是禁用查询缓存。当设置query_cache_size变量为非零值时,应记住查询缓存至少大约需要40KB来分配其数据结构。(具体大小取决于系统结构)。

如果你把该值设置的太小,将会得到一个警告,且会将query_cache_size值设置为0。

 

set @@global.query_cache_type=1;

1651 - Query cache is disabled; restart the server with query_cache_type=1 to enable it

 

查询缓存会生成内存碎片,可以通过:

flush query cache;

reset query cache;

 

flush query cache 主要用于整理碎片,不会清理查询缓存,

reset query cache则会清空查询缓存,包括之前已经缓存的查询语句结果集。

 

没有什么方法能够完全避免碎片,但是选择合适的query_cache_min_res_unit可以帮你减少由碎片导致的内存空间浪费。设置合适的值可以平衡每个数据块的大小和每次存储结果时内存块申请的次数。这个值太小,则浪费的空间更少,但是会导致更频繁的内存块申请操作;如果这个值设置的太大,那么碎片会很多。调整合适的值其实是在平衡内存浪费和CPU消耗。

 

这个参数的最合适的大小和应用程序的查询结果的平均大小直接相关。可以通过内存实际消耗(query_cache_size-qcache_free_memory)/qcache_queries_in_cache计算单个查询的平均缓存大小。如果你的应用程序的查询结果很不均匀,有的结果很大,有的结果很小,那么碎片和反复的内存块分配可能无法避免。如果你发小缓存一个非常大的结果并没有什么意义,那么你可以通过query_cache_limit限制可以缓存的最大查询结果,借此来大大减少大的查询结果的缓存,罪证减少内存碎片的发生。

 

还可以通过参数qcache_free_blocks来观察碎片。参数qcache_free_blocks反应了查询缓存中空闲块的多少。最糟糕的情况是,任何两个存储结果的数据块之间都有一个非常小的空间块。所以如果qcache_free_blocks大小恰好达到qcache_total_blocks大小的一半,那么查询缓存就有严重的碎片问题。而如果你还有很多空闲块,而状态值qcache_lowmem_prunes还不断的增加,则说明由于碎片导致了过早的在删除查询缓存结果。

 

可以使用命令flush query cache完成碎片整理。这个命令会将所有的查询缓存重新排序,并将所有的空闲空间都聚集到查询缓存的一块区域上。

不过需要注意,这个命令并不会将查询缓存清空,清空缓存由命令resert query cache完成。

flush query cache会访问所有的查询缓存,在这期间任何其他的链接都无法访问查询缓存,从而会导致服务器僵死一段时间,使用这个命令的时候需要特别小心着点。另外,根据经验,建议保持查询缓存空间足够小,以便在维护时可以将服务器僵死控制在非常短的时间内。

 

 

 

 

 

 

 

 

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注