2014-11-06 142 views
1

我對PHP和MYSQL真的很陌生,我一個月前都不知道,所以請原諒我的馬虎/糟糕的代碼:)MYSQL&PHP:在PHP while循環中運行INSERT INTO SELECT查詢,運行緩慢

我有我的PHP中下面的代碼:

$starttime = microtime(true); 
$q_un = 'SELECT i.id AS id 
      FROM items i 
      WHERE i.id NOT IN (SELECT item_id FROM purchased_items WHERE user_id=' . $user_id . ')'; 
$r_un = mysqli_query($dbc, $q_un); 
if (mysqli_num_rows($r_un) > 0) { 
while ($row_un = mysqli_fetch_array($r_un, MYSQLI_ASSOC)) { 
    $item_id = $row_un['id']; 
    $q_rec = 'INSERT INTO compatibility_recommendations (
       `recommendation`, 
       `user_id`, 
       `item_id`) 
       SELECT 
        ((SUM(a.rating*(a.compat-80)))/(SUM(a.compat-80)))*10 AS rec, 
        a.user_id AS user_id, 
        a.item_id AS item_id 
       FROM 
        (SELECT r.rating AS rating, 
         c.user2_id AS rater, 
         c.user1_id AS user_id, 
         c.compatibility AS compat, 
         r.item_id AS item_id 
        FROM ratings r 
        RIGHT JOIN compatibility_ratings c ON r.user_id=c.user2_id 
        WHERE c.user1_id=' . $user_id . ' AND r.item_id=' . $item_id . ' AND c.compatibility>80) a 
       ON DUPLICATE KEY UPDATE 
        recommendation = VALUES(recommendation)'; 
    $r_rec = mysqli_query($dbc, $q_rec); 
} 
} 
$endtime = microtime(true); 
$duration = $endtime - $starttime;</code> 

第一個查詢選擇當前用戶,$ USER_ID,尚未購買的物品清單。然後在返回的每一行(item)上運行一個while循環,在此循環中執行主查詢。

這下一個查詢是從收視率表中獲取信息,其中item_id等於當前正在查詢的item_id,並將其加入到具有正確聯接的預先計算的用戶兼容性表中。

然後,我對評級和兼容性評級運算算法以形成推薦值,然後將推薦item_id和user_id插入到稍後調用的另一個表中。在(item_id,user_id)列上有一個2列唯一鍵,因此在末尾的ON DUPLICATE KEY UPDATE

所以我今天早上寫了這段代碼,對自己很滿意,因爲它確實是我需要的做。

問題是,可以預見,它很慢。在我的測試數據庫中,有5個測試用戶和100個測試項目以及200個評分的隨機分組,它需要2.5秒才能通過while循環。我期待它會變慢,但不會這麼慢。一旦添加了更多的用戶和項目,這真的很難。主要問題是插入...在重複密鑰更新部分,我的磁盤利用率達到100%,我可以告訴我的筆記本電腦的硬盤正在尋求瘋狂。我知道我可能會在生產中使用固態硬盤,但我仍然預計有數千個項目和用戶會遇到大規模問題。

所以我的主要問題在於:任何人都可以提供任何建議,如何優化我的代碼,或完全rejig的東西,以提高速度。我確信在while循環中插入查詢是一個不好的方法,我只是想不出任何其他方式來獲得完全相同的結果

在此先感謝和抱歉,如果我格式化我的問題正確

+1

**警告:** 使用'mysqli'時,應該使用參數化查詢和['bind_param'](http://php.net/manual/en/mysqli-stmt.bind-param.php)將用戶數據添加到您的查詢中。**不要**使用字符串插值來實現此目的,因爲您將創建嚴重的[SQL注入漏洞](http://bobby-tables.com/)。 – tadman 2014-11-06 21:16:16

+2

@fetef FWIW:我已經看到了來自月齡較大的程序員的更糟糕的代碼;) – webnoob 2014-11-06 21:16:54

+0

如果您是PHP的新手,您應該從 [開發框架]開始(http://codegeekz.com/best-php-frameworks對於開發人員/)像[Laravel](http://laravel.com/),這符合你的風格和需求。用超級低級代碼粉碎並不是很有效率。 – tadman 2014-11-06 21:17:01

回答

0
$starttime = microtime(true); 
$q_un = " 

INSERT INTO compatibility_recommendations 
(recommendation 
,user_id 
,item_id 
) 
SELECT ((SUM(a.rating*(a.compat-80)))/(SUM(a.compat-80)))*10 rec 
     , a.user_id 
     , a.item_id 
    FROM 
     (SELECT r.rating rating 
      , c.user2_id rater 
      , c.user1_id user_id 
      , c.compatibility compat 
      , r.item_id 
      FROM compatibility_ratings c 
      JOIN ratings r 
      ON r.user_id = c.user2_id 

      JOIN items i 
      ON i.id = r.item_id 

      LEFT 
      JOIN purchased_items p 
      ON p.item_id = i.id 
      AND p.user_id = $user_id 

     WHERE c.user1_id = $user_id 
      AND c.compatibility > 80 
      AND p.item_id IS NULL 
    ) a 
GROUP BY a.item_id 
ON DUPLICATE KEY UPDATE recommendation = VALUES(recommendation); 

"; 

$r_rec = mysqli_query($dbc, $q_rec); 
} 
} 
$endtime = microtime(true); 
$duration = $endtime - $starttime;</code> 

對於任何進一步的改進,我們真的需要看到正確的DDL和上面的SELECT的解釋。

+0

這是正確的答案,通過保存尋找while循環的常量,將查詢時間從2.5秒縮短到0.08。我確信我可以通過索引優化進一步縮短時間,但我認爲這是一個我應該自學的旅程,來教育自己。但是非常感謝@Strawberry的統一查詢,我盯着這個10個小時看不到它。 Bravo – fetef 2014-11-08 10:02:31

+0

;-)這不是真的'剃'嗎?更像斷頭臺! – Strawberry 2014-11-08 15:38:49

-1

https://stackoverflow.com/a/14456661/2782404

FETCH_ASSOC可能比fetch_array顯著快,你應立即獲取你訪問值之前。

+0

他已經在MYSQLI_ASSOC中傳遞mysqli_fetch_array()中的可選第二個參數,以便僅將結果作爲關聯數組返回。 – 2014-11-06 21:45:42

0

我與刀片發現,我一直在尋找here

每個項目的第二個查詢正在採取0.002秒只是選擇的答案,但隨後0.06秒,所以我異型查詢,發現「查詢結束「佔據了查詢時間的99%。我已經設置innodb_flush_log_at_trx_commit = 0,但對這個答案的評論皺眉。但是,我不使用交易,那麼這種方法會有什麼後果/替代方案嗎?它確實將我的while循環時間從2.5秒減少到了0.08秒。

+0

你可能會發現它的子選擇確實在吃東西,試圖在插入和主選擇循環中用連接代替子查詢,並且你會看到執行時間下降,特別是如果結合正確配置的索引表。 – Dave 2014-11-07 11:52:03

+0

有關如何重新排列查詢/哪些列應該編入索引的建議? – fetef 2014-11-07 11:58:51

+0

索引用作連接點或where子句搜索點的任何內容,以便初學者r.user_id,c.user2_id,c.user1_id,r.item_id等查看您的查詢以獲取其餘部分。然後嘗試再次執行相同的查詢,看看它之後的任何更快,然後開始尋找重寫子選擇連接等。 – Dave 2014-11-07 12:08:13