Одной из наиболее распространенных причин медленной работы сайта WordPress не обязательно является ваш хост, CDN или даже размеры изображений — это ваша база данных. В частности, объем и неэффективность SQL-запросов, генерируемых вашей темой и плагинами. Каждая загрузка страницы может привести к десяткам или, в плохо оптимизированных случаях, сотням запросов к базе данных. В сочетании с виджетами, шорткодами, боковыми панелями и элементами редактора блоков эти запросы могут вызвать серьезные узкие места в производительности, которые остаются незамеченными, но все же негативно влияют на ваши основные веб-показатели (CWV) и общий пользовательский опыт.
Многие разработчики сосредотачиваются на кэшировании и доставке CDN, чтобы замаскировать проблемы с производительностью, но они решают лишь часть проблемы. Если базовая загрузка запросов неэффективна, вы по сути рисуете ржавчину. Первый шаг к настоящей оптимизации скорости — понимание того, что происходит под капотом.
Содержание
СОХРАНЕННЫЕ ЗАПРОСЫ
WordPress предлагает встроенную константу, SAVEQUERIESкоторый записывает каждый запрос к базе данных, выполненный во время запроса, включая продолжительность каждого запроса. При осторожном использовании это бесценный диагностический инструмент для выявления неэффективности шаблонов тем, конструкторов страниц и интеграции плагинов.
На хорошо оптимизированном сайте WordPress типичный шаблон страницы должен генерировать от 20 до 50 запросов. Минимальная домашняя страница или легкий макет одной публикации может приближаться к 20, в то время как более сложный архив или страница продукта WooCommerce может естественным образом вырасти до 50 из-за поиска по таксономии и метаданных.
Все, что выше этого значения, особенно в сотнях, указывает на неэффективность. Каждый запрос требует дополнительных затрат, и даже микросекунда быстро задерживает соединение под нагрузкой. Большое количество запросов обычно указывает на избыточные циклы, некэшированные параметры или плагины, которые делают свои собственные вызовы базы данных независимыми от основного запроса WordPress. Минимальный обмолот – это не произвольные ограничения, а гарантия того, что каждый запрос имеет причину для существования и что ваши шаблоны работают предсказуемо на всех страницах.
Включение SAVEQUERIES на производственной площадке без какого-либо контроля производительности может оказаться непосильной задачей. В нижней части HTML-файла могут быть тысячи запросов, что затрудняет определение точного шаблона или компонента, который замедляется. Решение состоит в том, чтобы автоматизировать ведение журнала, чтобы каждый тип шаблона — главная страница, страница, отдельная запись, архив — создавал свой собственный файл журнала. Он обеспечивает четкое и сегментированное представление о том, где выполняются самые дорогостоящие запросы.
Как зарегистрировать запросы шаблона WordPress
Вот простой, но эффективный подход к эффективному ведению журнала запросов WordPress. Разместив следующий код в файле function.php вашей темы или в специальном плагине, вы можете автоматически собирать и сохранять журналы запросов всякий раз, когда вы входите в систему как администратор и SAVEQUERIES включен.
// If SAVEQUERIES is enabled, print the queries in the footer in an HTML tag
function log_queries_to_file() {
global $wpdb;
// Check conditions: SAVEQUERIES enabled and logged-in admin
if (defined('SAVEQUERIES') && SAVEQUERIES && is_user_logged_in() && current_user_can('manage_options')) {
// Define the log directory (site root/queries/)
$log_dir = ABSPATH . 'queries/';
// Create the directory if it doesn't exist (with recursive creation and proper permissions)
if (!file_exists($log_dir)) {
mkdir($log_dir, 0755, true);
}
// Skip if directory isn't writable
if (!is_writable($log_dir)) {
return; // Or add error logging if desired
}
// Determine the filename based on page type
$filename="";
if (is_front_page()) {
$filename="queries-frontpage.log";
} elseif (is_page()) {
$template_slug = get_page_template_slug(get_the_ID());
if (empty($template_slug)) {
$template_slug = 'default';
} else {
// Sanitize template slug: basename without .php
$template_slug = basename($template_slug, '.php');
}
$filename="queries-page-" . sanitize_title($template_slug) . '.log';
} elseif (is_archive()) {
$post_type="post"; // Default for standard archives
if (is_post_type_archive()) {
$post_type = get_query_var('post_type');
if (is_array($post_type)) {
$post_type = reset($post_type); // Handle multi-type, take first
}
} elseif (is_category() || is_tag() || is_tax()) {
// For taxonomies, get the post type from queried object if custom
$queried = get_queried_object();
if ($queried && isset($queried->taxonomy)) {
$tax = get_taxonomy($queried->taxonomy);
$post_type = is_array($tax->object_type) ? reset($tax->object_type) : $tax->object_type;
}
} elseif (is_author() || is_date()) {
$post_type="post";
}
$filename="queries-archive-" . sanitize_title($post_type) . '.log';
} elseif (is_singular()) { // Covers single posts, pages, CPTs
$post_type = get_post_type(get_the_ID());
$filename="queries-single-" . sanitize_title($post_type) . '.log';
}
// If no matching type, skip or use a fallback like 'queries-other.log'
if (empty($filename)) {
return;
}
// Full file path
$file_path = $log_dir . $filename;
// Prepare log content (queries list with optional header)
$log_content = "\n\n=== Queries for " . esc_url_raw(home_url(add_query_arg(array(), $GLOBALS['wp']->request))) . " on " . current_time('mysql') . " ===\n";
$log_content .= print_r($wpdb->queries, true);
// Append to file (creates if not exists)
file_put_contents($file_path, $log_content, FILE_APPEND | LOCK_EX);
}
}
add_action('shutdown', 'log_queries_to_file'); После включения вы увидите /queries/ В корневом каталоге WordPress, содержащем файлы журналов, такие как query-frontpage.log или query-single-post.log. Войдя в систему как администратор, я посетил каждый URL-адрес, который включал один из моих шаблонов, чтобы зарегистрировать запросы и их производительность.
Как прочитать данные SAVEQUERIES
Каждая запись будет отображать текст запроса, время выполнения и стек вызовов функции, которая его инициировала. Со временем вы получите профиль, показывающий, какие шаблоны и ресурсы приносят больше всего затрат.
[20] => Array
(
[0] => SELECT wp_posts.ID FROM wp_posts LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (32489)
) AND wp_posts.post_type="nav_menu_item" AND ((wp_posts.post_status="publish")) GROUP BY wp_posts.ID ORDER BY wp_posts.menu_order ASC
[1] => 0.00042486190795898
[2] => require('wp-blog-header.php'), require_once('wp-includes/template-loader.php'), include('/themes/jannah/page.php'), get_header, locate_template, load_template, require_once('/themes/mtz-23/header.php'), TIELABS_HELPER::get_template_part, include('/themes/jannah/templates/header/load.php'), TIELABS_HELPER::get_template_part, include('/themes/jannah/templates/header/nav-top.php'), wp_nav_menu, wp_get_nav_menu_items, get_posts, WP_Query->query, WP_Query->get_posts
[3] => 1762916659.7638
[4] => Array
(
)
) Эта запись из вывода SAVEQUERIES представляет собой один SQL-запрос, выполненный WordPress, а также информацию о времени и контексте, которая позволяет отслеживать, где и почему он был запущен. Каждый элемент нумерованного массива соответствует запросу, а структура предоставляет пять основных фрагментов информации:
- Сам запрос — значение индекса [0] — это необработанный оператор SQL, который WordPress отправляет в базу данных. В этом случае:
SELECT wp_posts.ID
FROM wp_posts
LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1
AND (wp_term_relationships.term_taxonomy_id IN (32489))
AND wp_posts.post_type="nav_menu_item"
AND ((wp_posts.post_status="publish"))
GROUP BY wp_posts.ID
ORDER BY wp_posts.menu_order ASC - Этот запрос извлекает все опубликованные элементы меню навигации (post_type = ‘nav_menu_item’), принадлежащие данному элементу меню (term_taxonomy_id = 32489). LEFT JOIN связывает каждый пункт меню с его отношением таксономии, а предложения GROUP BY и ORDER BY гарантируют, что элементы меню возвращаются в указанном порядке. Это типичный запрос, генерируемый, когда тема WordPress отображает меню навигации с помощью wp_nav_menu().
- Время выполнения запроса — значение индекса [1] (0,00042486190795898) — время в секундах, необходимое для выполнения запроса. В данном случае все завершилось менее чем за полмиллисекунды, и это здорово. При просмотре логов потенциальными кандидатами на оптимизацию являются запросы, длительность которых постоянно превышает 0,02–0,05 секунды.
- Куча звонков — значение индекса [2] перечисляет всю цепочку вызовов PHP, которые привели к этому запросу. Читая его слева направо, вы можете увидеть, что запрос возник из шаблона заголовка темы при отрисовке меню навигации. В частности, функция wp_nav_menu под названием wp_get_nav_menu_items, которая запускала вызов get_posts, который в конечном итоге выполнялся методом WP_Query->get_posts. Отслеживание этого вызова необходимо для точного определения того, какой шаблон или функция инициировала запрос.
- Отметка времени — значение индекса [3] (1762916659.7638) — это временная метка Unix, когда запрос был выполнен во время запроса. Это поможет вам организовать запросы в хронологическом порядке или связать их с определенными последовательностями загрузки.
- Аргументы — окончательный массив в [4] в этом примере пусто, но будет содержать все связанные параметры, если в запросе использовались заполнители.
Таким образом, эта запись журнала показывает, что тема сгенерировала стандартный запрос для отображения элементов меню навигации в заголовке сайта. Хотя сам запрос эффективен, он является хорошим напоминанием о том, что каждое местоположение меню или динамический раздел может вызывать дополнительные запросы. На сложных страницах с несколькими меню или заголовками виджетов эти небольшие повторяющиеся поиски могут накапливаться, подчеркивая важность меню и других статических элементов веб-сайта как эффективной стратегии оптимизации.
Оптимизация шаблонов и запросов
После того как вы определили, какие шаблоны и компоненты генерируют больше всего запросов, следующим шагом будет их улучшение. Оптимизация запросов в WordPress — это не только скорость, но и стабильность и масштабируемость. Несколько целенаправленных настроек могут значительно снизить нагрузку на ваши запросы, сохраняя отзывчивость ваших страниц даже во время пиков трафика.
- Устраните ненужные запросы. Многие темы и плагины WordPress запрашивают данные, которые они фактически не используют. Например, некоторые получат все метаданные записи, когда им нужно только одно поле, или вызовут get_posts() внутри циклов вместо того, чтобы полагаться на основной запрос. Просмотрите файлы шаблонов и удалите избыточные вызовы базы данных, особенно внутри циклов или повторяющихся компонентов, таких как верхние и нижние колонтитулы. По возможности заменяйте динамический контент, управляемый базой данных, статическими тегами шаблона или заранее рассчитанными значениями.
- Уменьшите размер запросов с помощью кеша. Одним из наиболее эффективных способов повышения эффективности является кэширование часто используемой информации, которая редко меняется. Например, меню, области виджетов или параметры сайта можно кэшировать во временном или объектном кеше, чтобы предотвратить повторный поиск при каждой загрузке страницы. Например, меню сайта можно загрузить и сохранить в эфемерном режиме, который обновляется только при изменении самого меню. Такой подход разгружает повторяющиеся запросы и значительно снижает нагрузку на базу данных без потери актуальности.
- Оптимизируйте свою базу данных с помощью правильной индексации. Со временем базы данных WordPress становятся большими и фрагментированными, особенно на сайтах с обширными таблицами постмет или комментариев. Добавление соответствующих индексов к часто запрашиваемым столбцам, таким как Meta_key и Meta_value, может иметь большое значение. Многие плагины, включая Query Monitor и WP-Optimize, могут выделять медленные запросы и предлагать улучшения индексации. Периодическая оптимизация таблиц и очистка неизвестных данных также помогает MySQL более эффективно обрабатывать запросы.
- Используйте инструменты кэширования базы данных. Когда ваши запросы максимально эффективны, кэширование их результатов может значительно повысить производительность. Системы кэширования объектов, такие как Redis или Memcached, хранят результаты запросов и объекты PHP в памяти, чтобы WordPress мог извлекать их без повторного использования базы данных. Это особенно эффективно для сайтов с высоким трафиком или сайтов со сложными шаблонами запросов, где делается несколько запросов для одного и того же поиска постмета или таксономии. Постоянный кэш объектов гарантирует, что после получения данных они остаются быстро доступными до тех пор, пока не будут явно признаны недействительными, что значительно снижает нагрузку на MySQL и улучшает время отклика. Многие современные хосты предлагают интеграцию Redis или Memcached, а WordPress поддерживает их с помощью плагинов кэширования с раскрывающимся списком, таких как object-cache.php.
- Используйте инструменты кэширования страниц и CDN.. Помимо улучшений на уровне базы данных, решения для полностраничного кэширования вообще не позволяют многим страницам вызывать WordPress или MySQL. Такие плагины, как WP Rocket, W3 Total Cache и LiteSpeed Cache, могут создавать и хранить статические версии ваших страниц, которые сразу же отображаются посетителям. На уровне сервера кэш Nginx FastCGI или Varnish достигает того же эффекта с еще меньшими издержками. Добавление сети доставки контента (CDN), такой как Cloudflare или BunnyCDN, расширяет возможности за счет кэширования и распределения этих статических ресурсов по всему миру, уменьшая задержку для пользователей независимо от их местоположения. CDN также может кэшировать полные снимки HTML для анонимных посетителей, а это означает, что ваш исходный сервер редко обрабатывает динамические запросы. Вместе эти уровни кэширования образуют каскадную иерархию — кеш базы данных, кеш объектов, кеш страниц и кеш CDN — что гарантирует, что WordPress доставляет страницы как можно быстрее, одновременно значительно снижая нагрузку на запросы и сервер.
Заключение
Производительность базы данных — один из наиболее игнорируемых аспектов оптимизации WordPress. В то время как плагины, скрипты и изображения часто виноваты в медленной загрузке, часто виноваты чрезмерные и неэффективные запросы. Включив SAVEQUERIES, регистрируя данные запроса по шаблону и систематически устраняя проблемные области, вы можете обнаружить недостатки, которые большинство разработчиков никогда не замечают.
Устранив ненужные запросы, интеллектуальное кэширование, оптимизировав таблицы и внедрив решения многоуровневого кэширования, WordPress можно превратить из CMS с большим количеством запросов в быструю масштабируемую платформу публикации. Истинная скорость не может быть достигнута путем сокрытия неэффективности с помощью простого кэширования — она начинается с понимания и устранения основной причины неэффективности: того, как ваш сайт взаимодействует со своей базой данных.

