MYSQL&PHP:在PHP while循环中运行INSERT INTO SELECT查询,运行缓慢

问题描述:

我对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

$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

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

FETCH_ASSOC可能比fetch_array显著快,你应立即获取你访问值之前。

+0

他已经在MYSQLI_ASSOC中传递mysqli_fetch_array()中的可选第二个参数,以便仅将结果作为关联数组返回。 – 2014-11-06 21:45:42

我与刀片发现,我一直在寻找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