Come sviluppatori WordPress, spesso dobbiamo recuperare post, pagine e altri contenuti che soddisfano criteri specifici dal database di WordPress. Normalmente, non abbiamo bisogno di creare query SQL (e spesso non dovremmo) perché la classe WP_Query e i suoi metodi ci forniscono uno strumento sicuro ed efficiente per recuperare i dati dal database. Dobbiamo solo dichiarare un array di argomenti e WordPress costruirà la query SQL.

In questo post, supporremo che conosciate già le basi della classe WP_Query, i suoi metodi e proprietà e dove trovare un elenco delle variabili disponibili.

Ci concentreremo sui parametri forniti dalla classe WP_Query specificamente per ottimizzare le query SQL, riducendo i tempi di esecuzione e il consumo di risorse.

Quando il traffico e i contenuti sono limitati, di solito non ci preoccupiamo dell’efficienza delle nostre query. WordPress crea query SQL ben ottimizzate e fornisce un sistema di cache pronto all’uso.

Quando il traffico e il contenuto del sito crescono in modo significativo, fino ad arrivare a migliaia di post, dobbiamo considerare i tempi di esecuzione delle query.

La Nostra Cassetta degli Attrezzi

Il codice che stiamo per mostrarvi è stato testato con Query Monitor, un plugin gratuito che fornisce informazioni essenziali sulle prestazioni delle query, hook attivati, richieste HTTP, regole di rewrite e molto altro.

In alternativa a un plugin, possiamo forzare WordPress a memorizzare le informazioni sulle query dichiarando la seguente costante in wp-config.php:

define( 'SAVEQUERIES', true );

Quando SAVEQUERIES è impostato su true, WordPress registra le query e un sacco di informazioni utili nell’array $wpdb->queries. Quindi, i nomi delle funzioni chiamanti e l’intervallo di esecuzione di ciascuna query possono essere stampati aggiungendo il seguente codice in un template file come footer.php:

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

Ecco un esempio di ciò che viene stampato:

[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 desiderate approfondire questo argomento, date un’occhiata al nostro tutorial: Modificare il File wp-config.php.
Infine, considerate che sia il plugin che la funzionalità integrata SAVEQUERIES sono strumenti di sviluppo che dovremmo disattivare in un ambiente di produzione.

Detto questo, vediamo come velocizzare le query di WordPress.

WP_Query – Perché non Contiamo le Righe

È possibile eseguire una query sul database con la funzione get_posts, che restituisce un array di post o una nuova istanza dell’oggetto WP_Query. In entrambi i casi, possiamo determinare i risultati delle query impostando i valori appropriati su variabili specifiche.

Cominciamo con un esempio che mostra un Loop comune come di solito appare in un template file:

// 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 è un array di coppie chiave/valore. Queste coppie sono denominate query vars e determinano o influiscono sulla query SQL effettiva.
Quando si esegue una query sul database attraverso un plugin, sarebbe preferibile utilizzare il filtro pre_get_posts, come mostrato nell’esempio che segue:

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 da notare qui è che l’oggetto $query viene passato per riferimento, non per valore, il che significa che gli argomenti della query influenzano solo un’istanza $query esistente.

Il metodo set aggiunge una nuova query var alla query specification e forzerà WordPress a recuperare tutti i post dalla categoria webdev. Questa è la query che ne risulta:

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

In questo esempio, il valore di LIMIT è stato impostato dall’utente admin nelle Opzioni di lettura, come mostrato nell’immagine che segue.

Impostazioni lettura

Nelle query personalizzate possiamo impostare il numero di righe da recuperare dal database grazie al parametro di paginazione posts_per_page.

L’opzione SQL_CALC_FOUND_ROWS forza la query a contare il numero di righe trovate. Questo numero verrà restituito dalla funzione FOUND_ROWS(), come mostrato nell’esempio che segue:

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

SELECT FOUND_ROWS();

Sfortunatamente, SQL_CALC_FOUND_ROWS può rallentare significativamente il tempo di esecuzione della query.
La buona notizia è che possiamo forzare WordPress a rimuovere l’opzione fornendo la poco utilizzata (e non documentata) variabile no_found_rows.

Se si omette SQL_CALC_FOUND_ROWS, FOUND_ROWS() restituisce il numero di righe fino al valore di LIMIT (maggiori informazioni su questo argomento nella documentazione di MySQL).

In un’installazione di WordPress con poche centinaia di post, la seguente meta query ha richiesto 0,0107 secondi:

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

Rimuovendo SQL_CALC_FOUND_ROWS impostando no_found_rows su false, la stessa query ha impiegato 0,0006 secondi.

Il plugin Query Monitor
Grazie al plugin Query Monitor, possiamo confrontare facilmente due query con e senza l’opzione SQL_CALC_FOUND_ROWS

Quando la tabella wp_post contiene migliaia di righe, l’esecuzione della query potrebbe richiedere alcuni secondi.
Quando non abbiamo bisogno di impaginazione, dovremmo sempre impostare no_found_rows su true, rendendo la query molto più veloce.

Cache o Non Cache

WordPress fornisce un sistema di cache integrato pronto all’uso. Sebbene la memorizzazione nella cache generalmente migliora la velocità di caricamento della pagina, questa può causare l’esecuzione di alcune query aggiuntive sul database. Inoltre, ogni volta che viene eseguita una query è possibile che vengano richiesti alcuni dati non necessari.

Fortunatamente, WordPress ci consente di disabilitare la memorizzazione nella cache fornendo tre parametri specifici:

  • cache_results: se memorizzare nella cache le informazioni sui post. Valore predefinito true.
  • update_post_meta_cache: se aggiornare la meta cache dei post. Valore predefinito true.
  • update_post_term_cache: se aggiornare la cache dei termini dei post. Valore predefinito true.

Se è abilitato un sistema di cache persistente, come Memcached, non dobbiamo preoccuparci dei parametri di memorizzazione nella cache perché WordPress imposterà i valori su false di default.

In qualsiasi altra situazione, possiamo creare una query più veloce con il seguente codice:

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 non è disponibile un sistema di cache permanente, le query che restituiscono piccole quantità di dati non dovrebbero essere memorizzate nella cache.

Campi Restituiti

Come regola generale, non dovremmo mai interrogare il database per campi non necessari. La classe WP_Query fornisce l’argomento fields, che consente di limitare i campi restituiti ai campi ID o 'id=>parent'. La documentazione del file di origine definisce l’argomento fields come segue:

Quali campi restituire. Campo singolo o tutti i campi (stringa) o array di campi. ‘id=>parent’ utilizza ‘id’ e ‘post_parent’. Di default restituisce tutti i campi. Accetta ‘ids’, ‘id=>parent’.

La variabile fields accetta 'ids' e 'id=>parent' e di default è * (qualsiasi altro valore), sebbene noterete che di default WordPress imposterà il valore su ids in diverse query.
Infine, possiamo ottimizzare la nostra prima query:

<?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 non sono richiesti campi specifici, limitate i campi restituiti agli ID.

Riepilogo

Considerare la velocità delle query potrebbe non comportare enormi vantaggi per i piccoli siti con poche centinaia di post. Se volete prepararti alla crescita o gestite un sito di grandi dimensioni con query pesanti, dovreste ottimizzare le vostre query WordPress. Query inefficienti possono rallentare notevolmente il caricamento delle pagine, ma con alcune semplici modifiche è possibile velocizzare notevolmente il caricamento del sito.

Carlo Daniele Kinsta

Carlo è cultore appassionato di webdesign e front-end development. Gioca con WordPress da oltre 20 anni, anche in collaborazione con università ed enti educativi italiani ed europei. Su WordPress ha scritto centinaia di articoli e guide, pubblicati sia in siti web italiani e internazionali, che su riviste a stampa. Lo trovate su LinkedIn.