如何在没有减速的情况下从mysql表中删除数百万条记录

时间:2016-04-25 14:01:03

标签: php mysql pdo miva

有没有一种很好的方法可以在不降低网站速度的情况下删除大量记录?

我需要从没有索引且没有主键的MySQL表中删除数百万条记录。我在网上阅读SO和各种教程,基本策略是限制删除查询,在删除之间休眠一两秒,然后重复该过程直到完成。我也(使用PDO)在所有循环完成后运行提交。

上周工作正常,但每次我运行脚本时,数据库都放慢速度,我们收到很多关于网站运行缓慢的投诉等等。这是在Miva Merchant购物篮桌上,而不是真的很重要。

我几乎完成了整理桌面,所以我可以忍受它并完成。但必须有更好的方法......?

以下是相关代码:

$database->beginTransaction();
$selectLimit = 4900; // mysql will lock the entire table at 5000+.....
$loopLimit = 10;
$date = "1456272001"; // 2016-02-24

for( $i = 0; $i < $loopLimit; $i++ ) {
    $startTime = time();
    $oldBaskets = $database->prepare("DELETE FROM s01_Baskets WHERE CAST(lastupdate AS UNSIGNED) < '" . $date . "' LIMIT " . $selectLimit . "");
    if ( $oldBaskets->execute() ) {
        $deletes = $oldBaskets->rowCount();
        $totalDeletes += $deletes;
        $duration = time() - $startTime;
        echo "\ndeleted '" . $deletes . "' entries";
        echo "\n-- took '" . $duration . "' seconds";
    }
    sleep(2);
}
$database->commit();

2 个答案:

答案 0 :(得分:2)

lastupdate上创建索引并稍微修改一下您的查询:

DELETE
FROM    s01_Baskets
WHERE   lastupdate < :date
ORDER BY
        lastupdate
LIMIT   :limit

索引lastupdate将允许MySQL将其用于排序和过滤,因此引擎只会访问必须删除的记录。

如果没有索引,MySQL必须在数据库读取之前检查数据库中的所有记录,然后才能达到限制。

在MySQL中的索引字段上使用CAST使得表达式不可分割(无法使用索引进行过滤),这就是为什么要转换要与之比较的表达式($date),而不是反之亦然。

答案 1 :(得分:0)

由于听起来你没有索引而且没有自动增加的ID,我个人会像这样直接使用SQL:

注意:当系统上的活动最少时,您应该这样做

RENAME TABLE s01_Baskets TO s01_Baskets_to_be_deleted;

CREATE TABLE s01_Baskets LIKE s01_Baskets_to_be_deleted;

INSERT INTO s01_Baskets (col1, col2, ..., coln)
SELECT *
FROM s01_Baskets_to_be_deleted
WHERE lastupdate >= '2016-02-24 00:00:00';

DROP TABLE s01_Baskets_to_be_deleted;

前两个应该相对较快地执行,您的用户不会注意到减速。他们的所有互动都将被简单地路由到您的新空桌。

第三个命令将重新插入您希望保留的记录。

对于DROP命令,它可能会在磁盘I / O方面减慢数据库的速度,但由于没有任何记录与之交互,因此您的用户几乎不会遇到任何减速。

此外,删除速度如此缓慢和强烈的另一个原因是MySQL将记录每一行,如果您有任何活动触发器,那么必须先执行这些操作才能执行删除。