为了加快查询语句的执行性能,从MySQL早期的版本开始就提供了一种名叫Query Cache的缓存技术。这个缓存技术和技术人员使用哪种数据库引擎无关,它完全独立工作于各种数据库引擎的上层,并使用独立的内存区域。
Query Cache的工作原理描述起来也比较简单,当某一个客户端连接(session)进行SQL查询并得到返回信息时,MySQL数据库除了将查询结果返回给客户端外,还在特定的内存区域缓存这条SQL查询语句的结果,以便包括这个客户端在内的所有客户的再次执行相同查询请求时,MySQL能够直接从缓存区返回结果。这里有两个关键点需要明确:
-
什么是“相同的查询语句”?Query Cache使用K-V结构对查询结果进行记录,其中的K就是查询语句本身(当然还要附加上诸如database name这样的关键信息)。所以“select * from A”和“select * from a”这样的语句将被看成是两条不同的查询语句。“select * from A”和“select * from A”也将被视为两条不同的查询语句(空格数量不一样)。
-
怎样避免“缓存数据不一致”的问题?
一旦被缓存的查询结果所涉及的数据表发生了“写”操作,那么无论“写”操作本身是否影响到被缓存的数据,涉及到数据表的所有缓存数据都将被清除。这种简单暴力的处理方式,不仅绕过了数据一致性问题,还节约了宝贵的时间——因为在大多数数据库应用中,读请求是远远多余写请求的。如果您所在团队开发的应用会使MySQL数据库读写请求比例达到或查过1:1,那么使用Query Cache就没有什么意义,建议直接关闭。
Query Cache基本设置
您可以通过“show variables like ‘query_cache%’”语句查询当前为MySQL服务设定的和Query Cache相关的参数值。
# show variables like 'query_cache%';
query_cache_limit 1048576
query_cache_min_res_unit 4096
query_cache_size 0
query_cache_type OFF
query_cache_wlock_invalidate OFF
- 这些设置参数的定义可以简单描述如下:
-
query_cache_size:这是参数设置了MySQL服务中Query Cache的全局大小,单位为byte。该参数在MySQL version 5.5及以后版本中的默认值都为0,也就是说如果在这些版本中要使用Query Cache则需要自己设置Query Cache的大小。query_cache_size不应该这是太大(最大支持256M),这是因为当某张数据表进行写操作时,MySQL服务需要从Query Cache抹去的相关数据也就越多,反而会增加耗时。query_cache_size设置为33554432(32M)是比较好的。
-
query_cache_limit:该参数设置单条查询语句允许缓存到Query Cache中的最大结果容量值,1048576(1M)是它的默认值。也就是说如果查询语句返回的查询结果集合大于1M,则这个查询结果集合不会缓存到Query Cache区域。
-
query_cache_min_res_unit:该参数设置Query Cache每次分配内存的最小大小,默认值为4096(4KB)。
-
query_cache_type:注意,既是单独设置query_cache_size为0,也不会使MySQL服务关闭Query Cache功能。一定要设置query_cache_type参数为0(OFF)才行。另外当该参数值为1(ON)时,代表开启Query Cache功能,此时必须在SQL查询语句中明确使用SQL_NO_CACHE,才能关闭这条查询语句的Query Cache功能;该参数的值还可以为2(DEMAND),此时只有当SQL查询语句明确使用SQL_CACHE关键字,才能让这条查询语句使用Query Cache功能。
-
query_cache_wlock_invalidate:该参数设置Query Cache中数据的失效时刻(非常重要)。如果该值为1(ON),则在数据表被写锁定的同时该表中数据涉及的所有Query Cache都将失效;如果该值为0(OFF),则表示在数据表写锁定的同时,Query Cache中该数据表的相关数据都还继续有效。
您还可以通过“show status like ‘Qcache%’”语句查询当前MySQL服务中Query Cache的工作状态
# show status like 'Qcache%'
Qcache_free_blocks 0
Qcache_free_memory 0
Qcache_hits 0
Qcache_inserts 0
Qcache_lowmem_prunes 0
Qcache_not_cached 0
Qcache_queries_in_cache 0
Qcache_total_blocks 0
以上示例中所有和Query Cache相关的状态值都为0,这是因为在演示的MySQL服务中默认关闭了Query Cache功能(主要是设置了query_cache_type的值为0)。不过以上展示的Query Cache状态信息中一些状态项还是要进行说明:
-
Qcache_free_memory:该指标说明了当前Query Cache专用内存区域还有多少剩余内存。
-
Qcache_hits:该指标说明当前Query Cache从MySQL服务启动到现在的命中次数。
-
Qcache_lowmem_prunes:该指标说明因为Query Cache内存不足而被清除的查询结果数量。
其它的状态项可参见MySQL的官方文档《The MySQL Query Cache》
Query Cache的局限性和使用建议
为什么MySQL Version 5.5及以后的版本会默认关闭Query Cache功能呢?这至少说明官方并不建议在任何场景下都是用Query Cache功能,甚至是大多数场景下。首先,Query Cache存在功能局限性:
-
早期版本(Version 5.1)的Query Cache功能并不支持变量绑定,也就是说类似“select * from A where field = ?”这样的SQL查询结果不会被放入Query Cache中。
-
存储过程、触发器等基于数据库引擎类型工作的特定功能,如果其中使用了查询语句,这些查询语句的结果也不会放入Query Cache中。
-
复杂的SQL查询中,往往包含多个子句。这些子句的查询结果能够被放入Query Cache中。但是用于包含这些子句的外部查询结果却不能放入Query Cache。
以上提到的Query Cache功能局限性在每次MySQL版本升级的过程中,MySQL开发团队都逐渐进行了调整,所以这写功能性限制并不是什么太大的问题。例如以上说到的在存储过程中的SQL查询不会加入到Query Cache中,这个实际上就不是什么大问题,目前来看业务系统中业务逻辑处理部分还都是放在上层业务代码中来解决,使用复杂存储过来处理业务逻辑的情况不多见。
MySQL官方默认关闭Query Cache主要还是因为Query Cache的性能局限性:
-
“Waiting on query cache mutex”这种错误是典型的使用Query Cache不当所引起的错误。由于Query Cache设计的暴力清除策略,导致只要有数据表进行写操作,Query Cache中和这个数据表相关的所有结果都要失效的现象。所有需要从Query Cache中读取相关数据的客户端session就要等待数据清除完毕,所以就会出现以上错误提示。
-
如果这时query_cache_size设置得过大,反而会加剧这个问题的严重程度。因为过大的Query Cache区域意味着可能存储了和这个被写操作关联的数据表的更多查询结果集,也就需要更多时间去清除数据。
-
如果这张数据表又是写密集度非常高的数据表,那么这个问题会更加严重。因为Query Cache中相关数据会被频繁的擦除、重写。客户端session也会不停的进入锁定等待状态。
在实际业务应用中,笔者并不建议直接关闭Query Cache。而是建议在将query_cache_type参数设置为2(DEMAND)并分配不大的内存总空间(query_cache_size 设置为16MB足够了)的前提下,由业务层代码显式控制Query Cache的使用。
只有满足以下所有特点的SQL查询操作才建议显示开启Query Cache功能:写操作并不密集的数据表、读写操作比最好大于10:1(或者根据读者自己的业务特性规定的更大比值)。毕竟只有业务层才清楚哪些数据表的读写操作比大于10:1,并且写操作并不时常进行。而满足以上操作特性的数据表通常都是基础性码表:例如行政区域表、电话分区表、身份证分区表、车辆号牌表。
对于复杂的SQL查询、读写比不大的数据表、写操作频繁或者写操作并发特别大的数据表并不建议开启Query Cache功能。例如订单表、库存物品表、车辆承运表、评论信息表等业务写操作频繁的数据表。