Sur un site vitrine pour un cabinet d’architectes, un bloc « Nos trois derniers projets » s’affiche en pied de page sur absolument toutes les pages du site. Une requête WP_Query toute simple, limitée à trois résultats, sans pagination puisqu’un lien renvoie ensuite vers l’archive complète. Rien d’alarmant en apparence, jusqu’à ce qu’un audit de performance pointe cette requête précise comme un point chaud, exécutée des milliers de fois par jour.
Le coupable n’était pas la requête elle-même, mais un comportement par défaut invisible : chaque appel à WP_Query calcule, en plus des résultats demandés, le nombre total de lignes qui auraient correspondu sans la limite. Ce calcul sert exclusivement à afficher une pagination. Ici, il n’y en avait pas.
Comprendre SQL_CALC_FOUND_ROWS
Par défaut, WordPress construit ses requêtes avec la clause SQL_CALC_FOUND_ROWS, qui demande à MySQL de compter le nombre total de lignes correspondant aux critères, indépendamment de la limite appliquée. Ce total est ensuite récupéré via found_posts et max_num_pages sur l’objet WP_Query, deux propriétés indispensables pour générer une pagination.
Le problème, c’est que ce comptage a un coût qui grandit avec la taille de la table, en particulier lorsque la requête combine plusieurs jointures ou des méta-requêtes complexes. Sur une boucle qui n’affiche jamais de pagination, ce coût est payé pour rien à chaque chargement de page.

Désactiver le calcul avec no_found_rows
Le paramètre no_found_rows, mis à true, indique à WordPress de ne pas construire cette clause :
$derniers_projets = new WP_Query( array(
'post_type' => 'projet',
'posts_per_page' => 3,
'no_found_rows' => true,
'orderby' => 'date',
'order' => 'DESC',
) );
Après cet ajustement, $derniers_projets->found_posts vaudra toujours 0 et max_num_pages également, quelle que soit la quantité réelle de contenus correspondants. C’est précisément pour cette raison que ce paramètre ne convient qu’aux boucles qui n’affichent aucune pagination, que ce soit via paginate_links() ou un système personnalisé de type « charger plus ».
Où l’utiliser sans hésiter
- Les blocs de contenus liés en pied d’article ou en fin de page.
- Les carrousels de mise en avant sur une page d’accueil.
- Les widgets de barre latérale listant les derniers billets.
- Toute requête utilisée uniquement pour vérifier l’existence d’au moins un résultat, via
have_posts().
Où ne surtout pas l’utiliser
Sur l’archive principale d’un blog, la recherche interne ou toute page listant des contenus avec une navigation entre pages, retirer ce calcul casse silencieusement la pagination : les liens « page suivante » disparaissent purement et simplement, sans message d’erreur, ce qui rend le bug difficile à repérer en recette si personne ne pense à faire défiler jusqu’à la dernière page de résultats.
La règle qu’on applique en revue de code : si le gabarit n’appelle jamais
the_posts_pagination()ou équivalent pour cette requête précise,no_found_rowsdoit y figurer. Dans le cas contraire, c’est un oubli à corriger.
Mesurer le gain réellement obtenu
Sur le site du cabinet d’architectes, l’ajout de no_found_rows sur les cinq requêtes de mise en avant récurrentes a réduit d’environ 12 % le temps cumulé passé en requêtes SQL sur une page d’accueil déjà chargée en widgets. Le gain individuel par requête reste modeste, de l’ordre de quelques millisecondes, mais il se cumule sur des pages qui multiplient les petites boucles secondaires.
Pour objectiver ce type de gain sur un projet réel, un outil de profilage des requêtes reste le complément naturel de cette optimisation, déjà présenté séparément sur ce blog.
En résumé
no_found_rows est l’un des paramètres les plus rentables de WP_Query rapportés à sa simplicité de mise en œuvre : une seule ligne, aucun risque, à condition de réserver son usage aux requêtes qui n’affichent jamais de pagination. Ce paramètre complète naturellement une réflexion plus large sur les méta-requêtes et les requêtes par taxonomie, déjà traitées séparément.