Como desarrolladores de WordPress, a menudo necesitamos recuperar publicaciones, páginas y otros contenidos que coinciden con criterios específicos de la base de datos de WordPress. Por lo general, no necesitamos construir consultas SQL (y a menudo no deberíamos) porque la clase WP_Query y sus métodos nos proporcionan una forma segura y eficiente de recuperar datos de la base de datos. Sólo tenemos que declarar una serie de argumentos, y el objeto $query construirá la consulta SQL real.

En este post, asumo que ya conoce los fundamentos de la clase WP_Query, sus métodos y propiedades, y donde encontrar una lista de las variables disponibles.

Me centraré en los parámetros proporcionados por la clase WP_Query específicamente para optimizar las consultas SQL, reduciendo el tiempo de ejecución y el consumo de recursos.

Cuando el tráfico y el contenido son limitados, normalmente no nos preocupamos por la eficiencia de nuestras consultas. WordPress construye consultas SQL bien optimizadas y proporciona un sistema de almacenamiento en memoria caché fuera de la caja.

Cuando el tráfico y el contenido del sitio crecen significativamente – hasta miles de posts – entonces debemos considerar el tiempo de ejecución de la consulta.

Nuestra caja de herramientas

El código que les voy a mostrar ha sido probado con Query Monitor, un plugin gratuito que proporciona información esencial sobre el rendimiento de las consultas, los ganchos activados, las solicitudes HTTP, las reglas de reescritura y mucho más.

Como alternativa a un plugin, podemos forzar a WordPress a que almacene la información de la consulta declarando la siguiente constante en wp-config.php:

define( 'SAVEQUERIES', true );

Cuando SAVEQUERIES se establece como true, WordPress registra las consultas y un montón de información útil en la matriz $wpdb->queries. Así, los nombres de las funciones de llamada y el lapso de ejecución de cada consulta pueden imprimirse añadiendo el siguiente código en un archivo de plantilla como el footer.php:

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

Aquí hay un ejemplo de lo que se repite:

[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
)

Si quiere profundizarse en este tema, eche un vistazo a nuestro tutorial: Editando wp-config.php.
Por último, considere que tanto el plugin como la funcionalidad incorporada de SAVEQUERIES están desarrollando herramientas que deberíamos desconectar en un entorno de producción.

Dicho esto, veamos cómo acelerar las consultas de WordPress.

WP_Query – ¿Por qué no contamos las filas?

Podemos consultar la base de datos con la función get_posts, que devuelve un array de posts, o una nueva instancia del objeto WP_Query. En ambos casos podemos determinar los resultados de las consultas fijando valores adecuados a variables específicas.

Comencemos con un ejemplo que muestra un bucle común como suele aparecer en un archivo de plantilla:

// 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 es un conjunto de pares clave/valor. Estos pares se denominan vars de consulta, y determinan o afectan a la consulta SQL real.

Al consultar la base de datos desde un plugin, es posible que prefiramos utilizar el filtro pre_get_posts, como se muestra en el siguiente ejemplo:

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 );

Una cosa importante a notar aquí es que el objeto $query se pasa por referencia, no por valor, lo que significa que los argumentos de la consulta sólo afectan a una instancia $query existente.

El método del set añade una nueva varilla de consulta a la especificación de la consulta y obligará a WordPress a recuperar todos los mensajes de la categoría webdev. Esta es la 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

En este ejemplo, el valor LIMIT ha sido establecido por el usuario administrador en las opciones de lectura, como se muestra en la siguiente imagen.

Reading Settings

En las consultas personalizadas podemos establecer el número de filas a recuperar de la base de datos gracias al parámetro de paginación posts_per_page.

La opción SQL_CALC_FOUND_ROWS obliga a la consulta a contar el número de filas encontradas. Este número será devuelto por la función FOUND_ROWS() SQL, como se muestra en el siguiente ejemplo:

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

SELECT FOUND_ROWS();

Desafortunadamente, SQL_CALC_FOUND_ROWS puede ralentizar significativamente el tiempo de ejecución de la consulta.
La buena noticia es que podemos forzar a WordPress a eliminar la opción que proporciona la variable no_found_rows infrautilizada (y no documentada).

Si se omite SQL_CALC_FOUND_ROWS, FOUND_ROWS() devuelve el número de filas hasta el valor de LIMIT (más sobre este tema en la documentación de MySQL).

En una instalación de WordPress con unos pocos cientos de posts, la siguiente meta consulta tomó 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

Eliminando SQL_CALC_FOUND_ROWS poniendo no_found_rows en false, la misma consulta tomó 0.0006 segundos.

Thanks to Query Monitor plugin, we can easily compare two queries enabling and disabling SQL_CALC_FOUND_ROWS option

Gracias al plugin Query Monitor, podemos comparar fácilmente dos consultas con y sin la opción SQL_CALC_FOUND_ROWS

Cuando la tabla wp_post contiene miles de filas, la ejecución de la consulta podría tardar segundos.
Cuando no necesitemos paginación, deberíamos poner las filas de no_found_rows en true, haciendo que la consulta se ejecute dramáticamente más rápido.

Caché o no caché

WordPress proporciona un sistema de almacenamiento en caché incorporado desde la caja. Aunque el almacenamiento en caché generalmente mejora la velocidad de carga de la página, puede hacer que se ejecuten algunas consultas adicionales contra la base de datos. Además, cada vez que se ejecuta una consulta se podría solicitar un montón de datos innecesarios.

Afortunadamente, WordPress nos permite deshabilitar el cacheo con tres parámetros específicos:

  • resultados de la caché: Si se debe almacenar la información del correo. Por defecto es cierto.
  • update_post_meta_cache: Si actualizar el post meta cache. Por defecto es cierto.
  • update_post_term_cache: Si actualizar el caché post término. Por defecto es cierto.

Si se activa un sistema de cacheo persistente, como Memcached, no tenemos que preocuparnos por los parámetros de cacheo porque WordPress establecerá estos argumentos falsos por defecto.

En cualquier otra situación, podemos construir una consulta más rápida con el siguiente 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 );

Cuando no se dispone de un sistema de almacenamiento permanente, las consultas que devuelvan pequeñas cantidades de datos no deben almacenarse en la memoria intermedia.

Campos devueltos

Como regla general, nunca debemos consultar la base de datos por campos innecesarios. La clase WP_Query proporciona el argumento fields, que permite limitar los campos devueltos a los campos ID o 'id=>parent'. La documentación del archivo fuente define el argumento de los campos de la siguiente manera:

Qué campos devolver. Un solo campo o todos los campos (cadena), o una serie de campos. ‘id=>parent’ usa ‘id’ y ‘post_parent’. Por defecto todos los campos. Acepta ‘ids’, ‘id=>parent’.

La variable de campos admite 'ids' y 'id=>parent', y su valor predeterminado es * (cualquier otro valor), aunque notarás que, de forma predeterminada, WordPress establecerá el valor a ids en varias consultas.

Finalmente, podemos optimizar nuestra primera 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();
?>

Cuando no se requieran campos específicos, limite los campos devueltos a las identificaciones.

Resumen

Teniendo en cuenta que la velocidad de consulta puede no traer grandes ventajas para los pequeños sitios web con unos pocos cientos de mensajes. Si quiere prepararse para el crecimiento o tiene un gran sitio web con consultas costosas, debería optimizar sus consultas de WordPress. Las consultas ineficaces pueden ralentizar dramáticamente la carga de las páginas, pero con unos pocos y sencillos ajustes se puede acelerar considerablemente el sitio web.

Carlo Daniele Kinsta

Carlo es un diseñador y desarrollador de front-end freelance. Cuando escribe artículos y tutoriales, Carlo se ocupa principalmente de los estándares web, pero cuando juega con sitios web, su mejor compañero de trabajo es WordPress.