Saltar al contenido

Wordpress – Meta consulta terriblemente lenta

Solución:

Me he encontrado con este problema y parece que MySQL no maneja bien las múltiples uniones a la misma tabla (wp_postmeta) y OR-ed WHERE que WP genera aquí. Lo resolví reescribiendo la combinación y, como se menciona en la publicación a la que enlaza, aquí hay una versión que debería funcionar en su caso (actualizado para WP 4.1.1) (actualizado para WP 4.2.4):

function wpse158898_posts_clauses( $pieces, $query ) {
    global $wpdb;
    $relation = isset( $query->meta_query->relation ) ? $query->meta_query->relation : 'AND';
    if ( $relation != 'OR' ) return $pieces; // Only makes sense if OR.
    $prepare_args = array();
    $key_value_compares = array();
    foreach ( $query->meta_query->queries as $key => $meta_query ) {
        if ( ! is_array( $meta_query ) ) continue;
        // Doesn't work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS.
        if ( $meta_query['compare'] === 'EXISTS' ) {
            $key_value_compares[] = '(pm.meta_key = %s)';
            $prepare_args[] = $meta_query['key'];
        } else {
            if ( ! isset( $meta_query['value'] ) || is_array( $meta_query['value'] ) ) return $pieces; // Bail if no value or is array.
            $key_value_compares[] = '(pm.meta_key = %s AND pm.meta_value ' . $meta_query['compare'] . ' %s)';
            $prepare_args[] = $meta_query['key'];
            $prepare_args[] = $meta_query['value'];
        }
    }
    $sql=" JOIN " . $wpdb->postmeta . ' pm on pm.post_id = ' . $wpdb->posts . '.ID'
        . ' AND (' . implode( ' ' . $relation . ' ', $key_value_compares ) . ')';
    array_unshift( $prepare_args, $sql );
    $pieces['join'] = call_user_func_array( array( $wpdb, 'prepare' ), $prepare_args );
    // Zap postmeta clauses.
    $wheres = explode( "n", $pieces[ 'where' ] );
    foreach ( $wheres as &$where ) {
        $where = preg_replace( array(
            '/ +( +' . $wpdb->postmeta . '.meta_key .+) *$/',
            '/ +( +mt[0-9]+.meta_key .+) *$/',
            '/ +mt[0-9]+.meta_key = '[^']*"https://foroayuda.es/",
        ), '(1=1)', $where );
    }
    $pieces[ 'where' ] = implode( '', $wheres );
    $pieces['orderby'] = str_replace( $wpdb->postmeta, 'pm', $pieces['orderby'] ); // Sorting won't really work but at least make it not crap out.
    return $pieces;
}

y luego alrededor de su consulta:

  add_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10, 2 );
  $posts = new WP_Query($args);
  remove_filter( 'posts_clauses', 'wpse158898_posts_clauses', 10 );

Apéndice:

La solución para esto, el ticket 24093, no llegó a 4.0 (además, no solucionó este problema de todos modos), por lo que originalmente intenté una versión generalizada de lo anterior, pero en realidad es demasiado descabellado intentar una solución de este tipo, así que la eliminé …

La respuesta corta es que los metadatos en WordPress no están destinados a ser utilizados para datos relacionales. Obtener publicaciones por varias condiciones a sus metadatos no es la idea detrás de los metadatos. Por lo tanto, las consultas, las estructuras de las tablas y los índices no están optimizados para eso.

La respuesta más larga:

El resultado de su Meta-Consulta es algo así:

SELECT   wp_4_posts.* FROM wp_4_posts  
INNER JOIN wp_4_postmeta ON (wp_4_posts.ID = wp_4_postmeta.post_id)
INNER JOIN wp_4_postmeta AS mt1 ON (wp_4_posts.ID = mt1.post_id)
INNER JOIN wp_4_postmeta AS mt2 ON (wp_4_posts.ID = mt2.post_id)
INNER JOIN wp_4_postmeta AS mt3 ON (wp_4_posts.ID = mt3.post_id)
INNER JOIN wp_4_postmeta AS mt4 ON (wp_4_posts.ID = mt4.post_id) 
WHERE 1=1  
AND wp_4_posts.post_type="post" 
AND (wp_4_posts.post_status="publish" OR wp_4_posts.post_status="private") 
AND ( (wp_4_postmeta.meta_key = '_author' AND CAST(wp_4_postmeta.meta_value AS CHAR) = 'Test')
OR  (mt1.meta_key = '_publisher' AND CAST(mt1.meta_value AS CHAR) = 'Test')
OR  (mt2.meta_key = '_contributor_1' AND CAST(mt2.meta_value AS CHAR) = 'Test')
OR  (mt3.meta_key = '_contributor_2' AND CAST(mt3.meta_value AS CHAR) = 'Test')
OR  (mt4.meta_key = '_contributor_3' AND CAST(mt4.meta_value AS CHAR) = 'Test') ) GROUP BY wp_4_posts.ID ORDER BY wp_4_posts.post_date DESC

Echemos un vistazo a cómo MySQL maneja esta consulta (EXPLAIN):

    id      select_type     table           type    possible_keys                   key                     key_len ref                             rows    Extra
    1       SIMPLE          wp_4_posts      range   PRIMARY,type_status_date        type_status_date        124     NULL                            5       Using where; Using temporary; Using filesort
    1       SIMPLE          wp_4_postmeta   ref     post_id,meta_key                post_id                  8      wordpress.wp_4_posts.ID         1
    1       SIMPLE          mt1             ref     post_id,meta_key                post_id                  8      wordpress.wp_4_posts.ID         1
    1       SIMPLE          mt2             ref     post_id,meta_key                post_id                  8      wordpress.mt1.post_id           1       Using where
    1       SIMPLE          mt3             ref     post_id,meta_key                post_id                  8      wordpress.wp_4_posts.ID         1
    1       SIMPLE          mt4             ref     post_id,meta_key                post_id                  8      wordpress.wp_4_postmeta.post_id 1       Using where

Ahora lo que puede ver, MySQL haga una selección en wp_posts y se une 5 veces a la mesa wp_postmeta. los escribe ref dice que MySQL tiene que examinar todas las filas en esta tabla, haciendo coincidir el índice (post_id, meta_key) comparando un valor de columna no indexado con su where cláusula, y que para cada combinación de filas de la tabla anterior. El manual de MySQL dice: “Si la clave que se usa coincide solo con unas pocas filas, este es un buen tipo de unión”. Y ese es el primer problema: en un sistema WordPress promedio, la cantidad de post-metas por publicación puede crecer fácilmente hasta 30-40 registros o más. La otra clave posible meta_key crece con el recuento de publicaciones. Entonces, si tienes 100 publicaciones y cada una tiene un _publisher meta, hay 100 filas con este valor como meta_key en wp_postmeta, por supuesto.

Para manejar todos estos posibles resultados, mysql crea una tabla temporal (using temporary). Si esta tabla se vuelve demasiado grande, el servidor generalmente la almacena en el disco en lugar de en la memoria. Otro posible cuello de botella.

Soluciones posibles

Como se describe en las respuestas existentes, puede intentar optimizar la consulta por su cuenta. Eso puede funcionar bien para sus inquietudes, pero puede causar problemas a medida que crecen las tablas post / postmeta.

Pero si desea usar la API de consultas de WordPress, debe considerar usar taxonomías para almacenar los datos por los que desea buscar publicaciones.

Esto puede ser un poco tarde para el juego, pero me encontré con el mismo problema. Al crear un complemento para manejar la búsqueda de propiedades, mi opción de búsqueda avanzada consultaría hasta 20 metaentradas diferentes para cada publicación para encontrar aquellas que coincidan con los criterios de búsqueda.

Mi solución fue consultar la base de datos directamente usando el $wpdb global. Consulté cada entrada de meta individualmente y almacené el post_ids de las publicaciones que coinciden con cada criterio. Luego hice una intersección en cada uno de los conjuntos combinados para obtener el post_ids que cumplía con todos los criterios.

Mi caso era algo sencillo porque no tenía ninguna OR elementos que necesitaba tener en cuenta, pero que podrían incluirse con bastante facilidad. Dependiendo de la complejidad de su consulta, esta es una solución rápida y que funciona. Aunque admito que es una mala opción en comparación con poder hacer una verdadera consulta relacional.

El siguiente código se ha simplificado en gran medida de lo que usé, pero puede obtener una idea de él.

class property_search{

public function get_results($args){
    $potential_ids=[];
    foreach($args as $key=>$value){
        $potential_ids[$key]=$this->get_ids_by_query("
            SELECT post_id
            FROM wp_postmeta
            WHERE meta_key = '".$key."'
            AND CAST(meta_value AS UNSIGNED) > '".$value."'
        ");//a new operator would need to be created to handle each type of data and comparison. 
    }

    $ids=[];
    foreach($potential_ids as $key=>$temp_ids){
        if(count($ids)==0){
            $ids=$temp_ids;
        }else{
             $ids=array_intersect($ids,$temp_ids);
        }
    }

    $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
    $args = array(
        'posts_per_page'=> 50,
        'post_type'=>'property',
        'post_status'=>'publish',
        'paged'=>$paged,
        'post__in'=>$ids,
    );
    $search = new WP_Query($args);
    return $search;
}

public function get_ids_by_query($query){
    global $wpdb;
    $data=$wpdb->get_results($query,'ARRAY_A');
    $results=[];
    foreach($data as $entry){
        $results[]=$entry['post_id'];
    }
    return $results;
}

}
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)


Tags : /

Utiliza Nuestro Buscador

Deja una respuesta

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