从巨大的表中删除大量数据

And*_*and 14 mysql

我有两张桌子.我们称之为KEY和VALUE.
KEY很小,约有1.000.000条记录.
VALUE很大,比如1.000.000.000记录.

在它们之间存在一个连接,使得每个KEY可能具有许多VALUES.它不是外键,但基本上是相同的含义.

DDL看起来像这样

create table KEY (
 key_id int,
 primary key (key_id)
);

create table VALUE (
 key_id int,
 value_id int,
 primary key (key_id, value_id)
);
Run Code Online (Sandbox Code Playgroud)

现在,我的问题.VALUE中大约一半的key_id已从KEY中删除,我需要以有序的方式删除它们,而两个表仍然处于高负荷状态.

这很容易做到

delete v 
  from VALUE v
  left join KEY k using (key_id)
 where k.key_id is null;
Run Code Online (Sandbox Code Playgroud)

但是,因为它不允许limit有多表删除我不喜欢这种方法.这样的删除需要几个小时才能运行,这使得无法限制删除.

另一种方法是创建游标以查找所有缺少的key_ids并逐个删除它们.这似乎非常缓慢,有点倒退.

还有其他选择吗?一些可以帮助的好技巧?

Bil*_*win 23

试图在一个事务中删除如此多数据的任何解决方案都会压倒回滚段并导致很多性能问题.

pt-archiver是一个很好的帮助工具.它尽可能高效地对中等大小的行执行增量操作. pt-archiver可以根据选项复制,移动或删除行.

该文档包含删除孤立行的示例,这正是您的场景:

pt-archiver --source h=host,D=db,t=VALUE --purge \
  --where 'NOT EXISTS(SELECT * FROM `KEY` WHERE key_id=`VALUE`.key_id)' \
  --limit 1000 --commit-each
Run Code Online (Sandbox Code Playgroud)

执行此操作将花费更长时间来删除数据,但不会使用太多资源,也不会中断现有数据库上的服务.我成功地使用它来清除数亿行过时的数据.

pt-archiverPercona Toolkit for MySQL的一部分,这是一套免费的(GPL)脚本,可以帮助MySQL和兼容数据库完成常见任务.


rsa*_*hez 6

这个有限制怎么办?

delete x 
  from `VALUE` x
  join (select key_id, value_id
          from `VALUE` v
          left join `KEY` k using (key_id)
         where k.key_id is null
         limit 1000) y
    on x.key_id = y.key_id AND x.value_id = y.value_id;
Run Code Online (Sandbox Code Playgroud)

  • 这会损害性能,因为 1)连接两个大表可能会很慢;2) 嵌套 SELECT 必须扫描超过 1000 x N 次的行才能找到要删除的“前”1000 行;3) 最后 999 行将是最慢的,因为它将运行两次全索引扫描而不会提前退出;4) VALUE 中删除的行在表中的位置可能非常随机,IO 不太可能是连续的 (4认同)

Gus*_*tek 6

直接来自MySQL文档

如果要从大型表中删除许多行,则可能会超出InnoDB表的锁定表大小.要避免此问题,或者只是为了最小化表保持锁定的时间,以下策略(根本不使用DELETE)可能会有所帮助:

选择不要删除的行到与原始表具有相同结构的空表中:

INSERT INTO t_copy SELECT * FROM t WHERE ... ;
Run Code Online (Sandbox Code Playgroud)

使用RENAME TABLE以原子方式移动原始表并将副本重命名为原始名称:

RENAME TABLE t TO t_old, t_copy TO t;
Run Code Online (Sandbox Code Playgroud)

删除原始表:

DROP TABLE t_old;
Run Code Online (Sandbox Code Playgroud)

在RENAME TABLE执行时,没有其他会话可以访问所涉及的表,因此重命名操作不会受到并发问题的影响.请参见第12.1.9节"RENAME TABLE语法".

所以在你的情况下,你可以这样做

INSERT INTO value_copy SELECT * FROM VALUE WHERE key_id IN
    (SELECT key_id FROM `KEY`);

RENAME TABLE value TO value_old, value_copy TO value;

DROP TABLE value_old;
Run Code Online (Sandbox Code Playgroud)

根据他们在这里写的内容, RENAME操作很快,记录数量也不会影响它.

  • 问题是,将 5 亿条记录插入附近的表并提交事务需要一段时间。否则,如果原始表处于高负载且正在更新,则将复制表与原始表同步时会出现问题。 (2认同)