WordPress Hosting

¿Sabes que se crean consultas duplicadas a la base de datos que podrías eliminar fácilmente y sin plugins?

WordPress hace muchas más consultas a la base de datos de las que parece, en una página normalita pueden ser 30, 40 o más de 100. Pero el problema no es ese número en sí, sino las que se repiten, como la misma SELECT lanzada cuatro veces, ocho veces o más, por culpa de un tema mal hecho, un plugin que no cachea bien o un bucle que pide los mismos datos una y otra vez.

Cada query duplicada es trabajo que la base de datos hace dos o más veces sin necesidad. Si tu página de inicio dispara la misma consulta a wp_options diez veces y tienes 5.000 visitas al día, son 50.000 queries innecesarias diarias. En hosting compartido se nota, cuando tienes picos de tráfico ya ni te cuento.

Para detectarlas no hace falta ningún plugin, basta con activar SAVEQUERIES y meter un pequeño código (que te voy a regalar) en un mu-plugin y tienes el resumen al pie de cada página. Treinta segundos de trabajo y listo. ¿Te animas?

Activar SAVEQUERIES

SAVEQUERIES es una constante de WordPress que guarda en memoria todas las queries de la petición, con el tiempo que tarda cada una. Por defecto está desactivada porque consume memoria, pero activarla es fácil, solo tiene que añadir esto a tu wp-config.php, justo antes de la línea que dice «Eso es todo, deja de editar»:

define( 'SAVEQUERIES', true );

Advertencia: debes activarla solo en local, en staging o en producción durante el rato justo del diagnóstico. Si la dejas activa en una web con tráfico real te comes la memoria del servidor sin ganar nada a cambio.

Código para mostrar queries duplicadas

Crea un archivo en /wp-content/mu-plugins/ llamado ayudawp-debug-queries.php. Si no tienes esa carpeta, créala tú y pega esto dentro:

<?php
/**
 * Plugin Name: Debug de queries duplicadas
 * Plugin URI: https://servicios.ayudawp.com
 * Description: Muestra un en el pie de página un resumen de queries duplicadas. Solo lo ven los admins y solo cuando está activo SAVEQUERIES.
 * Version: 1.0
 * Author: Fernando Tellado
 * Author URI: https://ayudawp.com
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'wp_footer', 'ayudawp_mostrar_queries_duplicadas', 9999 );

function ayudawp_mostrar_queries_duplicadas() {

    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    if ( ! defined( 'SAVEQUERIES' ) || ! SAVEQUERIES ) {
        return;
    }

    global $wpdb;

    if ( empty( $wpdb->queries ) ) {
        return;
    }

    $contador = array();
    $tiempos  = array();
    // $callers = array(); // Descomenta para guardar quién dispara cada query.

    foreach ( $wpdb->queries as $query ) {

        $sql    = $query[0];
        $tiempo = $query[1];
        // $caller = isset( $query[2] ) ? $query[2] : ''; // Descomenta junto con $callers.

        // Normaliza la query para que los duplicados con distintos valores también coincidan
        $sql_norm = preg_replace( '/\d+/', 'N', $sql );
        $sql_norm = preg_replace( "/'[^']*'/", "'X'", $sql_norm );
        $sql_norm = trim( preg_replace( '/\s+/', ' ', $sql_norm ) );

        if ( ! isset( $contador[ $sql_norm ] ) ) {
            $contador[ $sql_norm ] = 0;
            $tiempos[ $sql_norm ]  = 0;
            // $callers[ $sql_norm ] = $caller;
        }

        $contador[ $sql_norm ]++;
        $tiempos[ $sql_norm ] += $tiempo;
    }

    // Mantiene solo las queries que aparecen más de una vez
    $duplicadas = array_filter(
        $contador,
        function ( $n ) {
            return $n > 1;
        }
    );

    arsort( $duplicadas );

    $total_queries = count( $wpdb->queries );
    $tiempo_bd     = round( $wpdb->timer_stop, 4 );

    if ( empty( $duplicadas ) ) {
        printf(
            '<div style="position:fixed;bottom:0;left:0;right:0;background:#0a4d1f;color:#fff;padding:10px;font:12px monospace;z-index:99999;">Sin queries duplicadas. Total: %d queries en %ss.</div>',
            (int) $total_queries,
            esc_html( $tiempo_bd )
        );
        return;
    }

    echo '<div style="position:fixed;bottom:0;left:0;right:0;max-height:50vh;overflow:auto;background:#1a1a1a;color:#fff;padding:15px;font:12px monospace;z-index:99999;border-top:3px solid #ff6b6b;">';

    printf(
        '<strong style="color:#ff6b6b;">%d tipos de queries duplicadas</strong> · Total: %d queries · Tiempo BD: %ss<br><br>',
        count( $duplicadas ),
        (int) $total_queries,
        esc_html( $tiempo_bd )
    );

    foreach ( $duplicadas as $sql => $veces ) {
        $tiempo_ms = round( $tiempos[ $sql ] * 1000, 2 );
        $sql_corta = strlen( $sql ) > 250 ? substr( $sql, 0, 250 ) . '...' : $sql;
        printf(
            '<div style="margin-bottom:8px;padding-bottom:8px;border-bottom:1px solid #333;"><span style="color:#ff6b6b;font-weight:bold;">×%d</span> <span style="color:#888;">[%sms]</span> %s</div>',
            (int) $veces,
            esc_html( $tiempo_ms ),
            esc_html( $sql_corta )
        );
    }

    echo '</div>';
}

Lo que hace ese código:

  • Solo se ejecuta para usuarios con permisos de administrador (manage_options).
  • Solo entra si SAVEQUERIES está activo.
  • Normaliza las consultas sustituyendo números y cadenas variables por marcadores, así detecta como duplicadas las que solo cambian en los valores (un SELECT por ID 5 y otro por ID 7 cuentan como la misma).
  • Imprime al pie de página de la parte visible de la web un resumen con cuántas veces se repite cada query y cuánto tiempo total se va en cada una.

Cómo interpretar el resultado

Al cargar cualquier página en portada habiéndote conectado como admin te aparece una franja al pie con algo así:

3 tipos de queries duplicadas · Total: 47 queries · Tiempo BD: 0,0234s

×6 [12,4ms] SELECT option_value FROM wp_options WHERE option_name = 'X' LIMIT N
×4 [3,2ms]  SELECT * FROM wp_postmeta WHERE post_id = N AND meta_key = 'X'
×3 [1,8ms]  SELECT post_status FROM wp_posts WHERE ID = N

Y lo que significa el ejemplo es esto:

  • La consulta a wp_options repetida seis veces es casi seguro un get_option() llamado en varios hooks sin cachear el resultado en una variable estática. Plugin o tema mal hecho.
  • La de postmeta repetida cuatro veces es la señal clásica del problema N+1: falta una llamada a update_post_meta_cache() antes de un bucle de posts.
  • La tercera puede ser un get_post_status() o similar dentro de otro bucle.

Qué hacer cuando encuentras duplicadas

Depende de quién las dispara y de tu nivel de acceso al código:

  • Si la query se repite por un plugin de terceros busca la opción que la lanza y mira si puedes desactivarla. A veces es un widget, una barra lateral o una sección de la cabecera que tampoco te aporta gran cosa.
  • Si es de tu propio tema o plugin cachea el resultado en una variable estática dentro de la función, o mejor todavía en wp_cache_set() si tienes caché de objetos (Redis o Memcached).
  • Para el patrón N+1 de postmeta añade update_post_meta_cache( $post_ids ) antes del bucle. Una sola query precarga toda la meta de los posts de golpe.
  • Para llamadas repetidas a get_option() agrupa varias opciones en un único array, o cachéalas en memoria de la petición con wp_cache_set().

Si te aparece una query rara y no sabes de dónde viene, en el código hay dos líneas comentadas con $callers y $caller. Descoméntalas temporalmente y luego cambia el último printf para que también imprima $callers[ $sql ]. Verás el callstack completo, función por función, hasta saber exactamente quién dispara cada consulta.

Y acuérdate de quitarlo cuando termines

Cuando hayas terminado el diagnóstico, dos cosas:

  1. Borra o renombra el archivo del mu-plugin.
  2. Quita la línea define( 'SAVEQUERIES', true ); de wp-config.php.

Si dejas SAVEQUERIES activo en producción cada petición guarda el array completo de queries en memoria durante todo el render. En sitios con tráfico real son 200 KB extra por petición por lo menos. No te tira el sitio pero es memoria que consumes sin necesidad.

Para diagnósticos más completos (callstack siempre visible, agrupación por componente, hooks ejecutados, monitorización de AJAX y REST API) la herramienta de referencia sigue siendo Query Monitor. Pero para una revisión rápida y puntual sin instalar nada este snippet va de sobra.

Compartir en redes
Resumir con IA

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en las estrellas para valorarlo!

Promedio de puntuación 5 / 5. Total de votos: 4

¡Todavía no hay votos! Sé el primero en valorar este contenido.

Ya que has encontrado útil este contenido...

¡Sígueme en las redes sociales!

¿Te gustó este artículo? ¡Ni te imaginas lo que te estás perdiendo en YouTube!



Sobre el autor

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio