高级特性
查询缓存
如何配置和维护查询缓存
一旦理解查询缓存工作的原理,配置起来就很容了。它也只有少数的参数可供配置。如下所示:
- 1.query_cache_type
是否打开查询缓存。可以设置成OFF、ON或DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放入查询缓存。这个变量可以是会话级别的也可以是全局级别的 - 2.query_cache_size
查询缓存使用的总内存空间,单位是字节。这个值必须是1024的整数倍,否则MySQL实际分配的数据会和你指定的略有不同 - 3.query_cache_min_res_unit
在查询缓存中分配内存块时的最小单位。 - 4.query_cache_limit
MySQL能够缓存的最大查询结果。如果查询结果大于这个值,则不会被缓存。因为查询缓存在数据生成的时候就开始尝试缓存数据,所以只有当结果全部返回后,MySQL才直到查询结果是否超出限制。如果超出,MySQL则增加状态值Qcache_not_cached,并将结果从查询缓存张删除。如果你事先直到有很多这样的情况发生,那么建议在查询语句中加入SQL_NO_CACHE来避免查询缓存带来的额外消耗 - 5.query_cache_wlock_invalidate
如果某个数据表被其他的连接锁住,是否仍然从查询缓存中返回结果。这个参数默认是OFF,这可能在一定程序上会改变服务器的行为,因为这使得数据库可能返回其他线程锁住的数据,将参数设置成ON,则不会从缓存中读取这类数据,但是这可能会增加锁等待。对于绝大数应用来说无须注意这个细节,所以默认设置通常是没有问题的。
配置查询缓存通常很简单,但是如果想直到修改这些参数会带来哪些改变,则是一项很复杂的工作。
减少碎片
没什么办法能够完全避免碎片,但是选择合适的query_cache_min_res_unit可以帮你减少由碎片导致的内存空间浪费。设置合适的值可以平衡每个数据块的大小和每次存储结果时内存块申请的次数。这个值太小,则浪费的空间更少,但是会导致更频繁的内存块申请操作;如果这个值设置得太大,那么碎片会很多。调整合适得值其实是在平衡内存浪费和CPU消耗。这个参数的最合适的大小和应用程序的查询结果的平均大小直接相关。可以通过内存实际消耗(query_cache_szie_Qcache_free_memory)除以Qcache_queries_in_cache计算单个查询的平均缓存大小。如果你的应用程序的查询结果很不均匀,有的结果很大,有的结果很小,那么碎片和反复的的内存块分配可能无法避免。如果你发现缓存一个非常大的结果并没有什么意义(通常确实是这样),那么你可以通过参数query_cache_limit限制可以缓存的最大查询结果,借此大大减少大的查询结果的缓存,最终减少内存碎片的发生。还可以通过参数Qcache_free_blocks来观察碎片。参数Qcache_free_blocks反应了查询缓存中空闲块的多少,
在图中的配置我们看到,有两个空闲块。最糟糕的情况是,任何两个存储结果的数据块之间都有一个非常小的空闲块。所以如果Qcache_free_blocks大小恰好达到Qcache_total_blocks/2,那么查询缓存就有严重的碎片问题。而如果你还有很多空闲块,而状态值Qcache_lowmem_prunes还不断地增加,则说ing由于碎片导致了过早地在删除查询缓存结果。可以使用命令FLUSH QUERY CACHE完成碎片整理。这个命令会将所有的查询缓存重新排序,并将所有的空闲空间都聚集到查询缓存的一块区域上。不过需要注意,这个命令并不会将查询缓存清空,清空缓存由命令RESET QUERY CACHE完成。FLUSH QUERY CACHE会访问所有的查询缓存,在这期间任何其他的连接都无法访问查询缓存,从而会导致服务器僵死一段时间,使用这个命令的时候需要特别小心这点。另外,根据经验,建议保持查询缓存空间足够小,以便在维护时可以将服务器僵死控制在非常短的时间内。
提高查询缓存的使用率
如果查询缓存不再有碎片问题,但你仍然发现命中率很低,还可能是查询缓存的内存空间太小导致的。如果MySQL无法为一个新的查询缓存结果的时候,则会选择删除某个老的缓存结果。当由于这个原因导致删除老的结果时,会增加状态值Qcache_lowmem_prunes.如果这个值增加得很快,那么可能是由下面两个原因导致的:
- 1.如果还有很多空闲块,那么碎片可能是罪魁祸首
- 2.如果这是没有什么空闲块了,就说明在这个系统压力下,你分配的查询缓存空间不够大。你可以通过检查状态值Qcache_free_memory来查看还有多少没有使用的内存。
如果空闲块很多,碎片很少,也没什么由于内存导致的缓存失效,但是命中率仍然很低,那么很可能说ing,在你的系统压力下,查询缓存并没有什么好处。一定是什么原因导致查询缓存无法为系统服务,例如有大量的更新或者查询语句本身都不能被缓存。如果在观察命中率时,仍然无法确定查询缓存是否给系统带来了好处,那么可以通过禁用它,然后观察系统的性能,再重新打开它,观察性能变化,据此来判断查询缓存是否给系统带来了好处。可以通过将query_cache_size设置成0,来关闭查询缓存。(改变query_cache_type的全局值并不会影响已经打开的连接,也不会将查询缓存的内存释放给系统)你还可以系统测试来验证,不过一般都很难精确地模拟实际情况。如图展示了一个用来分析配置查询缓存的流程图
InnoDB和查询缓存
因为InnoDB有自己的MVCC机制,所以相比其他存储引擎,InnoDB和查询缓存的交互要更加复杂。MySQL4.0版本中,在事务处理中查询缓存是被禁用的,从4.1和更新的InnoDB版本开始,InnoDB会控制在一个事务中是否可以使用查询缓存,InnoDB会同时控制对查询缓存的读(从缓存中获取查询结果)和写操作(向查询缓存写入结果)。事务是否可以访问查询缓存取决于当前事务ID,以及对应的数据表上是否有锁。每一个InnoDB表的内存数据字典都保存了一个事务ID号,如果当前事务ID小于该事务ID,则无法访问查询缓存。如果表上有任何的锁,那么对这个表的任何查询语句都是无法被缓存的。例如,某个事务执行了SELECT FOR UPDATE语句,那么在这个锁释放之前,任何其他的事务都无法从查询缓存中读取与这个表相关的缓存结果。当事务提交时,InnoDB持有锁,并使用当前的一个系统事务ID更新当前表的计数器。锁一定程度上说明事务需要对表进行修改操作,当然有可能事务获得锁,却不进行任何更新操作,但是如果想要更新任何表的内容,获得相应锁则是前提条件。InnoDB将每个表的计数器设置成某个事务ID,而这个事务ID就代表了当前存在的且修改了该表的最大的事务ID.那么下面的一些事实也就成立:
- 1.所有大于该表计数器的事务才可以使用查询缓存。例如当前系统系统的事务ID是5,且事务获取了该表的某些记录的锁,然后进行事务提交操作,那么事务1至4,都不应该再读取或者向查询缓存写入任何相关的数据
- 2.该表的计数器并不是直接更新为对该表进行加锁的事务ID,而是被更新成一个系统事务ID,而是被更新成一个系统事务ID.搜易,会发现该事务自身后续的更新操作也无法读取和修改查询缓存。
查询缓存存储、检索和失效操作都是在MySQL层面完成,InnoDB无法绕过或者延迟这个行为。但InnoDB可以在事务中显式地告诉MySQL何时应该让讴歌表地查询缓存都失效。在有外键限制地时候这是必须地,例如某个SQL语句有ON DELEETE CASCADE那么相关关联表地查询缓存也是要一起失效的。
原则上,在InnoDB的MVCC架构下,当某些修改不影响其他事务读取一致的数据时,是可以使用查询缓存的。但是这样实现起来会非常复杂,InnoDB做了一个简化,让所有有加锁操作的事务都不适用任何查询缓存,这个限制其实并不是必须的。
通用查询缓存优化
库表结构的涉及、查询语句、应用程序涉及都可能会影响到查询缓存的效率。除了前面的介绍之外,还有一些要点需要注意:
- 1.用多个淆表代替一个大表对查询缓存有好处。这个涉及将会使得失效策略能够在一个更合适的粒度上进行。当然,不要让这个原则过分影响你的涉及,毕竟其他的一些有时可能很容易就弥补了这个问题
- 2.批量写入时只需要做一次缓存失效,所以相比单条写入效率更好(另外需要注意,不要同时做延迟写和批量写,否则可能会因为失效导致服务器僵死较长时间)
- 3.因为缓存空间太大,在过期操作的时候可能会导致服务器僵死。一个简单的解决办法就是控制缓存空间的大小(query_cache_size)或者直接禁用查询缓存
- 4.无法在数据库或者表级别控制查询缓存,但是可以通过SQL_CACHE和SQL_NO_CACHE来控制某个SELECT语句是否需要进行缓存。你还可以通过修改会话级别的变量query_cache_type来控制查询缓存
- 5.对于写密集型的应用来说,直接禁用查询缓存可能会提高系统的性能。关闭查询缓存可以移除所有相关的消耗。例如将query_cache_size设置成0,那么至少这部分就不再消耗任何内存了
- 6.因为对互斥信号量的竞争,有时直接关闭查询缓存对读密集型的应用也会有好处。如果你希望提高系统的并发,那么最好做一个相关的测试,对比打开和关闭查询缓存时候的性能差异。
如果不像所有的查询都进入查询缓存,但是有希望某些查询走查询缓存,那么可以将query_cache_type设置成DEMAND,然后在希望缓存的查询中加上SQL_CACHE,这虽然需要在查询中加入一些额外的语法,但是可以让你非常自由地控制哪些查询需要被缓存。相反,如果希望缓存多数查询,而少数查询又不希望缓存,那么你可以使用关键字SQL_NO_CACHE
查询缓存的替代方案
MySQL查询缓存工作的原则是:执行查询最快的方式就是不执行,但是查询仍然需要发送到服务器,服务器也还需要做一点点工作。如果对于某些查询完全不需要与服务器通信效果会如何呢?这是客户端的缓存可以很大程度上帮你分担MySQL服务器的压力。