【问题标题】:MYSQL&PHP: running an INSERT INTO SELECT query within a PHP while loop, running slowMYSQL&PHP:在 PHP while 循环中运行 INSERT INTO SELECT 查询,运行缓慢
【发布时间】:2014-11-06 21:15:00
【问题描述】:

我真的是 php 和 MYSQL 的新手,一个月前我什么都不懂,所以请原谅我草率/糟糕的代码:)

我的 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 尚未购买的项目列表。然后我在返回的每一行(项目)上运行一个 while 循环,在这个循环中执行主查询。

下一个查询从评级表中获取信息,其中 item_id 等于正在查询的当前 item_id,并将其加入到预先计算好的用户兼容性表中。

然后我对评分和兼容性评分进行算术运算以形成推荐值,然后将推荐值、item_id 和 user_id 插入到另一个表中以供稍后调用。 (item_id,user_id) 列上有一个 2 列唯一键,因此最后是 ON DUPLICATE KEY UPDATE

所以我今天早上写了这段代码,对自己很满意,因为它完全符合我的需要。

问题在于,可以预见的是,它很慢。在我的测试数据库中,有 5 个测试用户和 100 个测试项目以及随机分类的 200 个评分,运行 while 循环需要 2.5 秒。我原以为它会很慢,但没有这么慢。一旦添加了更多用户和项目,它真的会很困难。主要问题在于插入...在重复密钥更新部分,我的磁盘利用率达到 100%,我可以告诉我笔记本电脑的硬盘正在疯狂寻找。我知道我可能会在生产中使用 SSD,但我仍然预计会有数千个项目和用户出现重大规模问题。

所以我的主要问题是:任何人都可以就如何优化我的代码或完全重新调整以提高速度提供任何建议。我确信在 while 循环中插入查询是一种糟糕的方法,我只是想不出任何其他方法来获得完全相同的结果

提前感谢,如果我的问题格式不正确,我们深表歉意

【问题讨论】:

  • 警告: 使用 mysqli 时,您应该使用参数化查询和 bind_param 将用户数据添加到您的查询中。 请勿使用字符串插值来完成此操作,因为您将创建严重的SQL injection bugs
  • @fetef FWIW:我见过一个月大的程序员写的更糟糕的代码;)
  • 如果您是 PHP 新手,您应该从适合您的风格和需求的 development framework 开始,例如 Laravel。用超低级代码粉碎并不是很有效率。
  • 如果您使用 php 框架,大多数时候您的查询将通过它内置的数据库函数进行优化。对于初学者,Codeigniter 非常容易学习和实施。
  • @Angelo - 我还没有看到可以优化数据库查询的框架。您通常会看到抽象库的性能下降。

标签: php mysql performance


【解决方案1】:
$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 的解释。

【讨论】:

  • 这是正确答案,通过保存 while 循环的不断搜索,将查询时间从 2.5 秒缩短到 0.08 秒。我确信我可以通过索引优化来进一步缩短时间,但我认为这是我应该自己进行的一次自我教育。不过非常感谢@Strawberry 的统一查询,我盯着这个看了10个小时没看到。太棒了
  • ;-) 这不是真正的“剃须”,是吗?更像断头台!
【解决方案2】:

我找到了我正在寻找的答案here

每个项目的第二个查询仅用于选择就需要 0.002 秒,但插入则需要 0.06 秒,因此我分析了查询并发现“查询结束”占用了 99% 的查询时间。我已设置 innodb_flush_log_at_trx_commit = 0,但该答案的 cmets 对此不以为然。但是我不使用交易,那么这种方法有什么后果/替代方案吗?它确实将我的 while 循环时间从 2.5 秒减少到了 0.08 秒。

【讨论】:

  • 你可能会发现它的子选择真的很浪费时间尝试用插入和主选择循环上的连接替换子选择,你会看到执行时间下降,特别是如果结合表格上正确配置的索引。
  • 关于如何重新排列查询/哪些列应该被索引的任何建议?
  • 索引任何用作连接点或 where 子句搜索点的内容,因此对于初学者 r.user_id、c.user2_id、c.user1_id、r.item_id 等,请查看您的查询以获取其余部分.然后尝试再次执行相同的查询,看看之后它是否更快,然后开始考虑将子选择重写为连接等。
  • 例如,您的第一个选择可能会被重写为类似 SELECT i.id AS id FROM items i INNER JOIN purchased_items ON i.id=purchased_items.item_id WHERE purchased_items.user_id NOT $user_id 的内容,内部连接只会选择两个表中都有条目的项目,因此您可能需要执行不同类型的连接语法虽然只是连接类型会改变,但仍然非常相似。
【解决方案3】:

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

fetch_assoc 可能比 fetch_array 快得多,您应该在访问值之前一次性获取所有数据。

【讨论】:

  • 他已经为 mysqli_fetch_array() 中的可选第二个参数传入 MYSQLI_ASSOC,以仅将结果作为关联数组返回。
猜你喜欢
  • 2017-03-13
  • 2018-12-14
  • 1970-01-01
  • 2017-03-15
  • 1970-01-01
  • 2016-06-21
  • 2015-08-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多