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     | 
+-------------------------+-----------+

Відповіді:


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;

І запити, і посилання на таблицю недійсні з кешу, залишаючи нам один суміжний вільний блок, звільнену всю пам'ять кешу і весь вільний простір, консолідований в одному блоці.

+-------------------------+----------+
| 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? вільна пам'ять знижується на 512 * 3 між 1 та 4 використаними блоками та 512 * 2 між 4 та 6 використаними блоками.
аланд

1
@aland, це дуже хороший момент. Ні, я повинен був використовувати значення за замовчуванням 4096. Схоже, кеш запитів обрізає блок до найменшої можливої ​​потужності двох після його заповнення, залишаючи вільний простір в кінці, так що 7 / 8th цілі 4096 байтів, спочатку виділених, не мають нитки. Мені доведеться глибше заглибитися в це.
Майкл - sqlbot
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.