【MySQL】查询缓存机制

MySQL查询缓存简介

开启查询缓存后,查询语句的解析过程:
在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据。如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前MySQL会检查一次用户权限。若权限没有问题,MySQL会跳过所有其他阶段(解析、优化、执行等),直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。

当MySQL Server打开Query Cache之后,MySQL Server会对接收到的每一个SELECT 语句通过特定的Hash算法计算该Query的Hash值,然后通过该hashi值到Query Cache中去匹配。

  • 如果没有匹配,将这个hash值存放在一个hash链表中,并将Query的结果集存放到cache中,存放hashi值链表的每个hash节点存放了相应Quey结果集在cache中的地址,以及该query所涉及到一些table相关信息;
  • 如果通过hash值匹配到了一样的Query,则直接将cache中相应的Query结果集返回给客户端。

目前MySQL Query Cache只会cache select语句,其他类似show ,use的语句不会被cache MySQL 的每个Query Cache都是以SQL文本作为key来存储的,在应用Query Cache之前,SQL文本不会做任何处理。也就是说,两个SQL语句,只要相差哪怕一个字符(例如大小写不一样,多一个空格,多注释),那么这两个SQL将使用不同的Cache地址。如: 下面三条SQL将会被存储在三个不同的缓存里,虽然他们的结果都是一样的。select * FROM people where name=‘surfchen’; select * FROM people where /hey~/ name=‘surfchen’; SELECT * FROM people where name=‘surfchen’;

MySQL缓存机制

MySQL缓存机制简单的说就是缓存sql文本及查询结果,如果运行相同的SQL,服务器直接从缓存中取到结果,而不需要再去解析和执行SQL。如果表更改了,那么使用这个表的所有缓存查询将不再有效,查询缓存中值相关条目被清空。这里的更改指的是表中任何数据或是结构发生改变,包括INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些映射到改变了的表使用MERGE表的查询。显然,这对于频繁更新的表,查询缓存是不适合的,而对于一些不常改变数据且有大量相同SQL查询的表,查询缓存会节约很大的性能。

查询必须是完全相同(逐字节相同)才能够被认为是相同的。另外,同样的查询字符串由于其它原因可能认为是不同的。使用不同的数据库、不同的协议版本或者不同 默认字符集的查询被认为是不同的查询并且分别进行缓存。

缓存规则

开启了缓存,MySQL Server会自动将查询语句和结果集返回到内存,下次再查直接从内存中取;

缓存的结果是通过sessions共享的,所以一个client查询的缓存结果,另一个client也可以使用;

MySQL Query Cache内容为 select 的结果集, cache 使用完整的SQL字符串做 key, 并区分大小写,空格等。即两个SQL必须完全一致才会导致cache命中。即检查查询缓存时,MySQL Server不会对SQL做任何处理,它精确的使用客户端传来的查询,只要字符大小写或注释有点不同,查询缓存就认为是不同的查询;

prepared statement永远不会cache到结果,即使参数完全一样。在 5.1 之后会得到改善。

where条件中如包含任何一个不确定的函数将永远不会被cache, 比如current_date, now等。

date 之类的函数如果返回是以小时或天级别的,最好先算出来再传进去。

太大的result set不会被cache (< query_cache_limit)

MySQL缓存在分库分表环境下是不起作用的

执行SQL里有触发器,自定义函数时,MySQL缓存也是不起作用的

手动清理缓存

手动清理缓存可以使用下面三个SQL:

  • FLUSH QUERY CACHE; #清理查询缓存内存碎片
  • RESET QUERY CACHE;#从查询缓存中移除所有查询
  • FLUSH TABLES; #关闭所有打开的表,同时该操作会清空查询缓存中的内容
缓存机制中的内存管理

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,不能再次被使用,就会产生碎片。

未生效

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

  • 查询缓存还没有完成预热,即MySQL还没有机会将查询结果都缓存起来。
  • 查询语句之前从未执行过。如果应用程序不会重复执行一条查询语句,那么即使完成预热仍然会有很多缓存未命中。
  • 缓存失效操作太多,缓存碎片、内存不足、数据修改都会造成缓存失效。可以通过参数Com_*来查看数据修改的情况(包括Com_update,Com_delete等),还可以通过Qcache_lowmem_prunes来查看有多少次失效是由于内存不足导致的。

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

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

MySQL Query Cache碎片优化

在这里插入图片描述

MySQL Query Cache优缺点

优点:
Query Cache的查询,发生在MySQL接收到客户端的查询请求、查询权限验证之后和查询SQL解析之前。也就是说,当MySQL接收到客户端的查询SQL之后,仅仅只需要对其进行相应的权限验证之后,就会通过Query Cache来查找结果,甚至都不需要经过Optimizer模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互。由于Query Cache是基于内存的,直接从内存中返回相应的查询结果,因此减少了大量的磁盘I/O和CPU计算,导致效率非常高。

缺点:

查询语句的hash计算和hash查找带来的资源消耗。如果将query_cache_type设置为1(也就是ON),那么MySQL会对每条接收到的SELECT类型的查询进行hash计算,然后查找这个查询的缓存结果是否存在。虽然hash计算和查找的效率已经足够高了,一条查询语句所带来的开销可以忽略,但一旦涉及到高并发,有成千上万条查询语句时,hash计算和查找所带来的开销就必须重视了。

Query Cache的失效问题。如果表的变更比较频繁,则会造成Query Cache的失效率非常高。表的变更不仅仅指表中的数据发生变化,还包括表结构或者索引的任何变化。

查询语句不同,但查询结果相同的查询都会被缓存,这样便会造成内存资源的过度消耗。查询语句的字符大小写、空格或者注释的不同,Query Cache都会认为是不同的查询(因为他们的hash值会不同)。
相关系统变量设置不合理会造成大量的内存碎片,这样便会导致Query Cache频繁清理内存。

设置使用查询缓存的方式

使用 query_cache_type 变量来开启查询缓存,开启方式有三种:

ON : 正常缓存。表示在使用 SELECT 语句查询时,若没指定 SQL_NO_CACHE 或其他非确定性函数,则一般都会将查询结果缓存下来。

DEMAND :指定SQL_CACHE才缓存。表示在使用 SELECT 语句查询时,必须在该 SELECT 语句中指定 SQL_CACHE 才会将该SELECT语句的查询结果缓存下来:

select SQL_CACHE name from user where id = 15; #只有明确指定 SQL_CACHE 的SELECT语句,才会将查询结果缓存。

OFF: 关闭查询缓存。

立刻生效,重启服务失效
mysql> set global query_cache_type=1;

当my.cnf 中,query_cache_type = OFF ,启动mysql服务后,在mysql命令行中使用上面语句开启查询缓存,会报错:

ERROR 1651 (HY000): Query cache is disabled; restart the server with query_cache_type=1 to enable it

遇到这种情况,是无法在mysql命令行中开启查询缓存的,必须修改my.cnf的query_cache_type = ON,然后重启mysql服务。

永久生效

修改 my.cnf :

query_cache_type = ON

重启 mysql 服务

设置查询缓存的大小
query_cache_size :查询缓存的总体可用空间。

如果 query_cache_size=0 ,那即便你设置了 query_cache_type = ON,查询缓存仍然是无法工作的。

立刻生效,重启服务失效

mysql> set global query_cache_size=536870912;   #单位为字节,必须为1024的倍数。

永久生效
修改 my.cnf :

query_cache_size = 500M   #支持单位:K,M,G
查询缓存相关参数

与缓存功能相关的服务器变量:

mysql> SHOW GLOBAL VARIABLES LIKE '%query_cache%';
+------------------------------+----------+
| Variable_name                | Value    |
+------------------------------+----------+
| have_query_cache             | YES     |      
| query_cache_limit            | 1048576  |
| query_cache_min_res_unit     | 4096     |
| query_cache_size             | 16777216 |
| query_cache_strip_comments   | OFF      |
| query_cache_type             | ON       |
| query_cache_wlock_invalidate | OFF      |
+------------------------------+----------+

变量说明:

have_query_cache:该MySQL Server是否支持Query Cache。

query_cache_limit : MySQL能够缓存的最大查询结果;如果某查询的结果大小大于此值,则不会被缓存;

query_cache_min_res_unit : 查询缓存分配的最小块的大小(字节)。 默认值是4096(4KB)。当查询进行的时候,MySQL把查询结果保存在query cache中,但如果要保存的结果比较大,超过query_cache_min_res_unit的值 ,这时候mysql将一边检索结果,一边进行保存结果,所以,有时候并不是把所有结果全部得到后再进行一次性保存,而是每次分配一块query_cache_min_res_unit大小的内存空间保存结果集,使用完后,接着再分配一个这样的块,如果还不不够,接着再分配一个块,依此类推,也就是说,有可能在一次查询中,mysql要进行多次内存分配的操作。适当的调节query_cache_min_res_unit可以优化内存如果你的查询结果都是一些small result,默认的query_cache_min_res_unit可能会造成大量的内存碎片如果你的查询结果都是一些larger resule,你可以适当的把query_cache_min_res_unit调大

计算单个查询的平均缓存大小:(query_cache_size-Qcache_free_memory)/Qcache_queries_in_cache

query_cache_size : 查询缓存的总体可用空间,单位为字节;其必须为1024的倍数;

query_cache_strip_comments:用于控制是否去掉SQL查询语句中的注释部分之后在作为key存入查询缓存中。

query_cache_type: 查询缓存类型;是否开启缓存功能,开启方式有三种{ON|OFF|DEMAND};

query_cache_wlock_invalidate : 如果某个表被锁住,是否返回缓存中的数据,默认关闭,也是建议的。一般情况,当客户端对MyISAM表进行WRITE锁定时,如果查询结果位于查询缓存中,则其它客户端未被锁定,可以对该表进行查询。将该变量设置为1,则可以对表进行WRITE锁定,使查询缓存内所有对该表进行的查询变得非法。这样当锁定生效时,可以强制其它试图访问表的客户端来等待。

与缓存相关的状态变量
mysql> SHOW  GLOBAL STATUS  LIKE  'Qcache%';
+-------------------------+----------+
| Variable_name            | Value   |
+-------------------------+----------+
| Qcache_free_blocks       | 1       | #查询缓存中的空闲块
| Qcache_free_memory       | 16759656| #查询缓存中尚未使用的空闲内存空间
| Qcache_hits              | 16      | #缓存命中次数
| Qcache_inserts           | 71      | #向查询缓存中添加缓存记录的条数
| Qcache_lowmem_prunes     | 0       | #表示因缓存满了而不得不清理部分缓存以存储新的缓存,这样操作的次数。若此数值过大,则表示缓存空间太小了。
| Qcache_not_cached        | 57      | #没能被缓存的次数
| Qcache_queries_in_cache  | 0       | #此时仍留在查询缓存的缓存个数
| Qcache_total_blocks      | 1       | #共分配出去的块数
+-------------------------+----------+
缓存命中率的计算
mysql> SHOW GLOBAL STATUS WHERE Variable_name='Qcache_hits' OR Variable_name='Com_select';
+---------------+-----------+
| Variable_name | Value |
+---------------+-----------+
| Com_select    | 279292490 | #非缓存查询次数
| Qcache_hits   | 307366973 | # 缓存命中次数
+---------------+-----------

缓存命中率:Qcache_hits/(Qcache_hits+Com_select)

“命中和写入”的比率
mysql> SHOW GLOBAL STATUS WHERE Variable_name='Qcache_hits' OR Variable_name='Qcache_inserts';
+----------------+-----------+
| Variable_name  | Value     |
+----------------+-----------+
| Qcache_hits | 307416113    | #缓存命中次数
| Qcache_inserts | 108873957 | #向查询缓存中添加缓存记录的条数
+----------------+-----------+

“命中和写入”的比率: Qcache_hits/Qcache_inserts # 如果此比值大于3:1, 说明缓存也是有效的;如果高于10:1,相当理想;

发布了210 篇原创文章 · 获赞 14 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/LU_ZHAO/article/details/105035546