web-dev-qa-db-ja.com

複数の分類法に対するWP_Queryパフォーマンスの向上

私はいくつかのカスタム分類法を持つサイトを持っています、そしてサイトの最も遅い部分の1つが一度にこれらのいくつかにまたがって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は、スラグによって検索されるためです。 2番目のものに近いものを生成するようにWP_Queryを説得する方法はありますか?

(上記は私のクライアントを保護するために匿名化されています)

2
Marcus Downing

私は解決策を持っていますが、それは本当に醜いものです。もっと良いものを聞きたいのですが、それが可能かどうかはわかりません。

WP_Query::get_posts()parse_tax_query()を2回呼び出します。最初は先頭付近、次にSQLを取得する直前です。 SQLを調整するのに間に合うように$tax_queryの値を傍受して調整することを可能にする単一のフックはないので、代わりに2つの部分でそれを行う必要がありました。

  • get_posts()の先頭近くにあるpre_get_postsに対するアクションは、'relation' => 'OR'による分類クエリを認識し、それらがwp_term_relationshipsに対して1つの結合のみを生成するようにそれらを単純化します。同時に、後で使用するために 'WP_Query'オブジェクト内のすべての分類法からIDの解決済みリストを保管します。

  • query_posts()のずっと後のposts_where_pagedに対するフィルタは、保存された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;
}

コードはまだ徹底的にテストされていないこと、そして疑いなくあらゆる種類のバグが含まれていることに注意してください。私はおそらくもっと複雑な問い合わせには対処しないでしょう。

このアプローチの利点は、それについて何かを知るためにコードの残りの部分を必要としないということです。分類クエリをORでコーディングするだけで、ピックアップされ最適化されます。それは私が望んでいたほど大きなスピードの向上をもたらすものではありませんでしたが、それは明らかな改善でした。

WordPressチームがコアコードにこのようなものを含めるべきではないかと思います。


更新:このような醜いハックにふさわしいように、これはWordPress 3.2で壊れています。私は修正を検討しています。

2
Marcus Downing

次の行に沿ったものとして、posts_joinおよびposts_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

1
postpostmodern