Кеш запросов - очень хорошая функция, но не поддавайтесь искушению уделять ему слишком много внимания и не поддавайтесь искушению сделать его слишком большим. Понимание некоторых из его внутренних органов, вероятно, поможет в этом отношении.
Кеш запросов начинается как один большой непрерывный кусок доступной памяти. Затем "блоки" вырезаны из этого большого блока:
- каждый кэшированный запрос занимает блок
- его сопутствующий набор результатов занимает блок
- каждая таблица, на которую ссылается любой кэшированный запрос (независимо от того, сколько запросов, ссылающихся на эту таблицу, находится в кеше), также принимает блок, по одному на таблицу.
Размер блока является динамическим, но сервер выделяет минимум 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 ...
директива, указывающая серверу не сохранять результаты в кеше Это полезно для оценки истинного времени выполнения запроса, когда кеш дает обманчиво быстрый ответ на последующие выполнения.