web-dev-qa-db-ja.com

複数の分類用語のMySQLクエリを最適化する

モデレータのメモ: タイトルはもともと「クエリ/データベースの最適化」でした)

私はユーザーが最大4つのカスタム分類法から用語を選択することを可能にするカスタム「フィルター」検索パネルのための機能を書きました。データベースに対して直接クエリを実行していますが、クエリの実行時間は平均で0.5秒です(4つすべての分類法の用語と1つの結果が返されます)。

これは私にはかなり遅いようです。私は、これをより効率的に/より速くするためにクエリまたはデータベースさえも最適化するために私にできることがあるのではないかと思いました。おそらく意見を書いていますか?私はMS-SQLの経験がありますが、MySQLの経験はそれほど多くありません。

これが私の機能コードです。

    function filter_resources($phase,$wa,$aus,$topics){
    global $wpdb;
    $querystr="
    SELECT * 
        FROM $wpdb->posts A
            LEFT JOIN $wpdb->term_relationships B ON(A.ID = B.object_id)
            LEFT JOIN $wpdb->term_taxonomy C ON(B.term_taxonomy_id = C.term_taxonomy_id)
            LEFT JOIN $wpdb->terms D ON(C.term_id = D.term_id)

        LEFT JOIN $wpdb->term_relationships BB ON(A.ID = BB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CC ON(BB.term_taxonomy_id = CC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DD ON(CC.term_id = DD.term_id)

        LEFT JOIN $wpdb->term_relationships BBB ON(A.ID = BBB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CCC ON(BBB.term_taxonomy_id = CCC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DDD ON(CCC.term_id = DDD.term_id)

        LEFT JOIN $wpdb->term_relationships BBBB ON(A.ID = BBBB.object_id)
            LEFT JOIN $wpdb->term_taxonomy CCCC ON(BBBB.term_taxonomy_id = CCCC.term_taxonomy_id)
            LEFT JOIN $wpdb->terms DDDD ON(CCCC.term_id = DDDD.term_id)

        WHERE A.post_type = 'resources' 
            AND A.post_status = 'publish'
            AND C.taxonomy = 'phase-of-learning'
            AND D.term_id = '$phase'
            AND CC.taxonomy = 'wa-curriculum'
            AND DD.term_id = '$wa'
            AND CCC.taxonomy = 'australian-curriculum'
            AND DDD.term_id = '$aus'
            AND CCCC.taxonomy = 'topics'
            AND DDDD.term_id = '$topics'
        ORDER BY A.post_date DESC";
    return $wpdb->get_results($querystr,OBJECT);
}

ありがとうございます。

4
goatlady

これは本当にMySQLの質問ですが、WordPressのSQLスキーマを理解するのに役立ちます。また、 StackOverflow に送っていただくよりも、SQLクエリを最適化するのが大好きです。ここでお答えします。あなたはまだ他の意見を得るためにあそこにそれを投稿したいと思うかもしれません。

そして、私はあなたの要求を完全には理解していませんが、私はあなたが何を求めているのか理解していると思います。私はあなたのデータを持っていないので、それが実際にうまくいくことを私がするのは少し難しいですが、私が言ったように、私はそれがあなたのニーズを満たしていると思います:

function filter_resources($phase,$wa,$aus,$topics){
  global $wpdb;
  $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN (%d,%d,%d,%d)
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN (
    'phase-of-learning/%s',
    'wa-curriculum/%s',
    'australian-curriculum/%s',
    'topics/%s'
  )
GROUP BY
  p.ID
HAVING
  COUNT(*)=4
ORDER BY
  p.post_date DESC
SQL;
  $sql = $wpdb->prepare($sql,
    $phase,$wa,$aus,$topics,  // For the %d replacements
    $phase,$wa,$aus,$topics   // For the %s replacements
  );
  $results = $wpdb->get_results($sql,OBJECT);
  return $results;
}

基本的にこれはあなたの分類学用語のすべてが適用されているすべての投稿を与え、分類学/用語が適用されているがwp_post.IDによるグループ化と発見投稿が4回結合されているすべてのレコード。 MySQLのEXPLAINを実行するとき、最適化はあなたが持っていたものと比べてかなり良く見えます。結合されたテーブルが少なくなりました。うまくいけば、これはあなたが必要とした論理でした。

トランジェントAPIを使ったキャッシング

そして、もしあなたがパフォーマンスを改善しようとしているなら、限られた時間の間、結果を "transient" にキャッシュすることを考えるかもしれません (1時間、4時間、12時間以上?) このブログ記事はWordPress Transients API の使い方を説明しています:

トランジェントの基本的なロジックは次のとおりです。

define('NUM_HOURS',4); // Time to cache set for your use case
$data = get_transient( 'your_transient_key' );
if( !$data ) {
  $data = // Do something to get your data here
  set_transient( 'your_transient_key', $data, 60 * 60 * NUM_HOURS );
}  

あなたのfilter_resources()関数でトランジェントを使うために、代わりにそれはこのように見えるかもしれません:

define('RESOURCE_CACHE_HOURS',4);
function filter_resources($phase,$wa,$aus,$topics){
  $resources = get_transient( 'yoursite_filtered_resources' );
  if(!$resources) {
    global $wpdb;
    $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN (%d,%d,%d,%d)
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN (
    'phase-of-learning/%s',
    'wa-curriculum/%s',
    'australian-curriculum/%s',
    'topics/%s'
  )
GROUP BY
  p.ID
HAVING
  COUNT(*)=4
ORDER BY
  p.post_date DESC
SQL;
    $sql = $wpdb->prepare($sql,
      $phase,$wa,$aus,$topics,  // For the %d replacements
      $phase,$wa,$aus,$topics   // For the %s replacements
    );
    $resources = $wpdb->get_results($sql,OBJECT);
    $hours = RESOURCE_CACHE_HOURS * 60 * 60;
    set_transient( 'yoursite_filtered_resources', $resources, $hours);
  }  
  return $resources;
}

更新

ユーザーが選択した基準が4つ未満の場合を処理しようとしているコードをもう1つ取り上げます。

define('RESOURCE_CACHE_HOURS',4);
function filter_resources($phase,$wa,$aus,$topics){
  $resources = get_transient( 'yoursite_filtered_resources' );
  if(!$resources) {
    $terms = $taxterms = array();
    if (!empty($phase))
      $taxterms[$phase] = 'phase-of-learning/%s';
    if (!empty($wa)) 
      $taxterms[$wa] = 'wa-curriculum/%s';
    if (!empty($aus))
      $taxterms[$aus] = 'axustralian-curriculum/%s';
    if (!empty($topics))
      $taxterms[$topics] = 'topics/%s';
    $count = count($taxterms);
    $having = ($count==0 ? '' : "HAVING COUNT(*)={$count}");
    $values = array_keys(array_flip($tax_terms));
    $values = array_merge($values,$values);  // For %d and $s
    $taxterms =  implode("','",$taxterms);
    $terms = implode(',',array_fill(0,$count,'d%'));
    global $wpdb;
    $sql =<<<SQL
SELECT
  t.slug,p.*
FROM
  wp_posts p
  INNER JOIN wp_term_relationships tr ON p.ID=tr.object_id
  INNER JOIN wp_term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
  INNER JOIN wp_terms t ON tt.term_id = t.term_id
WHERE 1=1
  AND p.post_type = 'resources'
  AND p.post_status = 'publish'
  AND t.term_id IN ({$terms})
  AND CONCAT(tt.taxonomy,'/',t.term_id) IN ('{$taxterms}')
GROUP BY
  p.ID
{$having}
ORDER BY
  p.post_date DESC
SQL;
    $sql = $wpdb->prepare($sql,$values);
    $resources = $wpdb->get_results($sql,OBJECT);
    $hours = RESOURCE_CACHE_HOURS * 60 * 60;
    set_transient( 'yoursite_filtered_resources', $resources, $hours);
  }  
  return $resources;
}
6
MikeSchinkel

WP 3.0との後方互換性を維持する必要がない限り、 高度な分類法クエリ [WP 3.1のサポートを利用することができます。

SQLを生成するコードはwp-includes/taxonomy.phpにあります。

5
scribu

まず第一に、左結合ではなく、内部結合を使用してください。左結合では、用語フィルタで一致する投稿が見つかるまで投稿テーブル全体をスキャンするクエリプランが強制されます。

次に、get_term()を使用して用語をプリフェッチすることで、必要な結合の数を減らすことができます。

この2つを組み合わせると、クエリは次のようになります。

SELECT * 
FROM $wpdb->posts posts
JOIN $wpdb->term_relationships termA
ON posts.ID = termA.object_id
AND termA.term_taxonomy_id = $termA_taxid
JOIN $wpdb->term_relationships termB
ON posts.ID = termB.object_id
AND termB.term_taxonomy_id = $termB_taxid
JOIN $wpdb->term_relationships termC
ON posts.ID = termC.object_id
AND termC.term_taxonomy_id = $termC_taxid
JOIN $wpdb->term_relationships termD
ON posts.ID = termD.object_id
AND termD.term_taxonomy_id = $termD_taxid
WHERE ...

13の代わりに5つの結合されたテーブルと、term_relationshipで発生頻度が最も低い用語に結び付けられた投稿を検索することから始まるクエリプランでも、同じ結果が得られます。

1