提高多分类的WP_QUERY性能

时间:2011-07-13 作者:Marcus Downing

我有一个具有多个自定义分类法的站点,并且发现该站点中最慢的部分之一正试图同时使用OR查询其中的多个分类法。我用的是WP_Query 像这样:

array(
  \'tax_query\' => array(
    \'relation\' => \'OR\',
    array(\'taxonomy\' => \'tax1\', \'field\' => \'slug\', \'terms\' => \'term1\'),
    array(\'taxonomy\' => \'tax2\', \'field\' => \'slug\', \'terms\' => \'term2\'),
    array(\'taxonomy\' => \'tax3\', \'field\' => \'slug\', \'terms\' => \'term3\'),
    array(\'taxonomy\' => \'tax4\', \'field\' => \'slug\', \'terms\' => \'term4\'),
  )
)
它生成的SQL需要不可接受的6秒钟才能运行:

SELECT SQL_CALC_FOUND_ROWS wp_posts.* FROM wp_posts  
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) 
INNER JOIN wp_term_relationships AS tt1 ON (wp_posts.ID = tt1.object_id) 
INNER JOIN wp_term_relationships AS tt2 ON (wp_posts.ID = tt2.object_id) 
INNER JOIN wp_term_relationships AS tt3 ON (wp_posts.ID = tt3.object_id) 
WHERE 1=1 AND wp_posts.ID NOT IN (70) 
AND (wp_term_relationships.term_taxonomy_id IN (23) 
  OR tt1.term_taxonomy_id IN (5)
  OR tt2.term_taxonomy_id IN (11)
  OR tt3.term_taxonomy_id IN (10) ) 
AND (wp_posts.post_status = \'publish\') 
GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 500
但这个等效查询需要更好的0.29秒:

SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts
INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)
WHERE 1=1 AND wp_posts.ID NOT IN (70)
AND (wp_term_relationships.term_taxonomy_id IN (23, 5, 11, 10)) 
AND (wp_posts.post_status = \'publish\')
GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 500
显然,多重连接使其速度比需要的慢。SQL不关心术语是否来自不同的分类法,但是WP_Query 是因为他们被slug查到了。有没有办法说服WP_Query 生成更接近第二个的东西?

(注意,为了保护我的客户,上述内容已匿名)

2 个回复
SO网友:Marcus Downing

我有一个解决方案,但这真的很难看。我很想听到更好的,但我不确定这是否可能。

WP_Query::get_posts() 呼叫parse_tax_query() 两次:第一次接近开始,然后在从中获取SQL之前再次。没有一个钩子可以让我拦截和调整$tax_query 为了及时调整SQL,我不得不分两部分来完成。

上的操作pre_get_posts, 即将开始get_posts() 使用识别分类查询\'relation\' => \'OR\' 并对其进行简化,使其仅在上生成一个连接wp_term_relationships. 同时,它存储了“WP\\u Query”对象内所有分类法中已解析的ID列表,以供以后使用。

过滤器打开posts_where_paged, 年晚些时候query_posts(), 检查已保存的ID列表并替换联接上的条件。

以下是代码:

add_action(\'pre_get_posts\', \'wp_query__pre\');
function wp_query__pre ($wp_query) {
  if (!isset($wp_query->query[\'tax_query\'])) return;
  if ($wp_query->query[\'tax_query\'][\'relation\'] != \'OR\') return;

  $allterms = array();
  foreach ($wp_query->tax_query->queries as $query) {
    $tax = $query[\'taxonomy\'];
    $terms = $query[\'terms\'];
    $wp_query->tax_query->_transform_terms($terms, $query[\'taxonomy\'], $query[\'field\'], \'term_taxonomy_id\');
    $allterms = array_merge($allterms, $terms);
  }

  $tax_query = array(array(
      \'taxonomy\' => $tax,
      \'terms\' => $terms,
      \'operator\' => \'IN\',
      \'include_children\' => 0,
      \'field\' => \'term_taxonomy_id\',
    ));

  $wp_query->query[\'tax_query\'] = $tax_query;
  $wp_query->query_vars[\'tax_query\'] = $tax_query;
  $wp_query->tax_query = new WP_Tax_Query($tax_query);
  $wp_query->saved_tax_terms = $allterms;
}

add_filter(\'posts_where_paged\', \'wp_query__where\', 10, 2);
function wp_query__where ($where, $wp_query) {
  if (!empty($wp_query->saved_tax_terms)) {
    $terms = implode(", ", $wp_query->dft_tax_terms);
    $where = preg_replace("!term_taxonomy_id IN \\([^)]*\\)!", "term_taxonomy_id IN ($terms)", $where);
  }
  return $where;
}
请注意,代码尚未经过彻底测试,毫无疑问包含各种各样的bug。我可能不会处理任何更复杂的问题。

这种方法的优点是,它不需要代码的其余部分了解它。只需使用OR 它将得到提升和优化。它并没有像我希望的那样提供很大的速度提升,但它确实是一个进步。

我想知道WordPress团队是否应该在核心代码中包含这样的内容。

<小时>Update: 为了适应如此丑陋的黑客行为,WordPress 3.2上打破了这一点。我正在寻找解决办法。

SO网友:postpostmodern

您可以使用posts\\u join和posts\\u where过滤器让Wordpress使用更高效的查询,这些内容大致如下:

add_filter( \'posts_join\', \'tax_posts_join\', 10, 2 );
add_filter( \'posts_where\', \'tax_posts_where\', 10, 2 );
add_filter( \'posts_request\', \'tax_posts_request\' );

function tax_posts_join( $sql, $wp_query ){
    if( $tax_ids = $wp_query->get(\'term_taxonomy_ids_in\') )
        $sql .= " INNER JOIN wp_term_relationships ON ( wp_posts.ID = wp_term_relationships.object_id )";

    return $sql;
}

function tax_posts_where( $sql, $wp_query ){
    if( $tax_ids = $wp_query->get(\'term_taxonomy_ids_in\') ){
        $tax_ids = implode( \', \', $tax_ids );
        $sql .= " AND ( wp_term_relationships.term_taxonomy_id IN (".$tax_ids.") ) ";
    }   

    return $sql;
}

function tax_posts_request( $sql ){
    //var_dump( $sql );
    return $sql;
}


$args = array(
    \'term_taxonomy_ids_in\' => array(23, 5, 11, 10)
);

$tax_posts = new WP_Query( $args );
可以稍微清理一下,可能在创建sql之前清理分类ID,您可能需要posts_groupby 也要过滤,但应该让你朝着正确的方向前进。http://codex.wordpress.org/Custom_Queries

结束

相关推荐

提高大型数据库的SQL查询速度?

我正在使用一个名为feedwordpress 为了在wordpress上运行一个类似行星的网站(请参见here).这个插件非常棒,除了一件事——它会占用我的(VPS)服务器,每周提交一次。在最近与webadmin的电子邮件交流中,他写道:看起来mysql资源使用量的增加是由r-bloggers运行的查询速度慢造成的。com。这是正在生成的一些日志的副本。您需要进一步优化此网站和数据库,以使其尽可能高效地运行。如果已经进行了这些更改,您最好的选择是考虑对VPS进行大规模升级,因为您的站点需要和看到高级别的资