序
虽然 MySQL 8.0 已经移除了查询缓存了,但是相信很多公司还没那么快升级,所以还是有必要了解一下查询缓存的。本文通过一张内存结构图来向读者呈现查询缓存的一些细节点从而扩展出一系列问题和概念。相信通过这篇文章可以让你对查询缓存有一个更加深入的认识。
先贴一张查询流程图:
查询缓存内存结构示意图
MySQL 查询缓存保存查询返回的完整结果。当查询命中该缓存,MySQL 会检查一次用户权,然后立刻返回结果,跳过了解析、优化和执行阶段。
key 是什么
如上图,缓存存放在一个引用表中,通过一个哈希值引用,这个哈希值包括了如下因素,即查询本身、当前要查询的数据库、客户端协议的版本等一些其他可能会影响返回结果的信息。
所以当判断缓存是否命中时,直接使用 SQL 语句和客户端发送过来的其他原始信息。SQL 语句中任何字符上的不同都会导致缓存的不命中。
哪些数据不会被缓存
当查询结果集大小超过了 MySQL 能够缓存的最大查询结果 query_cache_limit 时不会被缓存。
当查询语句中有一些不确定的数据时,查询结果不会被缓存。如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、mysql 库中的系统表,或者包含列级别权限的表,都不会被缓存。
例子:
select * from employee where create_time > NOW();
select * from order where create_time > DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY);
还有一些其他的比如 CURRENT_USER、CONNECTION_ID()等等。
复制代码
元数据管理
元数据管理大概占用 40KB 的内存资源,用来指定哪些内存目前是可用的、哪些是已经用掉的、哪些用来存储数据表和查询结果之前的映射、哪些用来存储查询字符串和查询结果
数据块
数据块是变长的,每一个数据块中,存储了自己的类型、大小和存储的数据本身、还外加指向前一个和后一个数据块的指针。其中数据块的类型有:存储查询结果、存储查询和数据表的映射、存储查询文本,等等。
数据块的大小必须大于 query_cache_min_res_unit 。即使查询结果远远小于此,因为需要在传开始返回结果的时候就分配空间,二此时是无法预知查询结果到底多大的,所以 MySQL 无法为每一个查询结果精确分配大小恰好匹配的缓存空间。
现在,我们来缕一下查询缓存的写入过程:
假设使用这条 SQL :SELECT * FROM employee LIMIT 2;
1. 当需要缓存第一个查询结果时,先选择一个尽可能小的内存块,然后将结果存入其中。
2. 如果数据块全部用完,那么 MySQL 会申请一块新的尽可能小的数据块,因为还有一条查询结果需要存储。
3. 当查询完成时,如果申请的内存空间还有剩余,MySQL 会将其释放,并放入空闲内存部分。
复制代码
碎片
考虑一下这种情况:
假设查询结果非常小,服务器在并发地向不同的两个连接返回结果,返回完结后 MySQL 回收剩余数据块空间时会发现,回收的数据块小于 query_cache_min_res_unit ,所以不能够直接在后续的内存块分配使用。这个时候就会产生碎片。
另一种情况是当缓存失效时,也可能导致留下太小的数据块无法在后续缓存中使用。
可以通过 Qcache_free_blocks
参数来观察碎片,该参数反映了查询缓存中空闲块的多少
可以通过 FLUSH QUERY CACHE
命令来完成碎片整理,将所有的查询缓存重新排序,并将所有的空闲空间都聚集到查询缓存的一块区域上。不过该命令会访问所有的查询缓存,在这期间任何其他的连接都无法访问查询缓存,从而会导致服务器僵死一段时间
查询结果集
查询结果集包含多个查询结果,比如范围查询的返回结果。
所以示意图中,查询结果集包含了三个数据块,数据块之间使用前后指针互相关联。
一条 SQL 语句的查询缓存对应的是一个查询结果集,也就是一到多个数据块。
剩余空闲空间
Qcache_free_memory
当我们求 单个查询的平均缓存大小 时会用到: