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

very slow search and update database operation

我有一个 table "table1",它有将近 400,000 条记录。还有另一个 table "table2" 大约有 450,000 条记录。

我需要删除 table1 中所有在 table2 中重复的行。我一直在尝试用 php 来做,脚本 运行 好几个小时了,还没有完成。真的需要那么多时间吗?

field asin is varchar(20) in table1

field ASIN is Index and char(10) in table2

$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";

你可以 运行 一个单一的 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] );

这样,您可以运行并行执行多个更新

是的,处理 RBAR(逐行痛苦)将。每个单独的 SELECT 和执行的 UPDATE 语句都会产生开销...将 SQL 文本发送到数据库,解析有效语法(关键字、逗号、表达式)的标记,验证语义(table 引用和列引用有效,用户具有所需的权限等),评估可能的执行计划(索引范围扫描、全索引扫描、全 table 扫描),转换选定的执行计划进入 executable 代码,执行查询计划(获取锁、访问行、生成回滚、写入 innodb 和 mysql 二进制日志等),然后 returning 结果。

所有这些都需要时间。对于一两个语句,时间并不那么明显,但将数千次执行置于一个紧密的循环中,这就像在沙漏中观看单个沙粒落下。

与大多数关系数据库一样,

MySQL 旨在高效地操作 数据。给数据库工作,让数据库启动,而不是花时间来回处理数据库。

就好像您有一千件小物品要运送到同一个地址。您可以单独处理每个项目。取一个箱子,用装箱单将物品放入箱子,密封包裹,写上包裹,称重并确定postage,贴上postage,然后放入车内, 开车到 post 办公室,把包裹放下。然后开车回去,处理下一个物品,把它放进一个盒子里,……一遍又一遍。

或者,我们可以一起处理很多小物品,作为一个更大的包裹,并减少包装和往返 post 办公室的开销工作量(时间)。


一方面,确实不需要 运行 单独的 SELECT 语句来确定我们是否需要进行更新。我们可以 运行 更新。如果没有要更新的行,查询将 return "affected rows" 计数为 0。

(运行 单独的 SELECT 就像乘车到 post 办公室的另一次往返,在每一轮之前检查需要运送的包裹清单前往 post 办公室投递包裹。我们可以在第一次旅行中带走包裹,而不是 两次 次往返。)

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


真正 的性能提升来自于 更多 的工作 更少 [=113] =] 语句。

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

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

(如果 asin 不是唯一的,我们需要稍微调整一下查询,以避免 returning "duplicate" 行。重点是,我们可以写一个SELECT 标识 所有 行需要更新的语句。)

对于非平凡的 tables,为了性能,我们需要有可用的 suitable 索引。在这种情况下,我们希望索引的前导列为 asin。如果这样的索引不存在,例如...

... ON asins_chukh (asin)

如果该查询没有 return 大量 行,我们可以一举处理更新:

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

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

我们可以将工作分解成更易于管理的部分。

(回到运输小件物品的类比...如果我们有数百万件小件物品,将所有这些物品放入一次装运中会产生比集装箱船集装箱更大更重的包裹...我们可以将货物分成大小合适的箱子。)

例如,我们可以处理 "batches" 中 10,000 个 id 值的更新(假设 id 是唯一的(或几乎唯一的),是集群中的前导列键,并且 id 值很好地分组到大部分连续的范围内,我们可以将更新 activity 本地化到块的一部分,而不必再次重新访问大部分相同的块...

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 值。 (我们可以 运行 a SELECT MAX(id) FROM asins 作为循环之前的第一步。)

(我们想先将这些语句作为 SELECT 语句进行测试,然后再转换为 UPDATE。)

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

我们的 objective 是为了创建可管理的 "chunks" 我们可以放入一个循环,其中块不与相同的数据库块重叠...我们不需要重新访问相同的一遍又一遍地使用多个语句对同一个块中的行进行多次更改。