Categories
PHP WordPress

Tip: Optimización de consultas SQL en plugins de WordPress

Una forma para reducir el número de consultas en WordPress es evitar varias consultas pequeñas si una sola puede hacer todo el trabajo. En este caso se muestra el uso correcto de la función get_permalink.

Durante el tiempo que no hubo actividad alguna en este blog y por algunos trabajos que me encomendaron relacionados al desarrollo de plugins y modificación del código de WordPress, he visto algo que se repite casi en todos los plugins que he visto hasta el momento: usan varias consultas pequeñas cuando sólo una puede hacer el trabajo (no tengo idea de porqué hacen las cosas de ese modo).

php:

$a = query("select ID from tabla1 ...");
$b = array();
foreach($a as $v) {
        $b[] = query("select ... from tabla2 where ID = $v");
}

El problema del código mostrado es que se hacen consultas innecesarias a la base de datos cuando en este caso un simple JOIN serviría para el mismo propósito.

Como ejemplo voy a poner dos plugins que acompañaron a este blog desde hace más de un año y por los que el número de consultas SQL aunmentaba en 55 para generar la página principa. Éstos son:

  • Real Fast Latest Comments: En este caso el plugin realiza una consulta para obtener las entradas que recibieron comentarios recientemente y luego itera para mostrar los datos devueltos:

    php:

    function rflc_show_comments($comment_limit = 5, $show_trackbacks = false) {
            global $wpdb;

            if(!$show_trackbacks) {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1' AND $wpdb->comments.comment_type
                                                    NOT LIKE '%back%' ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            } else {
                    $activity = $wpdb->get_results("SELECT $wpdb->comments.comment_date, $wpdb->comments.comment_author,
                                                    $wpdb->comments.comment_ID, $wpdb->posts.post_title,
                                                    $wpdb->posts.ID FROM $wpdb->comments INNER JOIN
                                                    $wpdb->posts ON $wpdb->posts.ID = $wpdb->comments.comment_post_ID WHERE
                                                    $wpdb->comments.comment_approved = '1'
                                                    ORDER BY $wpdb->comments.comment_date DESC
                                                    LIMIT $comment_limit");
            }
           
            if($activity) {
                    echo '<ul>';
                    foreach($activity as $comment) {

                            echo '<li>'.wp_specialchars($comment->comment_author).' en: <a href="'. get_permalink($comment->ID) .'#comment-'. $comment->comment_ID .'">'. wp_specialchars($comment->post_title) .'</a></li>' . "\n";

                    }
                    echo '</ul>';
            }
    }

    El problema en este caso es que para mostrar el enlace (permalink) de una entrada, el plugin invoca en cada iteración a la función get_permalink, la misma que hace una nueva consulta (o varias dependiendo de la estructura de permalinks) para recuperar la entrada y formatear el enlace, esto pasa siempre y cuando el argumento pasado no sea un objeto o la entrada ya se encuentre en la caché de objetos.

  • Related Posts: El problema es el mismo que se comenta en el anterior, a continuación muestro la parte relevante:

    php:

    $sql = "SELECT ID, post_title, post_content,"
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') AS score "
             . "FROM $wpdb->posts WHERE "
             . "MATCH (post_name, post_content) "
             . "AGAINST ('$terms') "
             . "AND post_date <= '$now' "
             . "AND (post_status IN ( 'publish',  'static' ) && ID != '$post->ID') ";
    if ($show_pass_post=='false') { $sql .= "AND post_password ='' "; }
    $sql .= "ORDER BY score DESC LIMIT $limit";
    $results = $wpdb->get_results($sql);
    $output = '';
    if ($results) {
            foreach ($results as $result) {
                    $title = stripslashes(apply_filters('the_title', $result->post_title));

                    $permalink = get_permalink($result->ID);

                    $post_content = strip_tags($result->post_content);
                    $post_content = stripslashes($post_content);
                    $output .= $before_title .'<a href="'. $permalink .'" rel="bookmark" title="Permanent Link: ' . $title . '">' . $title . '</a>' . $after_title;

                    if ($show_excerpt=='true') {
                            $words=split(" ",$post_content);
                            $post_strip = join(" ", array_slice($words,0,$len));
                            $output .= $before_post . $post_strip . $after_post;
                    }
            }
            echo $output;
    }

Para evitar este comportamiento pero con algunas posibles consecuencias no deseadas**, se debe recuperar también el campo post_name en la primera consulta de ambos casos y a continuación invocar a la función get_permalink con un objeto como parámetro -- get_permalink($comment); y get_permalink($result); respectivamente.

**: Al usar la función get_permalink con un objeto como parámetro, éste se almacena en el caché de objetos y cualquier función que haga uso de get_post puede mostrar entradas "incompletas". Una forma de evitar esto es recuperar todos los campos de la tabla posts o quitar la entrada almacenada en cache luego de invocar a la función get_permalink.

2 replies on “Tip: Optimización de consultas SQL en plugins de WordPress”

Precisamente es algo que quería retocar en UTI, al ser la base de datos grande y compleja, lo que ralentiza la generación de la página con la búsqueda avanzada (si no está en la caché).
http://unatemporadaenelinfierno.net/advanced-search/

En cambio, con el plugin ELA, es más rápido:
http://unatemporadaenelinfierno.net/archives/

Llevo mucho tiempo quejándome de que las búsquedas han de mejorarse mucho en todos los CMS bitacoriles, empezando por WP (en ello están para la 2.4 leí).

Pasan los años, y las bitácoras que perduran comienzan a tener unas bases de datos de entidad, que no son suficientemente bien gestionadas, de ahí que para montar un digital "casero" se recurra a otras opciones.

Es un tema en el que hay mucho por hacer en los CMS habituales, al que bien pocos prestan la debida atención.

Comments are closed.