Кэширование MySQL

Статья для новичков. В MySQL есть встроенное кэширование запросов. Подробнее можно почитать на Хабре. Тут я скажу о главном плюсе и главном минусе такого кэширования и приведу пример своего сервака.

Плюсы

Во-первых, кэширование запросов существенно ускоряет генерацию страницы. Существенно потому, что SQL-запросы почти всегда являются главным тормозом.

Во-вторых, встроенное кэширование гораздо проще в реализации, чем кэширование на уровне приложения.
Указав в конфиге "query_cache_type=1" мы включаем кэширование всех запросов. При этом нам не нужно запариваться с инвалидацией кэша — база сама знает когда таблица изменилась и кэш сбрасывается автоматически. Приведу пример.

// это кэширование с помощью MySQL
// тут даже менять в коде ничего не нужно (делаем запрос как обычно)
$tags = $DB->select('SELECT * FROM tags');

// а вот то же самое кэширование на файлах
$filemtime = filemtime($_SERVER['DOCUMENT_ROOT'].'/cache/tags.txt');
if ((time() - $filemtime) > 60*60*24) {
    $tags = $DB->select('SELECT * FROM tags');
    $fp = fopen($_SERVER['DOCUMENT_ROOT'].'/cache/tags.txt', 'w');
    flock($fp, LOCK_EX);
    fwrite($fp, serialize($tags));
    flock($fp, LOCK_UN);
    fclose($fp);
}
else {
    $tags = unserialize(file_get_contents($_SERVER['DOCUMENT_ROOT'].'/cache/tags.txt'));
}

// причем при добавлении/изменении тегов,
// нам нужно дописать код по инвалидации кэша.
if ($tag_change) {
    $fp = fopen($_SERVER['DOCUMENT_ROOT'].'/cache/tags.txt','a');
    ftruncate($fp, 0);
}

Если вместо файлов мы используем какой-то другой вид кэширования на уровне приложения, например Memcache, то это ничего не меняет — нам всё равно нужно бегать по всему коду и вставлять if-else.

Короче говоря, самый большой плюс кэширования MySQL — прописал одну сточку в конфиге query_cache_type=1 и тут же нагрузка на базу упала. Даже код не нужно трогать.

Минусы

Самый большой минус в том, что инвалидация кэша происходит по слишком простому алгоритму: база смотрит изменилась ли таблица, из которой мы хотим сделать запрос. На примере это выглядит так. Есть 100 000 пользователей, которых мы закэшировали. Пришел пользователь и поменял что-то в профиле. Казалось бы, можно сбросить 1 кэш (только для того пользователя, который что-то изменил). Но MySQL сбрасывает весь кэш пользователей, потому что он не знает у какого пользователя изменилась информация — он видит только дату изменения таблицы.

Таким образом, мы очень быстро забьем оперативную память, которой у нас и так мало, а кэширование будет малоэффективным.

Но если у вас данные меняются редко, то кэш MySQL — хорошее решение. Правда, если на серваке работает несколько сайтов, то кэширование включается для всех сразу. Поэтому, если памяти мало, то можно вручную указать какие запросы кэшировать, а какие нет. Делается это так

  1. Прописываем в конфиге query_cache_type=2
  2. В запросах, которые хотим закэшировать пишем директиву SQL_CACHE:
    'SELECT SQL_CACHE * FROM tags'

Как видно, в этом случае нам всё-таки придется пробежаться по коду и понаставлять SQL_CACHE в запросы.

Многие таблицы (например, таблица городов, таблица категорий и другие справочники) не меняются годами, поэтому кэш там будет лежать вечно.

Многие сайты обновляются 2-3 раза в день, а кол-во просмотров переваливает за тысячи — в этом случае кэш тоже будет весьма эффективным.

Теперь смотрим статистику (это у меня за 5 дней)

Variable_name Value
Qcache_free_blocks 9
Qcache_free_memory 12074192
Qcache_hits 44614
Qcache_inserts 1396
Qcache_lowmem_prunes 0
Qcache_queries_in_cache 1182
Qcache_total_blocks 2388

Как видно, 96% запросов берется из кэша.

В довершение статьи мой конфиг

query_cache_type  = 2
query_cache_limit = 1M
query_cache_size  = 16M
#1

> Статья для новичков.
Мой гордость не позволила читать дальше:) Наверное хорошая статья, надеюсь не забыл упомянуть о случаях когда квери кеш лучше отключать

Жора, 29.10.2011 - 20:49
#2

Здравствуйте! Спасибо за статью.
Подскажите, как вы определили - "Как видно, 96% запросов берется из кэша."???

У меня вот такие данные:
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Qcache_free_blocks | 9 |
| Qcache_free_memory | 49016624 |
| Qcache_hits | 23509 |
| Qcache_inserts | 24011 |
| Qcache_lowmem_prunes | 0 |
| Qcache_not_cached | 863 |
| Qcache_queries_in_cache | 16557 |
| Qcache_total_blocks | 33157 |
+-------------------------+----------+

Как мне рассчитать эффективно или нет использование?

Настройка my.cnf

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=****
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

key_buffer_size = 16M
key_cache_division_limit = 70
tmp_table_size = 16M
max_heap_table_size = 16M

query_cache_size = 64M
query_cache_type = 1
query_cache_limit = 2M

long_query_time = 0.5
slow_query_log_file = /var/log/mysql-slow.log
slow_query_log = 0
skip-innodb
thread_cache_size = 4
table_cache = 128

Игорь, 16.12.2013 - 17:32
#3

Qcache_hits 44614
Qcache_inserts 1396

(Qcache_inserts/Qcache_hits)*100%=3.1% даже так.

Qcache_inserts - происходит когда в кэше нет запроса или данные в кэше уже устарели, т.е. мы лезим в базу на диск, получаем данные и сохраняем в кэш.

Qcache_hits - когда запрос оказался в кэше и мы его взяли из памяти.

Чем меньше соотношение (Qcache_inserts/Qcache_hits) - тем лучше. Тем меньше данных берется с диска и тем больше данных берется из кэша.

В твоем случае эффективность 50%. При этом памяти хватает. Видимо ты меняешь данные в таблице и все кэши для этой таблицы сбрасываются.

Например, таблица постов post.
В кэше куча данных вида
select * from post where id=1
select * from post where id=2
select * from post where id=3
select * from post where id=n

Но как только ты вставил новый пост в базу или обновил у любого поста что-то, то сбрасываются все вышеперечисленные кэши.

Если это так, то нужно ставить индексы в базе - чтобы с диска быстрее читалось. Ну и переходить на более эффективный вид кэша типа Memcached http://ekimoff.ru/394/ - но здесь уже придется код править и думать над связями.

Кстати, у тебя long_query_time = 0.5 - не получится. mysql умеет только логировать запросы более 1 секунды. т.е этот параметр должен быть больше 1.

admin, 17.12.2013 - 11:05
Оставить комментарий