MySQL кэширование WordPress: пошаговая реализация и оптимизация
Введение: зачем кэшировать запросы 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. Инвалидация и очистка кэша
Самый сложный момент – гарантировать, что кэш будет сброшен, когда меняются данные. Мы используем два подхода:
- Хук
save_post,deleted_postи аналогичные – удаляем кэш, связанный с конкретным постом. - Тайм‑базированное истечение (
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(), индекс ускорит эту операцию.