Qcache_free_memory还不满,我得到了很多Qcache_lowmem_prunes


11

我刚刚开始涉足CMS的查询缓存。

谁能告诉我(或至少提供一个良好的猜测)为什么我得到了很多Qcache_lowmem_prunes时候有一半以上Qcache_free_memory是免费的吗?

query_cache_size=512M
query_cache_limit=1M

这是大约12小时后的样子

show status like '%qcach%';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Qcache_free_blocks      | 10338     | 
| Qcache_free_memory      | 297348320 | 
| Qcache_hits             | 10254104  | 
| Qcache_inserts          | 6072945   | 
| Qcache_lowmem_prunes    | 725279    | 
| Qcache_not_cached       | 2237603   | 
| Qcache_queries_in_cache | 48119     | 
| Qcache_total_blocks     | 111346    | 
+-------------------------+-----------+

这是它的照料flush query cache;

show status like '%qcach%';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| Qcache_free_blocks      | 1         | 
| Qcache_free_memory      | 443559256 | 
| Qcache_hits             | 10307015  | 
| Qcache_inserts          | 6115890   | 
| Qcache_lowmem_prunes    | 725279    | 
| Qcache_not_cached       | 2249405   | 
| Qcache_queries_in_cache | 26455     | 
| Qcache_total_blocks     | 54490     | 
+-------------------------+-----------+

Answers:


21

查询缓存是一个非常不错的功能,但是不要试图过多地关注它,也不要试图使其太大。在这方面,了解它的某些内部结构可能会有所帮助。

查询缓存从可用内存的一大块连续块开始。然后从这个大块中雕刻出“块”:

  • 每个缓存的查询占用一个块
  • 它的伴随结果集占用了一个街区
  • 任何高速缓存的查询所引用的每个表(无论在高速缓存中有多少个引用该表的查询)都占用一个块,每个表一个。

块大小是动态的,但是服务器为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 ...是指示服务器不要将结果存储在缓存中的指令。当缓存在后续执行中为您提供看似快速的响应时,对于基准查询的真实执行时间很有用。


在您的示例中,query_cache_min_res_unit = 512是否正确?1至4个已用块之间的可用内存减少512 * 3,4至6个已用块之间的可用内存减少512 * 2。
2014年

1
@aland很好。否,我应该一直使用默认值4096。看起来好像查询缓存在填充块之后将块缩减为最小可能的2的幂,在末尾保留了可用空间,因此块的7/8最初分配的整个4096个字节不会搁浅。我将不得不对此进行更深入的研究。
Michael-sqlbot 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.