Qcache_free_memory 未满但我得到了很多 Qcache_lowmem_prunes

Nif*_*fle 11 mysql performance cache query-cache

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

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

query_cache_size=512M
query_cache_limit=1M
Run Code Online (Sandbox Code Playgroud)

这是大约 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    | 
+-------------------------+-----------+
Run Code Online (Sandbox Code Playgroud)

这就是它的照顾方式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     | 
+-------------------------+-----------+
Run Code Online (Sandbox Code Playgroud)

Mic*_*bot 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        |
+-------------------------+----------+
Run Code Online (Sandbox Code Playgroud)

运行查询...

mysql> select * from junk where id = 2;
Run Code Online (Sandbox Code Playgroud)

总块增加了 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        |
+-------------------------+----------+
Run Code Online (Sandbox Code Playgroud)

运行相同的查询,但大小写不同...

mysql> SELECT * FROM junk where id = 2;
Run Code Online (Sandbox Code Playgroud)

此查询被单独缓存。总块仅增加了 2,因为我们已经为表分配了一个块。

+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67088560 |
| Qcache_inserts          | 2        |
| Qcache_queries_in_cache | 2        |
| Qcache_total_blocks     | 6        |
+-------------------------+----------+
Run Code Online (Sandbox Code Playgroud)

现在,我们更改表中的另一行。

mysql> update junk set things = 'items' where id = 1;
Run Code Online (Sandbox Code Playgroud)

查询和表引用都从缓存中失效,留下 1 个连续的空闲块,释放所有缓存内存,并将所有空闲空间合并到一个块中。

+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 67091120 |
| Qcache_queries_in_cache | 0        |
| Qcache_total_blocks     | 1        |
+-------------------------+----------+
Run Code Online (Sandbox Code Playgroud)

MySQL 不会在缓存中存储不确定的查询——例如SELECT NOW();或您明确告诉它不要缓存的任何查询。 SELECT SQL_NO_CACHE ...是告诉服务器不要将结果存储在缓存中的指令。当缓存对后续执行的响应速度极快时,它可用于对查询的真实执行时间进行基准测试。