MySQL кэширование WordPress: пошаговая реализация и оптимизация

text

Введение: зачем кэшировать запросы MySQL в WordPress

WordPress активно использует базу MySQL для получения постов, метаданных, пользовательских данных и прочего. При высокой нагрузке повторяющиеся запросы становятся узким местом, замедляя отклик сервера. MySQL кэширование позволяет сохранять результаты тяжёлых запросов в отдельной таблице и повторно использовать их без обращения к оригинальным таблицам.

В статье мы разберём, как построить собственную систему кэширования, интегрировать её в ядро темы/плагина и обеспечить корректную инвалидацию.

1. Планирование структуры таблицы кэша

Перед тем как писать код, нужно спроектировать таблицу, где будут храниться кэш‑записи. Минимальный набор полей:

  • cache_key – уникальный идентификатор (строка, SHA1 от запроса).
  • cache_value – сериализованные данные (BLOB или LONGTEXT).
  • expires_at – дата и время истечения (DATETIME).
  • created_at – дата создания записи.

Создаём таблицу через функцию активации плагина:

function wpb_create_cache_table() { global $wpdb; $table_name = $wpdb->prefix. 'my_cache'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, cache_key VARCHAR(64) NOT NULL, cache_value LONGTEXT NOT NULL, expires_at DATETIME NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY cache_key_idx (cache_key) ) $charset_collate;"; require_once ABSPATH. 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } register_activation_hook( __FILE__, 'wpb_create_cache_table' ); 

2. Запись и чтение кэша

2.1 Генерация ключа кэша

Ключ должен однозначно описывать запрос. Чаще всего используют хеш от SQL‑строки и параметров.

function wpb_generate_cache_key( $sql, $params = [] ) { $raw = $sql. serialize( $params ); return sha1( $raw ); } 

2.2 Сохранение результата

После выполнения оригинального запроса сохраняем результат в кэш‑таблицу.

function wpb_set_cache( $key, $value, $ttl = 3600 ) { global $wpdb; $table = $wpdb->prefix. 'my_cache'; $expires = current_time( 'mysql', 1 ); // UTC $expires = date( 'Y-m-d H:i:s', strtotime( "+$ttl seconds", strtotime( $expires ) ) ); $wpdb->replace( $table, [ 'cache_key' => $key, 'cache_value' => maybe_serialize( $value ), 'expires_at' => $expires, ] ); } 

2.3 Чтение из кэша

Проверяем, не истёк ли срок жизни, и если запись актуальна – возвращаем её.

function wpb_get_cache( $key ) { global $wpdb; $table = $wpdb->prefix. 'my_cache'; $row = $wpdb->get_row( $wpdb->prepare( "SELECT cache_value, expires_at FROM $table WHERE cache_key = %s", $key ) ); if (! $row ) { return false; } if ( strtotime( $row->expires_at ) delete( $table, [ 'cache_key' => $key ] ); return false; } return maybe_unserialize( $row->cache_value ); } 

3. Инвалидация и очистка кэша

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

  1. Хук save_post, deleted_post и аналогичные – удаляем кэш, связанный с конкретным постом.
  2. Тайм‑базированное истечение (expires_at) – гарантирует, что устаревшие записи исчезнут автоматически.

Пример удаления кэша при обновлении поста:

function wpb_invalidate_post_cache( $post_id ) { $key = wpb_generate_cache_key( 'post_'. $post_id ); global $wpdb; $wpdb->delete( $wpdb->prefix. 'my_cache', [ 'cache_key' => $key ] ); } add_action( 'save_post', 'wpb_invalidate_post_cache' ); add_action( 'deleted_post', 'wpb_invalidate_post_cache' ); 

Для массовой очистки удобно добавить WP‑CLI команду:

if ( defined( 'WP_CLI' ) && WP_CLI ) { WP_CLI::add_command( 'mycache flush', function() { global $wpdb; $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}my_cache" ); WP_CLI::success( 'Кеш MySQL очищен.' ); } ); } 

4. Интеграция с запросами WordPress

Самый удобный способ – обернуть WP_Query или любые вызовы $wpdb->get_results() в функцию‑кешер.

function wpb_cached_query( $sql, $params = [], $ttl = 1800 ) { $key = wpb_generate_cache_key( $sql, $params ); $cached = wpb_get_cache( $key ); if ( $cached!== false ) { return $cached; // Возврат из кэша } global $wpdb; $prepared = $wpdb->prepare( $sql, $params ); $result = $wpdb->get_results( $prepared, OBJECT ); wpb_set_cache( $key, $result, $ttl ); return $result; } // Пример использования $posts = wpb_cached_query( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status = %s", ['publish'], 300 ); 

Такой подход работает для любых «тяжёлых» запросов: агрегаты, поиск по метаполям, запросы к пользовательским таблицам.

5. Лучшие практики и отладка

Чтобы система кэширования действительно ускоряла сайт, соблюдайте рекомендации:

  • Кешируйте только запросы, которые реально тормозят (> 50 мс). Инструменты Эвристическое кэширование WordPress помогут определить «тяжёлые» запросы.
  • Не храните в кэше данные, меняющиеся каждую секунду (например, счётчики просмотров).
  • Следите за размером таблицы: регулярно запускайте OPTIMIZE TABLE или используйте PHP 8.4 для ускорения сериализации.
  • Для критически важных запросов дополнительно используйте объектный кэш (Redis, Memcached) – MySQL кэш будет «резервным» слоем.

Отладка происходит через простой лог:

function wpb_log_cache( $message ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( '[MySQL Cache] '. $message ); } } 

Вставляйте wpb_log_cache() в ключевые места (генерация ключа, запись, чтение) и проверяйте debug.log.

Заключение

MySQL кэширование – мощный, но часто недооцененный способ ускорить WordPress без внешних сервисов. С правильно спроектированной таблицей, надёжной инвалидацией и интеграцией в запросы вы получаете ускорение в 3‑5 раз для сложных выборок. Не забывайте комбинировать эту технику с другими методами защиты и производительности, например, с защитой сессий WordPress или защитой от enumeration. При правильной реализации ваш сайт будет быстрее, стабильно и безопаснее.

❓ Часто задаваемые вопросы

Какой тип данных лучше использовать для поля cache_value?

Для небольших кэшей подойдёт LONGTEXT, а если планируется хранить большие сериализованные массивы – используйте BLOB с кодировкой binary.

Можно ли использовать эту систему совместно с объектным кэшем?

Да, MySQL кэш может выступать как резервный слой. Сначала проверяйте Redis/Memcached, а при их недоступности – обращайтесь к MySQL‑кэшу.

Как избежать конфликтов ключей между разными плагинами?

Добавляйте префикс к ключу, например, wpb_ + sha1(…); так каждый плагин генерирует уникальные идентификаторы.

Нужна ли индексация поля expires_at?

Индексация не обязательна, но если планируется часто удалять просроченные записи запросом WHERE expires_at < NOW(), индекс ускорит эту операцию.