web-dev-qa-db-ja.com

Qcache_free_memoryはまだいっぱいではありませんが、たくさんのQcache_lowmem_prunesを取得します

私は、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     | 
+-------------------------+-----------+
11
Nifle

クエリキャッシュは非常に優れた機能ですが、あまり注意を向けすぎたり、大きすぎたりしないようにしてください。その内部を理解することはおそらくその点で役立つでしょう。

クエリキャッシュは、利用可能なメモリの1つの大きな連続したチャンクとして始まります。次に、この大きなブロックから「ブロック」が切り分けられます。

  • キャッシュされた各クエリはブロックを取得します
  • そのコンパニオン結果セットはブロックを取ります
  • キャッシュされたクエリによって参照される各テーブルも(そのテーブルを参照するクエリがキャッシュ内にいくつあるかに関係なく)、テーブルごとに1つのブロックを取得します。

ブロックサイズは動的ですが、サーバーはブロックごとに最低_query_cache_min_res_unit_バイトを割り当て、通常のデフォルトは4096バイトです。

クエリ、それに付随する結果、およびテーブル参照がキャッシュから削除されると、基になるテーブルの変更によって無効になるか、プルーニングによって新しいクエリの余地ができるたびに、これらのブロックのサイズと同じくらいのサイズの新しい穴が残ります。通常、「フリーブロック」の数は増加しますが、2つ以上の連続するブロックが解放された場合、「フリーブロック」の数は1だけ増加し、「フリーブロック」は、解放されたブロックは、すでに解放されたブロックと隣接しています-その解放されたブロックのサイズは大きくなるだけです。クエリキャッシュ内の空きメモリの開いているブロックは、1つの空きブロックとしてカウントされます。

もちろん、_query_cache_min_res_unit_よりも小さい空きブロックはまったく使用されません。

したがって、クエリキャッシュは断片化します。サーバーが新しいクエリをキャッシュする必要があり、十分なサイズの空きブロックを配置できない場合(その説明は、基礎となるアルゴリズムが複雑であるため、一見単純そうです)、他のものを剪定する必要があります...それはあなたの_Qcache_lowmem_prunes_です。何をプルーニングするかを決定する「最近使用されていない」(LRU)アルゴリズムがあります。

サーバーがメモリを最適化しない理由を尋ねるのは理にかなっていますが、それは意味がありません。クエリキャッシュは、可能な場合に役立ちますが、戦略的なものではありません。不必要なメンテナンスタスクで処理時間(特にグローバルロックに費やされる時間)を費やしたくない場合。

キャッシュされた結果は常に変化しており、キャッシュの全体のポイントはパフォーマンスを向上させることになるため、サーバーがクエリキャッシュ内のメモリの再配置-デフラグ-に時間を費やすことは逆効果です。

グローバルロックは、過度に大きなクエリキャッシュを使用したくない非常に良い理由です...クエリが順番に待機してキャッシュが発生するかどうかを確認し、パフォーマンスが低下するため、サーバーは時間を費やしすぎます。

ただし、_qcache_free_blocks_は基本的に、空き領域の断片化を示すものです。これで、利用可能なメモリの多くの不連続なブロックがクエリキャッシュに存在します。新しいクエリをキャッシュに挿入するには、クエリ、その結果、および(場合によっては)テーブル参照を格納するのに十分な大きさの空き領域が必要です。ない場合は、何か他のことを実行する必要があります...これが表示されているものです。繰り返しになりますが、利用可能なスペースは必ずしも隣接している必要はありませんが(ソースコードを読むことでわかることから)、断片化が発生したときにすべての穴が埋められるわけではありません。

ただし、特定のワークロードでは、時間の経過とともに断片化が横ばいになる傾向があります。これは、通常、クエリキャッシュに予想されるほど長くとどまることはないためです。

これは、いくつかの点で、クエリキャッシュはその単純さの点で優れているためです。

キャッシュされたクエリによって参照されるテーブルのデータが変更されると、その変更がキャッシュされた結果に影響を与えない場合でも、そのテーブルに関連するすべてのクエリがキャッシュから削除されます。これは、テーブルが変更された場合でも当てはまりますが、ロールバックされたInnoDBトランザクションの場合のように、変更されません。そのテーブルを参照するクエリキャッシュエントリは既に削除されています。

また、クエリキャッシュは、着信クエリごとにチェックされますbeforeサーバーは実際にクエリを解析します。一致する唯一のものは、バイトごとにまったく同じであった別のクエリです。 _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        |
+-------------------------+----------+
_

ここで、テーブルのdifferent行を変更します。

_mysql> update junk set things = 'items' where id = 1;
_

クエリとテーブル参照の両方がキャッシュから無効化され、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 ..._は、サーバーに結果をキャッシュに保存しないように指示するディレクティブです。これは、以降の実行でキャッシュが一見して速い応答を示している場合に、クエリの実際の実行時間をベンチマークするのに役立ちます。

21