我有一个表“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";
答案 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;我们可以放入一个循环,其中块不会重叠相同的数据库块......我们不需要一遍又一遍地使用多个语句重新访问同一个块,以对行内的行进行更改同一个区块多次。