Meta query terribly slow

时间:2014-08-23 作者:user1706680

我有一个自定义的元查询,速度非常慢,甚至直到最后才加载。最多三个arrays 在里面\'meta_query\' 查询工作正常,如果有四个或更多,它就不再工作了。

在搜索我找到的原因时this post 但我绝对不熟悉自定义db查询。

非常感谢您的帮助!非常感谢。

<?php

$args = array(
    \'post_type\' => $post_type,
    \'posts_per_page\' => -1,
    \'meta_query\' => array( 
        \'relation\' => \'OR\',
        array(
           \'key\'=>\'_author\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_publisher\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_1\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_2\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_3\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        )  
      )
  );   

  $posts = new WP_Query($args);

  if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?>

    <li><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></li>

  <?php endwhile; endif; ?>
––––

更新了代码,添加了boger:

页php

<?php

$args = array(
    \'post_type\' => $post_type,
    \'posts_per_page\' => -1,
    \'meta_query\' => array( 
        \'relation\' => \'OR\',
        array(
           \'key\'=>\'_author\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_publisher\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_1\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_2\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_contributor_3\',
           \'value\'=> $author_single["fullname"],
           \'compare\' => \'=\'
        )  
      )
  );   

  add_filter( \'posts_clauses\', \'wpse158898_posts_clauses\', 10, 2 );

  $posts = new WP_Query($args);

  if( $posts->have_posts() ) : while( $posts->have_posts() ) : $posts->the_post(); ?>

      <li><a href="<?php echo get_the_permalink(); ?>"><?php the_title(); ?></a></li>

  <?php endwhile; endif; 

  remove_filter( \'posts_clauses\', \'wpse158898_posts_clauses\', 10 ); ?>
功能。php

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 $meta_query ) {
        // Doesn\'t work for IN, NOT IN, BETWEEN, NOT BETWEEN, NOT EXISTS.
        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 );
    $pieces[\'where\'] = preg_replace( \'/ AND[^w]+wp_postmeta.*$/s\', \'\', $pieces[\'where\'] ); // Zap postmeta clauses.
    return $pieces;
}
–––

$posts->request 输出

$args = array(
    \'post_type\' => $post_type,
    \'posts_per_page\' => -1,
    \'meta_query\' => array( 
        \'relation\' => \'OR\',
        array(
           \'key\'=>\'_author\',
           \'value\'=> "Hanna Meier",
           \'compare\' => \'=\'
        ),
        array(
           \'key\'=>\'_publisher\',
           \'value\'=> "Friedhelm Peters",
           \'compare\' => \'=\'
        )
    )
);   
没有自定义查询

SELECT   wp_vacat_posts.* FROM wp_vacat_posts  INNER JOIN wp_vacat_postmeta ON (wp_vacat_posts.ID = wp_vacat_postmeta.post_id)
INNER JOIN wp_vacat_postmeta AS mt1 ON (wp_vacat_posts.ID = mt1.post_id) WHERE 1=1  AND wp_vacat_posts.post_type = \'product\' AND (wp_vacat_posts.post_status = \'publish\' OR wp_vacat_posts.post_status = \'private\') AND ( (wp_vacat_postmeta.meta_key = \'_author\' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = \'Hanna Meier\')
OR  (mt1.meta_key = \'_publisher\' AND CAST(mt1.meta_value AS CHAR) = \'Friedhelm Peters\') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC   
使用自定义查询

SELECT   wp_vacat_posts.* FROM wp_vacat_posts  
JOIN wp_vacat_postmeta pm on pm.post_id = wp_vacat_posts.ID AND ((pm.meta_key = \'_author\' AND pm.meta_value = \'Hanna Meier\') OR (pm.meta_key = \'_publisher\' AND pm.meta_value = \'Friedhelm Peters\')) WHERE 1=1  AND wp_vacat_posts.post_type = \'product\' AND (wp_vacat_posts.post_status = \'publish\' OR wp_vacat_posts.post_status = \'private\')                 AND ( (wp_vacat_postmeta.meta_key = \'_author\' AND CAST(wp_vacat_postmeta.meta_value AS CHAR) = \'Hanna Meier\')
OR  (mt1.meta_key = \'_publisher\' AND CAST(mt1.meta_value AS CHAR) = \'Friedhelm Peters\') ) GROUP BY wp_vacat_posts.ID ORDER BY wp_vacat_posts.post_date DESC    

5 个回复
最合适的回答,由SO网友:bonger 整理而成

我遇到了这个问题,MySQL似乎不能很好地处理同一个表的多个连接(wp\\u posteta)和或wp在此处生成的ed。我通过重写join和你链接到的帖子中提到的地方来处理它-这里有一个版本应该适用于你的情况(updated for WP 4.1.1) (updated for 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 = \\\'[^\\\']*\\\'/\',
        ), \'(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;
}
然后围绕您的查询:

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

Addendum:

对此的修复,ticket 24093, 没有进入4.0(plus it didn\'t fix this issue anyway), 因此,最初我尝试了上述的通用版本,但尝试这样的解决方案实在是太脆弱了,所以我将其删除。。。

SO网友:David

简而言之,WordPress中的元数据不打算用于关系数据。通过多种条件获取其元数据的帖子并不是元数据背后的想法。因此,查询、表结构和索引没有为此进行优化。

较长的答案:

元查询的结果如下所示:

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
让我们看看MySQL是如何处理这个查询的(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
现在您可以看到,MySQL在wp_posts 并将表连接5次wp_postmeta. 这个type ref 表示MySQL必须检查此表中的所有行,并将非索引列值与where 条款and that for each combination of rows from the previous table. MySQL手册says: »如果使用的键只匹配几行,则这是一种很好的联接类型。«这是第一个问题:在一个平均WordPress系统中,每篇帖子的帖子元数很容易增长到30-40条记录或更多。另一个可能的键meta_key 随着帖子数量的增长而增长。所以如果你有100个帖子,每个帖子都有一个_publisher meta,有100行此值为meta_key 在里面wp_postmeta, 当然

为了处理所有这些可能的结果,mysql创建了一个临时表(using temporary). 如果此表变大,服务器通常会将其存储在磁盘上,而不是存储在内存中。另一个可能的瓶颈。

可能的解决方案

如现有答案所述,您可以尝试自己优化查询。这可能会很好地解决您的问题,但随着预计后/预计后表格的增长,可能会带来麻烦。

但是如果你想使用WordPress查询API,你应该考虑使用分类法来存储你想搜索帖子的数据。

SO网友:Aj Best

这可能有点晚了,但我遇到了同样的问题。当构建一个插件来处理属性搜索时,我的高级搜索选项将为每个帖子查询多达20个不同的元条目,以找到与搜索条件匹配的条目。

我的解决方案是使用$wpdb 全球的我分别查询每个元条目并存储post_ids 符合每个标准的帖子。然后我在每个匹配的集合上进行交叉,得出post_ids 符合所有标准。

我的情况有点简单,因为我没有OR 我需要说明的元素,但它们可以很容易地包含在内。根据查询的复杂程度,这是一个有效且快速的解决方案。尽管如此,我承认与能够进行真正的关系查询相比,这是一个糟糕的选择。

下面的代码已经从我使用的代码中大大简化了,但您可以从中获得想法。

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;
}

}

SO网友:Rick James

wp\\U Posteta的索引效率低下。以下是对此类治疗方法的讨论,以及推荐的治疗方法:

http://mysql.rjweb.org/doc.php/index_cookbook_mysql#speeding_up_wp_postmeta

SO网友:Tomás Cot

口服补液盐真的很贵。

您的钥匙太多了,但假设您现在无法更改。你可以做的另一件事,不需要太多的编码,就是改变你收到的帖子的数量,改变\'posts_per_page\' 到10或更大的数字,然后看看性能会有多大变化。

结束

相关推荐