非常慢的搜索和更新数据库操作

时间:2016-11-11 23:25:09

标签: php mysql performance sql-delete

我有一个表“table1”,它有近400,000条记录。还有另一个表“table2”,它有大约450,000条记录。

我需要删除table1中的所有行,这些行在table2中是重复的。我一直试图用PHP做这个,脚本运行了几个小时但尚未完成。真的需要那么多时间吗?

  

字段asin是table1中的varchar(20)

     

字段ASIN是表2中的索引和char(10)

$duplicat = 0;
$sql="SELECT asin from asins";
$result = $conn->query($sql);

if ($result->num_rows > 0) {
    while($row = $result->fetch_assoc()) {
        $ASIN = $row['asin'];
        $sql2 = "select id from asins_chukh where ASIN='$ASIN' limit 1";
        $result2 = $conn->query($sql2);
        if ($result2->num_rows > 0) {
            $duplicat++;
            $sql3 = "UPDATE `asins` SET `duplicate` = '1' WHERE `asins`.`asin` = '$ASIN';";
            $result3 = $conn->query($sql3);
            if($result3) {
                echo "duplicate = $ASIN <br/>";
            }
        }
    }    
}

echo "totaal :$duplicat";

2 个答案:

答案 0 :(得分:1)

你可以运行一个单独的sql命令,而不是循环,例如:

update table_2 t2 
set t2.duplicate = 1
where exists (
    select id 
    from table_1 t1 
    where t1.id = t2.id);

警告!我没有测试上面的sql,所以你可能需要验证语法。

对于这种类型的数据库操作,使用php循环和连接永远不是一个好主意。大部分时间都会浪费在php服务器和mysql服务器之间的网络数据传输上。

如果上面的sql花了太长时间,你可以考虑用一些范围来限制查询集。类似的东西:

update table_2 t2 
set t2.duplicate = 1
where exists (
    select id 
    from table_1 t1 
    where t1.id = t2.id
      and t2.id > [range_start] and t2.id < [range_end] );

这样,你可以踢几个并行运行的更新

答案 1 :(得分:1)

是的,处理 RBAR (通过Agonizing Row行)将。与执行的每个SELECT和UPDATE语句相关联的开销...将SQL文本发送到数据库,解析标记以获取有效语法(关键字,逗号,表达式),验证语义(表引用和列引用)有效,用户具有所需权限等),评估可能的执行计划(索引范围扫描,完整索引扫描,全表扫描),将选定的执行计划转换为可执行代码,执行查询计划(获取锁,访问行,生成回滚,写入innodb和mysql二进制日志等),并返回结果。

所有这些都需要时间。对于一两个陈述,时间并不明显,但是将成千上万的执行置于一个紧密的循环中,这就像看着沙漏落入一小时玻璃中一样。

MySQL与大多数关系数据库一样,旨在有效地对数据进行操作。给数据库做的工作,让数据库动摇,而不是花时间来回绊倒数据库。

就像你有一千个小物品可以送到同一地址。您可以单独处理每个项目。拿到一个盒子,将物品放入包装盒的包装盒中,密封包装,找到包裹,称重包装并确定邮资,加贴邮资,然后将其放入车内,开车到邮局,放下包装关闭。然后开车回去,处理下一个项目,把它放进一个盒子里,......一遍又一遍。

或者,我们可以将很多小物品组合在一起,作为一个更大的包装,减少包装和往返邮局的工作量(时间)包装和往返。

首先,确实没有必要运行单独的SELECT语句,以确定是否需要进行更新。我们可以运行UPDATE。如果没有要更新的行,查询将返回一个&#34;受影响的行&#34;数为0.

(运行单独的SELECT就像在汽车到邮局的另一个往返行程,检查需要交付的包裹列表,在每次往返邮局之前卸下包裹。而不是< em>两次往返,我们可以在第一次旅行时带上我们的包裹。)

所以,这可以改善一些事情。但它并没有真正解决性能问题的根源。

真正的性能提升来自于更少 SQL语句中完成更多工作。

我们如何识别需要更新的所有行?

 SELECT t.asins
   FROM asins t
   JOIN asins_chukh s
     ON s.asin = t.asin
  WHERE NOT ( t.duplicate <=> '1' )

(如果asin不是唯一的,我们需要稍微调整一下查询,以避免返回&#34;重复&#34;行。重点是,我们可以编写一个SELECT语句标识所有需要更新的行的。)

对于非平凡的表,为了提高性能,我们需要有合适的索引。在这种情况下,我们希望索引的前导列为asin。如果这样的指数不存在,例如......

... ON asins_chukh (asin)

如果该查询没有返回巨大行数,我们可以一举处理UPDATE:

 UPDATE asins t
   JOIN asins_chukh s
     ON s.asin = t.asin
    SET t.duplicate = '1' 
  WHERE NOT ( t.duplicate <=> '1' )

我们需要注意行数。我们希望避免长时间持有阻塞锁(影响可能访问asins表的并发进程),并且我们希望避免生成巨大的数量的回滚。

我们可以将工作分解为更易于处理的块。

(参考运输小物品类比......如果我们有数百万件小物品,并将所有这些物品放入一个货物中,那么就会产生一个比集装箱船容器更大且更大的包装......我们可以打破装运到可管理尺寸的箱子里。)

例如,我们可以在&#34;批次&#34;中处理UPDATE。 10,000个id值(假设id是唯一的(或几乎唯一的),是群集密钥中的前导列,并且id值在相当大的连续范围内进行了很好的分组,我们可以得到更新活动本地化为一个块的一部分,而不必再次恢复大多数相同的块...

WHERE子句可能是这样的:

  WHERE NOT ( t.duplicate <=> 1 )
    AND t.id >= 0 
    AND t.id <  0 + 10000

下一批......

  WHERE NOT ( t.duplicate <=> 1 )
    AND t.id >= 10000
    AND t.id <  10000 + 10000

然后

  WHERE NOT ( t.duplicate <=> 1 )
    AND t.id >= 20000
    AND t.id <  20000 + 10000

依此类推,重复这一过程,直到我们超过最大id值。 (我们可以在循环之前的第一步运行SELECT MAX(id) FROM asins。)

(我们希望在转换为UPDATE之前先将这些语句作为SELECT语句进行测试。)

使用id列可能不是创建批次的最合适方式。

我们的目标是创造可管理的&#34;块&#34;我们可以放入一个循环,其中块不会重叠相同的数据库块......我们不需要一遍又一遍地使用多个语句重新访问同一个块,以对行内的行进行更改同一个区块多次。