2013-12-08 44 views
0

我試圖在我的網站的查詢中進行一些優化。我一直認爲,使用一個「JOIN」查詢比在php循環中放置幾個​​查詢更好,但是當我這樣做時,它真的很慢。加入查詢比使用php和MySQL循環查詢慢20倍

在循環中查詢的原始方法需要0.055s,新加入「加入」需要1.084s ..我應該使用哪種解決方案?有沒有辦法讓新查詢更快?也許另一個想法是用循環在mysql中創建一個過程?

這是情況:我有一個論壇,在論壇上的主題和主題的消息。爲了得到用戶名,我在用戶信息表中得到了用戶的id,如果這是肯定的,那麼它是註冊用戶,否則它是賓客表中的一個訪客。不知道這是不是一個好的建築,但我不能把客人和註冊會員放在同一張桌子上。

CREATE TABLE `topic` (
    `id` int(6) NOT NULL AUTO_INCREMENT, 
    `post_date` int(14) NOT NULL DEFAULT '0', 
    `last_answere_date` int(14) NOT NULL, 
    `author_name` varchar(50) NOT NULL DEFAULT '', 
    `title` varchar(60) NOT NULL DEFAULT '', 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `msgs` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `topic_id` int(10) unsigned NOT NULL DEFAULT '0', 
    `post_date` int(14) unsigned DEFAULT NULL, 
    `user_id` int(11) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `topic_id` (`topic_id`), 
    KEY `user_id` (`user_id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 


CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `nom` varchar(30) NOT NULL DEFAULT '', 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `guests` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `name` varchar(50) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

和我的兩個情況:

//Goal : get the list of 5 new topics or topic with new messages 
//Methode query in loop 
    $timestart=microtime(true); 
    echo '<h3>Query in loop</h3>'; 
    //Get the list of new topics 
    $reqTopic = $bdd->query('SELECT id,title,author_name,post_date FROM topic ORDER BY last_answere_date DESC LIMIT 0,5'); 
    While($topic = $reqTopic->fetch()) 
    { 
     //Get the last message 
      $reqMsgs = $bdd->prepare('SELECT count(*) as msgCount, user_id,post_date FROM msgs WHERE topic_id=? ORDER BY post_date DESC'); 
      $reqMsgs->bindparam(1,$topic['id'],PDO::PARAM_INT); 
      $reqMsgs->execute(); 
      $msg = $reqMsgs->fetch(); 
     //IF the id is >0 then it's a registered member, else it's a guest 
      if($msg['user_id'] > 0) 
       $reqAuthor = $bdd->prepare('SELECT nom as name FROM users WHERE id=? LIMIT 0,1'); 
      else 
       $reqAuthor = $bdd->prepare('SELECT name FROM guests WHERE id=? LIMIT 0,1'); 

      $reqAuthor->bindparam(1,$msg['user_id'],PDO::PARAM_INT); 
      $reqAuthor->execute(); 
      $author = $reqAuthor->fetch(); 
     //Output 
     echo '<strong>'.$topic['title']. '</strong><br/>' 
      .'Author : '. $topic['author_name'] . ' | Date : '. date('d/m/Y H:i',$topic['post_date']) .'<br/>'; 
      if($msg['msgCount'] > 0) 
      { 
       echo 'Last msg author : '. $author['name'] . ' | Date : '. date('d/m/Y H:i',$msg['post_date']) .'<br/>' 
       .'Nbr msgs : '.$msg['msgCount'].'<br/>'; 
      } 
      echo '<br/>'; 

    } 
    //End of script : get time 
    $timeend=microtime(true); 
    $time=$timeend-$timestart; 
    echo '<strong>'.number_format($time, 10) . ' sec</strong>'; 

//Methode Left join 
    $timestart=microtime(true); 
    echo '<h3>Left Join</h3>'; 
    //The query 
     $reqTopic = $bdd->query('SELECT t.id,title,t.author_name,t.post_date, m.*, (SELECT COUNT(*) FROM msgs WHERE t.id=topic_id) as msgCount 
     FROM topic t 
     LEFT JOIN (
      SELECT m.id,topic_id,post_date as mdate,user_id, IFNULL(u.nom,i.name) as name 
      FROM msgs m 
      LEFT JOIN users u ON u.id=m.user_id 
      LEFT JOIN guests i ON i.id=-m.user_id 
      GROUP BY topic_id 
      ORDER BY post_date DESC 
      ) m 
      ON t.id=m.topic_id 
     GROUP BY t.id 
     ORDER BY last_answere_date DESC LIMIT 0,5'); 
    While($topic = $reqTopic->fetch()) 
    { 

     //Output 
     echo '<strong>'.$topic['title']. '</strong><br/>' 
      .'Author : '. $topic['author_name'] . ' | Date : '. date('d/m/Y H:i',$topic['post_date']) .'<br/>'; 
      if($topic['msgCount'] > 0) 
      { 
       echo 'Last msg author : '. $topic['name'] . ' | Date : '. date('d/m/Y H:i',$topic['mdate']) .'<br/>' 
       .'Nbr msgs : '.$topic['msgCount'].'<br/>'; 
      } 
      echo '<br/>'; 

    } 
    //End of script : get time 
    $timeend=microtime(true); 
    $time=$timeend-$timestart; 
    echo '<strong>'.number_format($time, 10) . ' sec</strong>'; 
+0

您的問題是什麼?這兩段代碼似乎做了非常不同的事情,這將解釋性能的差異。 –

+0

兩者的輸出完全相同。我讀過很多次,這是不好的把查詢放在php循環中,但是當我試圖不這樣做時,這是非常慢的。那麼,是否有解決方案來優化第二個查詢,或者在這種情況下,第一個解決方案更好? – Alabate

回答

1

一般而言,對於這種東西的最佳方式是分兩步進行:

  1. 獲取你需要
  2. 提取與這些行相關的數據

在你的情況下,通過提取你的話題開始:

SELECT id,title,author_name,post_date 
FROM topic 
ORDER BY last_answere_date DESC LIMIT 0,5 

使用所取得的主題標識,並獲取相關數據:

SELECT topic_id, user_id, post_date 
FROM msgs 
WHERE topic_id IN (…) 
ORDER BY topic_id, post_date DESC 

SELECT id, nom as name FROM users WHERE id IN (…) 

在您的特定情況下,相關的子查詢可以便宜地得出計數,如果您確實希望SQL返回它:

SELECT t.id,title,t.author_name,t.post_date, 
     (SELECT COUNT(*) FROM msgs WHERE t.id=topic_id) as msgCount 
FROM topic t 
ORDER BY t.last_answere_date DESC LIMIT 0,5 

(在上面的,規劃應該先把行,然後運行每行一次子查詢。)

與使用LEFT JOIN一氣呵成檢索一切都像你這樣做的問題是:

  1. 您可能會不必要地加入在子查詢中使用group by語句導致的巨大集合,因爲後者可能會阻止使用索引;
  2. 由於您加入的子查詢中的order by子句,您在執行where … order by … limit …時可能會丟失索引的好處;和
  3. 您將發送數據的時間乘以相關行數 - 例如,主題相關數據在您的左連接查詢中每發送一條消息獲取一次。 (這在您的特定情況下可能可以忽略不計,但可能會加在其他情況下。)
+0

確實,這應該是方法。 –