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     | 
+-------------------------+-----------+
Nifle
источник

Ответы:

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 ...директива, указывающая серверу не сохранять результаты в кеше Это полезно для оценки истинного времени выполнения запроса, когда кеш дает обманчиво быстрый ответ на последующие выполнения.

Майкл - sqlbot
источник
В ваших примерах правильно ли, что query_cache_min_res_unit = 512? объем свободной памяти уменьшается на 512 * 3 между 1 и 4 используемыми блоками и на 512 * 2 между 4 и 6 используемыми блоками.
Аланд
1
@land, это очень хороший момент. Нет, я должен был использовать значение по умолчанию 4096. Похоже, что кеш запросов обрезает блок до минимально возможной степени два после его заполнения, оставляя свободное место в конце, так что 7/8 от целые 4096 байт, изначально выделенные, не скручены. Я должен копнуть глубже в этом.
Майкл - sqlbot