En tant que développeurs WordPress, nous avons souvent besoin de récupérer depuis la base de données WordPress des articles, des pages et d’autres contenus répondant à des critères spécifiques. Habituellement, nous n’avons pas besoin de construire des requêtes SQL (et souvent nous ne devrions pas le faire) parce que la classe WP_Query et ses méthodes nous fournissent un moyen sûr et efficace pour récupérer les données de la base de données. Il suffit de déclarer un tableau d’arguments, et l’objet $query va construire la requête SQL proprement dite.

Dans cet article, je suppose que vous connaissez déjà les principes de base de la classe WP_Query ses méthodes et ses propriétés, et où trouver une liste des variables disponibles.

Je vais me concentrer sur les paramètres fournis spécifiquement par la classe WP_Query pour optimiser les requêtes SQL, en réduisant le temps d’exécution et la consommation de ressources.

Lorsque le trafic et le contenu sont limités, nous ne nous soucions généralement pas de l’efficacité de nos requêtes. WordPress construit des requêtes SQL bien optimisées et fournit un système de mise en cache prêt à l’emploi.

Lorsque le trafic et le contenu du site augmentent de manière significative – jusqu’à des milliers d’articles – alors nous devons considérer le temps d’exécution des requêtes.

Notre Boîte à Outils

Le code que je vais vous montrer a été testé avec Query Monitor, une extension gratuite qui fournit des informations essentielles sur les performances des requêtes, les hooks déclenchés, les requêtes HTTP, les règles de réécriture, et bien plus encore.

Alternativement à une extension, nous pouvons forcer WordPress à stocker les informations de requête déclarant la constante suivante dans wp-config.php :

define( 'SAVEQUERIES', true );

Lorsque SAVEQUERIES est réglé sur true, WordPress enregistre les requêtes et un tas d’informations utiles dans le tableau $wpdb->queries. Ainsi, les noms des fonctions de l’appelant et l’erreur d’exécution de chaque requête peuvent être affichées en ajoutant le code suivant dans un fichier modèle comme footer.php :

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

Voici un exemple de ce que ça donne :

[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 vous souhaitez approfondir ce sujet, jetez un coup d’œil à notre tutoriel : Modification de wp-config.php.
Enfin, pensez que l’extension et la fonctionnalité intégrée SAVEQUERIES développent des outils que nous devrions désactiver dans un environnement de production.

Ceci étant dit, voyons comment accélérer les requêtes WordPress.

WP_Query – Pourquoi Nous Ne Comptons Pas les Lignes

Nous pouvons interroger la base de données avec la fonction get_posts, qui retourne un tableau d’articles, ou une nouvelle instance de l’objet WP_Query. Dans les deux cas, nous pouvons déterminer les résultats des requêtes en définissant des valeurs appropriées pour des variables spécifiques.

Commençons par un exemple qui montre une boucle commune telle qu’elle apparaît habituellement dans un fichier modèle :

// 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 est un tableau de paires clé/valeur. Ces paires sont appelées variables de requête et déterminent ou affectent la requête SQL proprement dite.
Lorsque vous interrogez la base de données à partir d’une extension, nous pouvons préférer utiliser le filtre pre_get_posts, comme le montre l’exemple suivant :

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

Une chose importante à noter ici est que l’objet $query est passé par référence, et non par valeur, ce qui signifie que les arguments de requête affectent juste une instance $query existante.

La méthode de set définie ajoute un nouvelle variable de requête à la spécification de requête et forcera WordPress à récupérer tous les articles de la catégorie webdev. Ceci est la requête résultante :

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

Dans cet exemple, la valeur LIMIT a été définie par l’utilisateur administrateur dans les options de lecture, comme le montre l’image ci-dessous.

Écran de lecture

Dans les requêtes personnalisées, nous pouvons définir le nombre de lignes à récupérer dans la base de données grâce à l’option réglage de pagination posts_per_page.

L’option SQL_CALC_FOUND_ROWS force la requête à compter le nombre de lignes trouvées. Ce numéro sera retourné par la fonction FOUND_ROWS() SQL, comme dans l’exemple suivant :

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

SELECT FOUND_ROWS();

Malheureusement, SQL_CALC_FOUND_ROWS peut ralentir considérablement le temps d’exécution de la requête.

La bonne nouvelle est que nous pouvons forcer WordPress à supprimer l’option fournissant la variable no_found_rows sous-utilisée (et non documentée).

Si SQL_CALC_FOUND_ROWS est omis, FOUND_ROWS() retourne le nombre de lignes jusqu’à la valeur de LIMIT (pour en savoir plus à ce sujet, voir la documentation MySQL).

Dans une installation WordPress avec quelques centaines d’articles, la méta requête suivante a pris 0.0107 secondes :

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

Supprimer SQL_CALC_FOUND_ROWS définit no_found_rows sur false, la même requête a pris 0.0006 secondes.

Grâce à l’extension Query Monitor, nous pouvons facilement comparer deux requêtes avec et sans option SQL_CALC_FOUND_ROWS
Grâce à l’extension Query Monitor, nous pouvons facilement comparer deux requêtes avec et sans option SQL_CALC_FOUND_ROWS

Lorsque la table wp_post contient des milliers de lignes, l’exécution de la requête peut prendre quelques secondes.
Quand nous n’avons pas besoin de pagination, nous ne devrions jamais mettre no_found_rows sur true, ce qui rend la requête beaucoup plus rapide.

Mettre en Cache ou ne pas Mettre en Cache

WordPress fournit un système de mise en cache intégré prêt à l’emploi. Bien que la mise en cache améliore généralement la vitesse de chargement des pages, elle peut entraîner l’exécution de certaines requêtes supplémentaires dans la base de données. De plus, chaque fois qu’une requête est exécutée, un tas de données inutiles peut être demandé.

Heureusement, WordPress nous permet de désactiver la mise en cache en fournissant trois paramètres spécifiques :

  • cache_results : S’il y a lieu de mettre en cache les informations publiées. Par défaut : true.
  • update_post_meta_cache : S’il faut mettre à jour le cache de méta de l’article. Par défaut : true.
  • update_post_term_cache : S’il faut mettre à jour le cache de terme d’article. Par défaut : true.

Si un système de cache persistant est activé, tel que Memcached, nous n’avons pas à nous soucier des paramètres de mise en cache car WordPress définir sur false ces arguments par défaut.

Dans toute autre situation, nous pouvons construire une requête plus rapide avec le code suivant :

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

Lorsqu’un système de mise en cache permanent n’est pas disponible, les requêtes renvoyant de petites quantités de données ne doivent pas être mises en cache.

Champs Retournés

En règle générale, nous ne devrions jamais interroger la base de données à la recherche de champs inutiles. La classe WP_Query fournit l’argument des champs, qui permet de limiter les champs retournés aux ID ou aux champs « id=>parent ». Le fichier source documente l’argument des champs comme suit :

Quels champs retourner. Champ unique ou tous les champs (chaîne), ou tableau de champs. « id=>parent » utilise « id » et « post_parent ». Par défaut, tous les champs. Accepte « ids », « id=>parent ».

La variable des champs admet « ids » et « id=>parent », et par défaut * (toute autre valeur), bien que vous remarquerez que par défaut WordPress mettra la valeur des ids dans plusieurs requêtes.
Enfin, nous pouvons optimiser notre première requête :

<?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 );
// The Loop
if ( $the_query->have_posts() ) {
	?>
	<ul>
	<?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
		<li><a href="<?php the_permalink(); ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a></li>
	<?php endwhile; ?>
	</ul>
	<?php
} else {
	// no posts found
}
/* Restore original Post Data */
wp_reset_postdata();
?>

Lorsque des champs spécifiques ne sont pas nécessaires, limitez les champs retournés aux IDs.

Résumé

Considérant la vitesse de requête peut ne pas apporter d’énormes avantages pour les petits sites Web avec quelques centaines d’articles. Si vous voulez vous préparer à la croissance ou si vous avez un grand site Web avec des requêtes lourdes, vous devriez optimiser vos requêtes WordPress. Des requêtes inefficaces peuvent ralentir considérablement le chargement des pages, mais avec quelques modifications simples, vous pouvez accélérer considérablement votre site Web.

Carlo Daniele Kinsta

Carlo is a passionate lover of webdesign and front-end development. He has been playing with WordPress for more than 20 years, also in collaboration with Italian and European universities and educational institutions. He has written hundreds of articles and guides about WordPress, published both on Italian and international websites, as well as on printed magazines. You can find him on LinkedIn.