Прямые запросы к бд WordPress: безопасные WPDB‑запросы в 6 шагах
1. Почему иногда нужны прямые запросы к БД WordPress
Стандартный API WordPress (WP_Query, get_posts и т.д.) покрывает большинство задач, но в реальных проектах часто требуются сложные JOIN‑ы, агрегации или массовые обновления, которые невозможно реализовать через обычные функции. В таких случаях единственной надёжной опцией остаются прямые запросы к бд WordPress, но их нужно писать с учётом безопасности и производительности.
2. WPDB: базовый инструмент для безопасных запросов
2.1 Подключение и базовые методы
Класс $wpdb – глобальная обёртка над mysqli, доступная в любой части темы или плагина. Он уже знает префикс таблиц, кодировку и поддерживает транзакции.
global $wpdb;
// Пример простого SELECT без подготовки (не рекомендуется)
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_status = 'publish'" );
2.2 Подготовленные запросы (prepare)
Для защиты от инъекций используйте метод prepare(). Он заменяет плейсхолдеры %s, %d, %f на безопасные значения.
global $wpdb;
$sql = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status = %s AND post_type = %s",
'publish',
'post'
);
$posts = $wpdb->get_results( $sql );
3. Защита от SQL‑инъекций
3.1 Экранирование данных вручную
Если по какой‑то причине вы не можете использовать prepare(), применяйте $wpdb->esc_like() и $wpdb->prepare() только для переменных, а остальные части строки фиксируйте в коде.
3.2 Полный пример с $wpdb->prepare()
global $wpdb;
$search = isset($_GET['s']) ? $_GET['s'] : '';
$search_like = '%' . $wpdb->esc_like( $search ) . '%';
$sql = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_title LIKE %s",
$search_like
);
$found = $wpdb->get_results( $sql );
Такой запрос полностью защищён от попыток внедрить произвольный SQL.
4. Практические примеры
4.1 SELECT с подготовкой и пагинацией
global $wpdb;
$per_page = 20;
$paged = max( 1, get_query_var('paged') );
$offset = ( $paged - 1 ) * $per_page;
$sql = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status = %s ORDER BY post_date DESC LIMIT %d OFFSET %d",
'publish', $per_page, $offset
);
$posts = $wpdb->get_results( $sql );
4.2 INSERT/UPDATE с транзакциями
global $wpdb;
$wpdb->query('START TRANSACTION');
try {
$wpdb->insert(
$wpdb->postmeta,
[
'post_id' => 123,
'meta_key' => '_custom_key',
'meta_value' => maybe_serialize( $data )
],
[ '%d', '%s', '%s' ]
);
$wpdb->update(
$wpdb->posts,
[ 'post_status' => 'draft' ],
[ 'ID' => 123 ],
[ '%s' ], [ '%d' ]
);
$wpdb->query('COMMIT');
} catch ( Exception $e ) {
$wpdb->query('ROLLBACK');
error_log( $e->getMessage() );
}
Транзакции гарантируют, что набор запросов выполнится атомарно, а в случае ошибки – откатится.
5. Оптимизация и кэширование запросов
Прямые запросы могут стать «узким местом», если их выполнять часто. Для снижения нагрузки рекомендуется кэшировать результаты в объектный кэш.
WordPress поддерживает несколько бекендов кэша: Redis vs Memcached. Пример кэширования SELECT‑запроса через wp_cache_get() и wp_cache_set():
global $wpdb;
$cache_key = 'latest_posts_'. $paged;
$posts = wp_cache_get( $cache_key, 'my_plugin' );
if ( false === $posts ) {
$sql = $wpdb->prepare(
"SELECT ID, post_title FROM {$wpdb->posts} WHERE post_status = %s ORDER BY post_date DESC LIMIT %d OFFSET %d",
'publish', $per_page, $offset
);
$posts = $wpdb->get_results( $sql );
wp_cache_set( $cache_key, $posts, 'my_plugin', HOUR_IN_SECONDS );
}
При использовании Redis кэш будет храниться в памяти, что ускорит отдачу страниц в 2–3 раза.
6. Отладка и логирование запросов
Для быстрого поиска «тормозных» запросов включите отладочный режим:
define('SAVEQUERIES', true);
После этого массив $wpdb->queries будет содержать каждый выполненный запрос, его время и стек вызовов. Выводим в админ‑панель:
add_action('admin_footer', function(){
global $wpdb;
echo ' Не забывайте отключать SAVEQUERIES на продакшене, иначе увеличите нагрузку и объём логов. Для полного контроля над чистыми URL, кэшированием и безопасностью ознакомьтесь с нашими материалами: удаление category и tag из URL, кэширование браузера WordPress и миграция HTTP→HTTPS. Эти статьи помогут построить надёжную и быструю инфраструктуру вокруг ваших прямых запросов.
❓ Часто задаваемые вопросы
Можно ли использовать $wpdb->query() для SELECT‑запросов?
Да, но предпочтительно использовать $wpdb->get_results() или $wpdb->get_row(), так как они сразу возвращают массив объектов и упрощают обработку.
Как избежать двойного экранирования при использовании $wpdb->prepare()?
Передача уже экранированных значений в prepare приводит к двойному экранированию. Передавайте «чистые» переменные, а не результаты $wpdb->esc_sql().
Поддерживает ли WPDB мульти‑сайты?
Да, $wpdb автоматически учитывает префикс таблиц для каждой подсайта, поэтому запросы работают без изменений.
Можно ли кэшировать результаты $wpdb->prepare() в объектный кэш?
Определённо. Хэшировать готовый результат (массив записей) безопасно; при следующем запросе проверяйте кэш перед выполнением prepare().