MySQL 查询缓存

命中判定

MySQL通过一个hash值引用来获取语句对应的缓存数据集。

hash值受到查询语句,需要查询的数据库和客户端协议版本等的影响。

MySQL在进行缓存命中判定的时候,不会解析语句生成执行计划,而是直接生成hash值并进行命中判定,大大缩短了查询语句的运行时间。查询缓存前只进行一个判断语句是否为SEL开头的大小写不敏感检查。

MySQL对于不确定的数据不会进行缓存,准确的描述是 如果查询语句中包含任何不确定函数,则查询缓存中不可能找到结果

一般来说,自定义函数、存储函数、用户变量、临时表、系统表、包含列级权限的表、子查询和存储过程不能缓存。

绑定变量在5.1版本前不能缓存。

某些数据可以通过提前计算避开限制。

开启缓存的开销有:

  • 读查询前检查是否命中缓存
  • 可以被缓存的读查询进行缓存操作
  • 写操作时设置所有对应缓存失效

缓存的内存使用

由一个40KB左右大小的管理维护数据结构和多个变长数据块组成。

管理维护数据结构记录可用内存空间、已使用内存空间、存储数据表和查询结果映射空间以及存储查询字符串和查询结果空间等内容。

数据块存储了自己的类型、大小、数据和指向前一个和后一个数据块的指针。

数据块的类型:

  • 存储查询结果
  • 存储查询和数据表的映射
  • 存储查询文本

等。

服务器启动时,先初始化查询需要的内存,生成一个完整的空闲块作为内存池,大小是配置的查询缓存大小减去维护元数据的数据结构消耗的空间。

当由查询结果需要缓存时,MySQL从空间块中申请一个大于query_cache_min_res_unit的数据块,同时一般保证这个内存块尽可能小(也可能选择大的)然后填入数据。
若数据块用完,则MySQL会依照上述原则申请一块新的数据块。

若完成查询后内存空间还有剩余,则空余的内存会被释放,并放回空闲内存部分。

缓存查询过程

过程如图

MySQL的查询缓存在写入时可能产生碎片问题。常见于并发。如图

碎片问题

缓存适用情况

只有缓存带来的资源节约大于其本身的资源消耗时才会带来性能提升,取决于具体的服务器压力模型。

理论上可以通过关闭和开启查询缓存并对比系统效率来决定是否开启缓存。

网络消耗为系统主要瓶颈时缓存的意义不大。

复杂的SELECT语句都可以使用查询缓存。如果某些关键的查询能够得到优化,那么降低其他的查询速度也是有意义的,可以通过SQL_CACHE选项进行控制来得到进一步优化。

UPDATE、DELETE和INSERT操作尽可能少。

查询缓存命中率计算公式: Qcache_hits/(Qcache_hits+Com_select)

缓存未命中的可能原因有:

  • 无法缓存(语句结果不确定/结果过大)
  • 从未处理的语句
  • 缓存失效

缓存失效的主要原因有:

  • 缓存碎片
  • 内存不足
  • 数据修改

可以通过Com_*查看数据修改的情况(Com_update,Com_delete等)

通过Qcache_lowmem_prunes来查看内存不足情况。这个参数代表内存不足时删除缓存的数量。

“命中和写入”的比率可以更好的反应查询缓存的效率,即 Qcache_hits/Qcache_inserts
根据经验,通常3:1以上缓存有效
推荐达到10:1。

配置和维护缓存

相关的参数有:

  • query_cache_type:是否打开查询缓存 OFF/ON/DEMAND
  • query_cache_size:缓存总内存空间 1024整数倍
  • query_cache_min_res_unit:分配内存块时的最小单位
  • query_cache_limit:能够缓存的最大查询结果
    • 注意:数据开始生成时就开始缓存,因此当结果返回完毕后MySQL才知道结果是否超出限制,若超出则进行删除缓存操作,严重拖慢性能
  • query_cache_wlock_invalidate:表有锁时是否返回结果

可以通过设置合理的query_cache_min_res_unit减少碎片。

可以通过单个查询的平均缓存大小 (query_cache_size-Qcache_free_memory)/Qcache_queries_in_cache 作为参考。需要考虑结果不均匀的情况。

通过参数Qcache_free_blocks来观察碎片,若Qcache_free_blocks大小接近Qcache_total_blocks/2则说明有严重的碎片问题。

可以使用 FLUSH QUERY CACHE完成碎片整理。

RESET QUERY CACHE是缓存清空,加以区别。

观察Qcache_lowmem_prunes值,若增长过快,则:

  • 很多空闲块:碎片
  • 少空闲块:缓存过小

也有可能是可能缓存不适合工作于目前的应用系统。

InnoDB与查询缓存

4.0版本前,事务处理中查询缓存是禁用的,4.1开始,InnoDB控制事务是否可以使用查询缓存。

事务是否可以访问缓存取决于当前事务ID和数据表上是否有锁。

每一个InnoDB内存数据字典中都保存一个事务ID号,若当前事务小于该事务ID则无法访问查询缓存。

如果表上有锁,则关于该表的任何查询无法访问缓存。

恒成立的事实:

  • 所有大于该表计数器的事务才能使用缓存
  • 对表加锁的事务ID不会被更新为该表的计数器,计数器会被更新成系统事务ID。该事务后续更新操作也无法读取和修改缓存。

通用查询缓存优化

使用多个小表代替一个大表。

采用批量写入。

控制缓存大小,放置服务器僵死,必要时禁用缓存。

通过SQL_CACHE和SQL_NO_CACHE控制单条查询语句缓存。也可以修改会话级的变量query_cache_type。

写密集型应用直接禁用缓存。

读密集型应用根据评估判断是否禁用缓存。

发表评论

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

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据