Como desenvolvedores do WordPress, muitas vezes precisamos recuperar posts, páginas e outros conteúdos que correspondam a critérios específicos a partir do banco de dados do WordPress. Normalmente, não precisamos de construir consultas SQL (e muitas vezes não devemos) porque a classe WP_Query e os seus métodos fornecem-nos uma forma segura e eficiente de recuperar dados da base de dados. Nós só precisamos declarar um array de argumentos, e o objeto $query irá construir a consulta SQL real.

Neste post, vou assumir que você já conhece o básico da classe WP_Query, seus métodos e propriedades, e onde encontrar uma lista das variáveis disponíveis.

Vou me concentrar nos parâmetros fornecidos pela classe WP_Query especificamente para otimizar as consultas SQL, reduzindo o tempo de execução e o consumo de recursos.

Quando o tráfego e o conteúdo são limitados, normalmente não nos preocupamos com a eficiência das nossas consultas. O WordPress constrói consultas SQL bem otimizadas e fornece um sistema de cache fora da caixa.

Quando o tráfego e o conteúdo do site crescem significativamente – até milhares de posts – então devemos considerar o tempo de execução da consulta.

Nossa Caixa de Ferramentas

O código que vou mostrar foi testado com o Query Monitor, um plugin gratuito que fornece informações essenciais sobre o desempenho da consulta, ganchos acionados, pedidos HTTP, regras de reescrita, e muito mais.

Alternativamente a um plugin, podemos forçar o WordPress a armazenar informações de consulta declarando a seguinte constante em wp-config.php:

define( 'SAVEQUERIES', true );

Quando SAVEQUERIES é definido para true, o WordPress registra as consultas e um monte de informações úteis no array $wpdb->queries. Assim, os nomes das funções do chamador e o lapso de execução de cada consulta podem ser impressos adicionando o seguinte código em um arquivo modelo como footer.php:

if ( current_user_can( 'administrator' ) ) {
	global $wpdb;
	echo '<pre>';
	print_r( $wpdb->queries );
	echo '</pre>';
}

Aqui está um exemplo do que é ecoado:

[4] => Array
(
	[0] => SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10
	[1] => 0.0163011550903
	[2] => require('wp-blog-header.php'), wp, WP->main, WP->query_posts, WP_Query->query, WP_Query->get_posts, QM_DB->query
	[trace] => QM_Backtrace Object
		( ... )
	[result] => 10
)

Se você gostaria de mergulhar fundo neste tópico, dê uma olhada no nosso tutorial: Editando o wp-config.php.
Finalmente, considere que tanto o plugin como a funcionalidade SAVEQUERIES integrada estão desenvolvendo ferramentas que devemos desligar em um ambiente de produção.

Dito isto, vamos dar uma olhada em como acelerar as consultas ao WordPress.

WP_Query – Porque não contávamos as filas

Podemos consultar o banco de dados com a função get_posts, que retorna um array de posts, ou uma nova instância do objeto WP_Query. Em ambos os casos, podemos determinar os resultados das consultas, definindo valores apropriados para variáveis específicas.

Vamos começar com um exemplo que mostra um Loop comum como ele normalmente aparece em um arquivo modelo:

// The Query
$the_query = new WP_Query( $args );
// The Loop
if ( $the_query->have_posts() ) {
	while ( $the_query->have_posts() ) : $the_query->the_post(); 
		// Your code here
	endwhile;
} else {
		// no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

$args é um conjunto de pares chave/valor. Estes pares são nomeados vars de consulta, e determinam ou afetam a consulta SQL real.
Ao consultar a base de dados a partir de um plugin, podemos preferir usar o filtro pre_get_posts, como mostrado no exemplo a seguir:

function myplugin_pre_get_posts( $query ) {
  if ( is_admin() || ! $query->is_main_query() ){
	return;
  }
  $query->set( 'category_name', 'webdev' );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

Uma coisa importante a notar aqui é que o objeto $query é passado por referência, não por valor, o que significa que os argumentos da consulta estão apenas afetando uma instância $query existente.

O método set adiciona uma nova query var à especificação da consulta e forçará o WordPress a recuperar todas as postagens da categoria webdev. Esta é a consulta resultante:

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
INNER JOIN wp_term_relationships
ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 
AND ( wp_term_relationships.term_taxonomy_id IN (12) )
AND wp_posts.post_type = 'post'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

Neste exemplo, o valor LIMIT foi definido pelo usuário administrador em Opções de Leitura, como mostrado na imagem abaixo.

Reading Settings

Em consultas personalizadas podemos definir o número de linhas a serem recuperadas do banco de dados graças ao parâmetro de paginação posts_per_page.

A opção SQL_CALC_FOUND_ROWS força a consulta a contar o número de linhas encontradas. Este número será retornado pela função FOUND_ROWS() SQL, como mostrado no exemplo a seguir:

SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name
WHERE id > 100 LIMIT 10;

SELECT FOUND_ROWS();

Infelizmente, SQL_CALC_FOUND_ROWS pode retardar significativamente o tempo de execução da consulta.
A boa notícia é que podemos forçar o WordPress a remover a opção que fornece a variável no_found_rows
subutilizada (e não documentada).

Se SQL_CALC_FOUND_ROWS for omitido, FOUND_ROWS() retorna o número de linhas até o valor de LIMIT (mais sobre este tópico na documentação do MySQL).

Em uma instalação do WordPress com poucas centenas de posts, a seguinte meta consulta demorou 0,0107 segundos:

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts 
INNER JOIN wp_postmeta
ON ( wp_posts.ID = wp_postmeta.post_id )
WHERE 1=1 
AND ( ( wp_postmeta.meta_key = 'book_author'
AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%Isaac Asimov%' ) )
AND wp_posts.post_type = 'book'
AND (wp_posts.post_status = 'publish'
OR wp_posts.post_status = 'private')
GROUP BY wp_posts.ID
ORDER BY wp_posts.post_date DESC
LIMIT 0, 10

Removendo SQL_CALC_FOUND_ROWS definindo no_found_rows para false, a mesma consulta levou 0,0006 segundos.

Graças ao plugin Query Monitor, podemos facilmente comparar duas consultas com e sem a opção SQL_CALC_FOUND_ROWS
Graças ao plugin Query Monitor, podemos facilmente comparar duas consultas com e sem a opção SQL_CALC_FOUND_ROWS

Quando a tabela wp_post contém milhares de linhas, a execução da consulta pode levar segundos.
Quando não precisamos de paginação, nunca devemos definir no_found_rows para true, fazendo a consulta correr dramaticamente mais rápido.

Cache ou Não Cache

O WordPress fornece um sistema de cache integrado fora da caixa. Embora o cache geralmente melhore a velocidade de carregamento da página, pode fazer com que algumas consultas extras sejam feitas contra a base de dados. Além disso, sempre que uma consulta é executada, um monte de dados desnecessários pode ser solicitado.

Felizmente, o WordPress nos permite desativar o cache fornecendo três parâmetros específicos:

  • cache_results: Se para guardar informações. Por omissão, é verdade.
  • update_post_meta_cache: Se deve atualizar a meta cache posterior. Por omissão, é verdade.
  • update_post_term_cache: Se deve atualizar a cache pós-termo. Por omissão, é verdade.

Se um sistema de cache persistente for ativado, como o Memcached, não temos que nos preocupar com os parâmetros de cache, porque o WordPress irá definir estes argumentos como falsos por padrão.

Em qualquer outra situação, podemos construir uma consulta mais rápida com o seguinte código:

function myplugin_pre_get_posts( $query ) {
  if ( is_admin() || ! $query->is_main_query() ){
	return;
  }
  $query->set( 'category_name', 'webdev' );

  $query->set( 'no_found_rows', true );
  $query->set( 'update_post_meta_cache', false );
  $query->set( 'update_post_term_cache', false );
}
add_action( 'pre_get_posts', 'myplugin_pre_get_posts', 1 );

Quando um sistema de cache permanente não está disponível, as consultas que retornam pequenas quantidades de dados não devem ser colocadas em cache.

Campos devolvidos

Como regra geral, nunca devemos consultar a base de dados para campos desnecessários. A classe WP_Query fornece o argumento campos, que permite limitar os campos devolvidos aos IDs ou campos 'id=>parent'. A documentação do arquivo fonte define o argumento dos campos da seguinte forma:

Quais os campos a devolver. Campo único ou todos os campos (string), ou array de campos. id=>parent’ usa ‘id’ e ‘post_parent’. Predefinir todos os campos. Aceita ‘ids’, ‘id=>parente’.

A variável campos admite 'ids' e 'id=>parent', e o valor padrão é * (qualquer outro valor), embora você note que por padrão o WordPress irá definir o valor como ids em várias consultas.
Finalmente, podemos optimizar a nossa primeira consulta:

<?php
$args = array( 
	'no_found_rows' => true, 
	'update_post_meta_cache' => false, 
	'update_post_term_cache' => false, 
	'category_name' => 'cms', 
	'fields' => 'ids'
);
// The Query
$the_query = new WP_Query( $args );
$my_posts = $the_query->get_posts();

if( ! empty( $my_posts ) ){
    foreach ( $my_posts as $p ){
        // Your code
    }
}
/* Restore original Post Data */
wp_reset_postdata();
?>

Quando não são necessários campos específicos, limite os campos devolvidos a IDs.

Resumo

Considerando a velocidade de consulta pode não trazer grandes vantagens para sites pequenos com algumas centenas de posts. Se você quer se preparar para o crescimento ou está rodando um grande site com consultas caras, você deve otimizar suas consultas ao WordPress. Consultas ineficientes podem reduzir drasticamente o número de páginas, mas com alguns ajustes simples você pode acelerar consideravelmente o seu site.

Carlo Daniele Kinsta

Carlo é um apaixonado por webdesign e desenvolvimento frontend. Ele tem mais de 10 anos de experiência com WordPress e colaborou com diversas universidades e instituições educacionais na Itália e na Europa. Carlo já publicou inúmeros artigos e guias sobre WordPress, tanto em sites italianos quanto internacionais, além de revistas impressas. Você pode seguir ele no LinkedIn e no X.