3

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

我需要从没有索引和主键的 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();
4

2 回答 2

2

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

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

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

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

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

于 2016-04-25T14:17:02.813 回答
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 方面稍微减慢 DB,但由于没有与任何记录交互,因此您的用户应该几乎不会遇到任何减慢。


此外,删除如此缓慢和密集的另一个原因是因为 MySQL 将记录每一行,如果您有任何活动触发器,那么必须在执行删除之前执行这些触发器。

于 2016-04-25T15:10:07.677 回答