查询缓存是一个非常不错的功能,但是不要试图过多地关注它,也不要试图使其太大。在这方面,了解它的某些内部结构可能会有所帮助。
查询缓存从可用内存的一大块连续块开始。然后从这个大块中雕刻出“块”:
- 每个缓存的查询占用一个块
- 它的伴随结果集占用了一个街区
- 任何高速缓存的查询所引用的每个表(无论在高速缓存中有多少个引用该表的查询)都占用一个块,每个表一个。
块大小是动态的,但是服务器为query_cache_min_res_unit
每个块分配最少的字节,典型的默认值为4096字节。
每当查询,它们的伴随结果和表引用从缓存中删除时,要么由于基础表的更改而无效,要么通过修剪为较新的查询腾出空间,从而留下了新漏洞,无论这些块的大小如何,并且通常,“空闲块”的数量会增加...尽管如果释放了两个或多个连续块,则“空闲块”的数量只会增加1,而如果新的“释放的块与已经释放的块是连续的-该空闲块的大小会变得更大。查询缓存中任何打开的空闲内存块都计为1个空闲块。
当然,小于一个的免费块query_cache_min_res_unit
根本不会使用。
因此,查询缓存片段。如果服务器要缓存一个新查询,并且无法安排足够大的空闲块(描述很简单,因为底层算法很复杂),则必须修剪其他内容……就是您的Qcache_lowmem_prunes
。有一个“最近最少使用”(LRU)算法,该算法决定要删除的内容。
询问服务器为什么不对内存进行碎片整理是明智的……但这没有任何意义。查询缓存会在可能的情况下提供帮助,但它根本不是战略性的。您不想花费不必要的维护任务来处理时间(尤其是在全局锁上花费的时间)。
对于服务器来说,花时间重新安排(对磁盘碎片整理)查询缓存中的内存会适得其反,因为缓存的结果在不断变化,而缓存的整个目的就是提高性能。
全局锁定是您不希望使用过大的查询缓存的一个很好的理由...服务器将在那里花费太多时间,因为查询等待轮流查看它们是否恰好被缓存,从而降低性能。
但是从qcache_free_blocks
本质上讲,它是自由空间碎片的指示。现在,查询缓存中存在许多不连续的可用内存块。为了将新查询插入到缓存中,必须有足够大的可用空间块来包含查询,其结果以及(有时)其表引用。如果没有的话,那么还有其他的事情要做……这就是您所看到的。再次注意,可用空间并不一定总是必须连续(根据我从阅读源代码可以看出的),但是当出现碎片时,并不是每个孔都会被填充。
但是对于给定的工作负载,碎片化趋势往往会随着时间的推移而趋于平稳,因为通常没有任何东西会像您期望的那样停留在查询缓存中。
这是因为在某些方面,查询缓存的简单性十分出色。
每当缓存查询所引用的表中的数据发生更改时,涉及该表的所有查询都会从缓存中删除-即使更改不会影响缓存的结果。如果表发生了变化,但是没有发生变化,甚至是事实,就像回滚的InnoDB事务一样。引用该表的查询缓存条目已被清除。
另外,在服务器实际解析查询之前,将针对每个传入查询检查查询缓存。唯一要匹配的是另一个完全相同的查询(逐字节)。 SELECT * FROM my_table
并且select * from my_table
不是逐字节相同的,因此查询缓存不会意识到它们是同一查询。
FLUSH QUERY CACHE
不清空查询缓存。它对查询缓存进行碎片整理,这就是为什么Qcache_free_blocks
变为“ 1”的原因。所有可用空间已合并。
RESET QUERY CACHE
实际上刷新(清除掉所有内容)查询缓存。
FLUSH STATUS
清除计数器,但这不是您要例行执行的操作,因为这会将的大多数状态变量清零SHOW STATUS
。
这里有一些快速演示。
基线:
mysql> show status like '%qcache%';
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 67091120 |
| Qcache_hits | 0 |
| Qcache_inserts | 0 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 1 |
| Qcache_queries_in_cache | 0 |
| Qcache_total_blocks | 1 |
+-------------------------+----------+
运行查询...
mysql> select * from junk where id = 2;
总块增加了3,插入增加了1,缓存中的查询为1。
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 67089584 |
| Qcache_inserts | 1 |
| Qcache_queries_in_cache | 1 |
| Qcache_total_blocks | 4 |
+-------------------------+----------+
运行相同的查询,但大小写不同...
mysql> SELECT * FROM junk where id = 2;
该查询是单独缓存的。块总数仅增加了2,因为我们已经为该表分配了一个块。
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 67088560 |
| Qcache_inserts | 2 |
| Qcache_queries_in_cache | 2 |
| Qcache_total_blocks | 6 |
+-------------------------+----------+
现在,我们在表中更改另一行。
mysql> update junk set things = 'items' where id = 1;
查询和表引用都从高速缓存中无效,从而为我们留出了1个连续的空闲块,释放了所有缓存内存,并将所有可用空间合并到一个块中。
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 1 |
| Qcache_free_memory | 67091120 |
| Qcache_queries_in_cache | 0 |
| Qcache_total_blocks | 1 |
+-------------------------+----------+
MySQL不会将不确定的查询存储在不确定的缓存中,例如SELECT NOW();
或您明确告诉其不要缓存的任何查询。 SELECT SQL_NO_CACHE ...
是指示服务器不要将结果存储在缓存中的指令。当缓存在后续执行中为您提供看似快速的响应时,对于基准查询的真实执行时间很有用。