【问题标题】:What is the best way to delete old rows from MySQL on a rolling basis?从 MySQL 滚动删除旧行的最佳方法是什么?
【发布时间】:2012-03-17 08:21:00
【问题描述】:

我发现自己想在很多应用程序中滚动删除早于 (x) 天的行。在人流量大的桌子上最有效地执行此操作的最佳方法是什么?

例如,如果我有一个存储通知的表,并且我只想保留这些通知 7 天。或者我只想保留 31 天的高分。

现在我保留一行存储发布的纪元时间并运行一个每小时运行一次的 cron 作业,并以如下增量删除它们:

DELETE FROM my_table WHERE time_stored < 1234567890 LIMIT 100

我这样做直到 mysql_affected_rows 返回 0。

我曾经一次完成所有操作,但这导致应用程序中的所有内容在 INSERTS 堆积时挂起 30 秒左右。添加 LIMIT 可以缓解这种情况,但我想知道是否有更好的方法来做到这一点。

【问题讨论】:

  • 您是否考虑过在非工作时间(凌晨 12 点至凌晨 2 点)运行 cron 作业,或者您当时仍在处理繁忙的交通?
  • +1 @MikePurcell 我通常在活动最低时安排 cron 作业。
  • 您是否在time_stored 列上设置了索引?没有一个可能会导致您的DELETE 查询缓慢。
  • 如果要删除行:(过多)表中各列的索引过多也会导致删除缓慢,因为这些索引也必须更新。
  • 您是否在删除后重新优化您的表?它们可能是超级碎片化的。

标签: mysql


【解决方案1】:

尝试创建将在您想要的时间间隔后自动在数据库上运行的事件。

这是一个例子: 如果要从某个表“tableName”中删除超过 30 天的条目,则具有列条目“datetime”。然后每天运行以下查询,这将执行所需的清理操作。

CREATE EVENT AutoDeleteOldNotifications
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 DAY 
ON COMPLETION PRESERVE
DO 
DELETE LOW_PRIORITY FROM databaseName.tableName WHERE datetime < DATE_SUB(NOW(), INTERVAL 30 DAY)

我们需要添加ON COMPLETION PRESERVE 以在每次运行后保留该事件。你可以在这里找到更多信息:http://www.mysqltutorial.org/mysql-triggers/working-mysql-scheduled-event/

【讨论】:

  • 那不是一次性事件吗?对于重复发生的事件,必须使用 CREATE EVENT 语法中的 EVERY 语句。
  • 不,'ON COMPLETION PRESERVE' 的作用与 'EVERY' 子句的作用相同。两种不同的方法。
【解决方案2】:

查看MySQL Partitioning:

通常可以通过删除仅包含该数据的分区(或多个分区)轻松地从分区表中删除失去其用处的数据。相反,在某些情况下,通过添加一个或多个新分区来专门存储该数据,可以极大地促进添加新数据的过程。

参见例如本节以获取有关如何应用它的一些想法:

MySQL Partition Pruning

还有这个:

Partitioning by dates: the quick how-to

【讨论】:

  • 太棒了,这看起来很有希望。我必须对此进行一些基准测试!
  • 阅读您发布的文章,这看起来正是我想要的。谢谢! (接受的答案)
  • 只是想更新一下我现在有这个工作了,太棒了!特别是因为分区修剪意味着我要删除的分区一旦过期,只要我对 SELECTS 进行限制,就根本无法访问它。这个资源帮助很大:slideshare.net/datacharmer/mysql-partitions-tutorial
  • @BradDwyer:太好了!当计划达成时总是很高兴听到。感谢您的反馈。
  • 第二个链接好像没了。我发现另一个可能是相同的:dev.mysql.com/doc/internals/en/…
【解决方案3】:

不要单独对表执行删除,而是先尝试收集匹配的键,然后执行 DELETE JOIN

给定上面的示例查询

DELETE FROM my_table WHERE time_stored < 1234567890 LIMIT 100 ;

您可以将 LIMIT 排除在外。

假设您要删除超过 31 天的数据。

让我们以秒计算 31 天 (86400 X 31 = 2678400)

  • 从收集密钥开始
  • 接下来,索引键
  • 然后,执行 DELETE JOIN
  • 最后,放下收集的钥匙

这是算法

CREATE TABLE delete_keys SELECT id FROM my_table WHERE 1=2;
INSERT INTO delete_keys
SELECT id FROM
(
    SELECT id FROM my_table
    WHERE time_stored < (UNIX_TIMESTAMP() - 2678400)
    ORDER BY time_stored
) A LIMIT 100;
ALTER TABLE delete_keys ADD PRIMARY KEY (id);
DELETE B.* FROM delete_keys
INNER JOIN my_table B USING (id);
DROP TABLE delete_keys;

如果密钥收集时间少于 5 分钟,则每 5 分钟运行一次此查询。

试试看!!!

更新 2012-02-27 16:55 EDT

这里有一些东西可以加快密钥收集速度。添加以下索引:

ALTER TABLE my_table ADD INDEX time_stored_id_ndx (time_stored,id);

这将更好地支持填充 delete_keys 表的子查询,因为这提供了一个覆盖索引,以便仅从索引检索字段。

更新 2012-02-27 16:59 EDT

由于您必须经常删除,您可能需要每两个月尝试一次

OPTIMIZE TABLE my_table;

这将在两个月内每 5 分钟进行一次所有烦人的小删除后对表进行碎片整理

【讨论】:

  • 如果 timestored 被索引,为什么这会加快删除速度?
  • 它应该加快密钥收集。事实上,一个灯泡刚刚熄灭。让我在我的答案中添加一些东西......
【解决方案4】:

在我的公司,我们也有类似的情况。我们有一个包含过期键的表。我们有一个运行来清理它的 cron:

DELETE FROM t1 WHERE expiration < UNIXTIME(NOW());

这每小时运行一次,但我们遇到了与您遇到的类似的问题。我们将其增加到每分钟一次。然后每分钟6次。使用 bash 脚本设置一个 cron,该脚本基本上执行查询,然后休眠几秒钟并重复,直到一分钟结束。

增加的频率显着减少了我们要删除的行数。这缓解了争论。这是我要走的路线。

但是,如果您发现仍有太多行要删除,请使用限制并在它们之间进行休眠。例如,如果你有 50k 行要删除,那么做一个 10k 的块,它们之间有 2 秒的睡眠。这将有助于查询堆积,并允许服务器在这些批量删除之间执行一些正常操作。

【讨论】:

  • 是的,这就是我们目前所采用的解决方案。希望有一些更具可扩展性的东西。似乎应该有更好的方法来做到这一点,特别是因为所有要删除的数据都应该在磁盘上组合在一起,因为它们都是按顺序插入的。
【解决方案5】:

您可能需要考虑在您的设计中引入master/slave (replication) 解决方案。如果将所有读取流量转移到从属服务器,则打开主服务器以处理“即时”CRUD 活动,然后将其复制到从属服务器(您的读取服务器)。

由于您要删除的记录太多,您可能需要考虑在要删除行的表上运行optimize

【讨论】:

    【解决方案6】:

    最终使用它只保留了最后 100 行,因此在频繁执行(每分钟)时会出现明显的延迟

    delete a from tbl a left join (
        select ID
        from tbl
        order by id desc limit 100
    ) b on a.ID = b.ID
    where b.ID is null;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-15
      • 2020-12-10
      • 1970-01-01
      相关资源
      最近更新 更多