【问题标题】:MySQL - Big DELETEs on multiple tablesMySQL - 多个表上的大删除
【发布时间】:2016-04-16 02:11:29
【问题描述】:

我有 7 个相关表,其中一个表有一个时间戳列,我想删除所有超过 30 天的行。但是,这些都是非常大的删除。我说的是数千万条记录。如果我从主表中删除所有这些记录,我必须查看其他 6 个表并从这些表中删除相关记录。

我的问题是优化它的最佳方法是什么?

我正在考虑使用PARTITION,但只有一张表有时间戳列。我担心如果我在主表中删除旧分区,相关记录仍然存在于其他6个表中。相关记录由字段(sid、cid)关联。

对于上下文,我使用的是 IDS 处理器的 snort 和 barnyard。

我正在使用 MySQL 5.1.73,MyISAM 表

这是清理日志中的一个 sn-p:

StartTime,EndTime,TimeElapsed,AffectedRows
Wed Jan 6 01:00:01 EST 2016,Wed Jan 6 01:45:11 EST 2016,45:10,2911807
Thu Jan 7 01:00:02 EST 2016,Thu Jan 7 01:25:29 EST 2016,25:27,2230255
Fri Jan 8 01:00:01 EST 2016,Fri Jan 8 01:24:18 EST 2016,24:17,1400470
Sat Jan 9 01:00:02 EST 2016,Sat Jan 9 05:47:10 EST 2016,287:8,23360088
Sun Jan 10 01:00:01 EST 2016,Sun Jan 10 10:06:16 EST 2016,546:15,44970072
Mon Jan 11 01:00:01 EST 2016,Mon Jan 11 09:40:39 EST 2016,520:38,43948091

这是我的旧清理脚本:

/usr/bin/mysql --defaults-extra-file=/old/.my.cnf snort_db >> /root/snortcleaner.log 2>&1 <<EOF
use snort_db;

DROP TRIGGER IF EXISTS delete_old;

DELIMITER //
CREATE TRIGGER delete_old AFTER DELETE ON event
FOR EACH ROW
BEGIN
DELETE FROM data WHERE data.cid = old.cid AND data.sid = old.sid;
DELETE FROM iphdr WHERE iphdr.cid = old.cid AND iphdr.sid = old.sid;
DELETE FROM icmphdr WHERE icmphdr.cid = old.cid AND icmphdr.sid = old.sid;
DELETE FROM tcphdr WHERE tcphdr.cid = old.cid AND tcphdr.sid = old.sid;
DELETE FROM udphdr WHERE udphdr.cid = old.cid AND udphdr.sid = old.sid;
DELETE FROM opt WHERE opt.cid = old.cid AND opt.sid = old.sid;
END //
DELIMITER ;

EOF

# Send the main MySQL command: Deletes all records betweeen the oldest     timestamp and 31 days from now()
# Gets the oldest timestamp and ranges a deletion from that to 31 days before now(). If the oldest timestamp is more recent than 31 days, the following command returns 0 anyway. If it is older than 31 days, it will return them
OLDEST_TIMESTAMP=$(mysql --defaults-extra-file=/old/.my.cnf -Dsnort_db -se "SELECT timestamp FROM event ORDER BY timestamp ASC LIMIT 1;")
NUM_AFFECTED=$(mysql --defaults-extra-file=/old/.my.cnf -Dsnort_db -se "DELETE FROM event WHERE timestamp BETWEEN DATE_SUB('${OLDEST_TIMESTAMP}', INTERVAL 1 HOUR) AND DATE_SUB(NOW(), INTERVAL 31 DAY); SELECT ROW_COUNT();")

这是我当前的清理脚本:

DELETE FROM event WHERE timestamp BETWEEN DATE_SUB('${OLDEST_TIMESTAMP}', INTERVAL 1 HOUR) AND DATE_SUB(NOW(), INTERVAL 31 DAY);

DELETE FROM data USING data LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;
DELETE FROM iphdr USING iphdr LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;
DELETE FROM icmphdr USING icmphdr LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;
DELETE FROM tcphdr USING tcphdr LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;
DELETE FROM udphdr USING udphdr LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;
DELETE FROM opt USING opt LEFT OUTER JOIN event USING (sid,cid) WHERE event.sid IS NULL;

我在两者之间来回切换,因为我不知道哪个更快,但现实是两者都太慢了。

【问题讨论】:

  • 是否有从“其他”表到主表的外键?
  • InnoDB?我的ISAM?表定义?
  • 是的,有外键,但其他表的行数相同。它们是 MyISAM 表。
  • 按比例,您每天要删除多少% 的event 表?
  • 我的另一个问题是:为什么需要删除这些记录?

标签: mysql snort


【解决方案1】:

尝试将您的外键设置为删除时级联,这样您就无需创建触发器并手动加入和删除相关记录。

下面的例子展示了如何创建一个级联删除的关系

CREATE TABLE parent ( id INT NOT NULL, PRIMARY KEY (id) ) ENGINE=INNODB; CREATE TABLE child ( id INT, parent_id INT, INDEX par_ind (parent_id), FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE ) ENGINE=INNODB;

来自Mysql website 的示例

【讨论】:

  • 我使用的是 MyISAM 表,但是如果我要转换为 InnoDB 表,级联删除会比触发器删除更快吗?
  • 不确定,我可能会尝试将其排除,然后尝试分区方法
  • 让我们知道什么是最快的,有趣的问题
  • 事实证明,我认为我不能使用级联删除:stackoverflow.com/questions/1457013/…。我有超过 1 个相关表。
  • 那个帖子是微软sql,不是Mysql。我想你可以用 mysql
【解决方案2】:

我们通过创建和删除分区解决了此类问题。 因此,首先您在表中按日期创建分区(最佳实践 - 使用 MySql 事件自动化),当您需要删除旧数据时 - 只需删除一些分区 - 操作将立即进行,没有任何延迟或锁定。

【讨论】:

  • 这是我最感兴趣的路线但是我不知道如何管理这6个相关的表。其他表中没有可分区的字段。
【解决方案3】:

如何在删除之前将要删除的行的 ID 保存到临时表中。

然后,您可以将清理脚本从加入 id = null 的大表切换到加入 id null 的小(er)表。

【讨论】:

  • 在 MySQL 5.6+ 中,您可以指定要加入的分区,避免将数据复制到临时表中。 ... inner join the_big_table partition(partition_name) on ...
  • 这会明显更快吗?我绝对可以试试这个。
【解决方案4】:

我会做两件事:

在其他表中定义外键

ON DELETE CASCADE

与其每小时蚕食行,不如添加一个 LIMIT 来简单删除

DELETE FROM event
WHERE timestamp < DATE_SUB(NOW(), INTERVAL 31 DAY)
LIMIT 500000

并继续重新运行它,直到没有行受到影响或根据经验告诉您需要多少次为止。

500000 调整为尽可能大,而不会导致查询死亡。

【讨论】:

  • 据我所知,ON DELETE CASCADE 仅在 InnoDB 表上。我正在使用 MyISAM 表。我听说过这种分块删除的方法。我不明白它的性能增强。它如何更快/更高效?
  • event.cid 是递增的(总是增加值)吗?
  • 我不这么认为。不一定。
【解决方案5】:

将您的脚本更改为:

  • 确保所有表的cid 上都有索引
  • 捕获您即将从事件中删除的 cid
  • 而不是定位所有旧行。目标(最多)一个(小)最大数量的旧行,因此它执行相对较快
  • 经常运行脚本(比如每 5 分钟、每小时、每天,只要有意义)

类似:

CREATE TABLE IF NOT EXISTS deleted_cids(int cid); -- ensure same datatype as cid in tables
TRUNCATE deleted_cids;
INSERT INTO deleted_cids
SELECT cid FROM event
WHERE timestamp BETWEEN DATE_SUB('${OLDEST_TIMESTAMP}', INTERVAL 1 HOUR)
  AND DATE_SUB(NOW(), INTERVAL 31 DAY)
LIMIT 100000; -- Choose largest LIMIT that gives acceptable execution time
DELETE event FROM deleted_cids, event WHERE event.cid = deleted_cids.cid;
DELETE data FROM deleted_cids, data WHERE data.cid = deleted_cids.cid;
DELETE iphdr FROM deleted_cids, iphdr WHERE iphdr.cid = deleted_cids.cid;
DELETE icmphdr FROM deleted_cids, icmphdr WHERE icmphdr.cid = deleted_cids.cid;
DELETE tcphdr FROM deleted_cids, tcphdr WHERE tcphdr.cid = deleted_cids.cid;
DELETE udphdr FROM deleted_cids, udphdr WHERE udphdr.cid = deleted_cids.cid;
DELETE opt FROM deleted_cids, opt WHERE opt.cid = deleted_cids.cid;

这里的优点是每次删除都是基于索引的单次执行以删除所有目标行 - 它应该快速执行。

通过调整 LIMIT 和执行频率,您可以找到服务器负载的正确平衡。我会选择频繁执行少量的操作,这样您的服务器就不会因该过程而陷入停顿。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 2014-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多