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

时间:2013-10-18 11:44:26

标签: mysql

我有两张桌子。我们称之为KEY和VALUE KEY很小,约有1.000.000条记录 价值很高,比如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)
);

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

这很容易做到

delete v 
  from VALUE v
  left join KEY k using (key_id)
 where k.key_id is null;

但是,由于不允许在多表删除中使用limit,所以我不喜欢这种方法。这样的删除需要几个小时才能运行,这使得无法限制删除。

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

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

12 个答案:

答案 0 :(得分: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

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

pt-archiverPercona Toolkit for MySQL的一部分,{{3}}是一组免费的(GPL)脚本,可帮助MySQL和兼容数据库执行常见任务。

答案 1 :(得分:5)

直接来自MySQL documentation

  

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

     

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

INSERT INTO t_copy SELECT * FROM t WHERE ... ;
     

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

RENAME TABLE t TO t_old, t_copy TO t;
     

删除原始表格:

DROP TABLE t_old;
     

在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;

根据他们写的here,RENAME操作很快,记录数量不会影响它。

答案 2 :(得分:4)

有限制怎么样?

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;

答案 3 :(得分:2)

首先,检查您的数据。找到具有太多值的键,可以“快速”删除。然后找出白天您在系统上负载最小的时间。在此期间执行“坏”键的删除。对于其余的,开始逐个删除它们,删除之间有一些停机时间,这样你就不会对数据库施加太大的压力。

答案 4 :(得分:1)

可能不是通过key_id将整个行集限制为小部分:

delete v 
  from VALUE v
  left join KEY k using (key_id)
 where k.key_id is null and v.key_id > 0 and v.key_id < 100000;

然后在100000..200000中使用key_id删除行,依此类推。

答案 5 :(得分:1)

您可以尝试在分离的事务批处理中删除。 这适用于MSSQL,但应该类似。

declare @i INT
declare @step INT
set @i = 0
set @step = 100000

while (@i< (select max(VALUE.key_id) from VALUE))
BEGIN
  BEGIN TRANSACTION
  delete from VALUE where
    VALUE.key_id between @i and @i+@step and
    not exists(select 1 from KEY where KEY.key_id = VALUE.key_id and KEY.key_id between @i and @i+@step)

  set @i = (@i+@step)
  COMMIT TRANSACTION
END

答案 6 :(得分:1)

创建一个临时表!

drop table if exists batch_to_delete;
create temporary table batch_to_delete as
select v.* from `VALUE` v
left join `KEY` k on k.key_id = v.key_id
where k.key_id is null
limit 10000; -- tailor batch size to your taste

-- optional but may help for large batch size
create index batch_to_delete_ix_key on batch_to_delete(key_id); 
create index batch_to_delete_ix_value on batch_to_delete(value_id);

-- do the actual delete
delete v from `VALUE` v
join batch_to_delete d on d.key_id = v.key_id and d.value_id = v.value_id;

答案 7 :(得分:1)

对我而言,这是一项任务,我希望在日志文件中看到它的进展。我会避免在纯SQL中解决这个问题,我会在Python或其他类似语言中使用一些脚本。另一件令我困扰的事情是,许多LEFT JOIN与表之间的WHERE IS NOT可能会导致不必要的锁定,所以我也会避免使用JOIN。

这是一些伪代码:

max_key = select_db('SELECT MAX(key) FROM VALUE')
while max_key > 0:
    cur_range = range(max_key, max_key-100, -1)
    good_keys = select_db('SELECT key FROM KEY WHERE key IN (%s)' % cur_range)
    keys_to_del = set(cur_range) - set(good_keys)
    while 1:
        deleted_count = update_db('DELETE FROM VALUE WHERE key IN (%s) LIMIT 1000' % keys_to_del)
        db_commit
        log_something
        if not deleted_count:
            break
    max_key -= 100

这不应该打扰系统的其他部分,但可能需要很长时间。另一个问题是在删除所有这些行后优化表,但这是另一个故事。

答案 8 :(得分:1)

如果目标列已正确编入索引,则应该快速进行,

DELETE FROM `VALUE`
WHERE NOT EXISTS(SELECT 1 FROM `key` k WHERE k.key_id = `VALUE`.key_id)
-- ORDER BY key_id, value_id -- order by PK is good idea, but check the performance first.
LIMIT 1000

将限制从10改为10000以获得可接受的性能,并重新运行几次。

另请注意,此批量删除将为每行执行锁定和备份。 多次执行每行的执行时间......

有一些先进的方法可以防止这种情况,但最简单的解决方法 只是围绕此查询进行交易。

答案 9 :(得分:0)

您是否拥有包含相同数据的SLAVE或Dev / Test环境?

如果您担心具有100万value_ids的特定密钥,第一步是查找您的数据分发

SELECT v.key_id, COUNT(IFNULL(k.key_id,1)) AS cnt 
FROM `value` v  LEFT JOIN `key` k USING (key_id) 
WHERE k.key_id IS NULL 
GROUP BY v.key_id ;

以上查询的EXPLAIN PLAN比添加

要好得多
ORDER BY COUNT(IFNULL(k.key_id,1)) DESC ;

由于你没有对key_id进行分区(在你的情况下分区太多)并且想要在删除过程中保持数据库运行,所以选项是在不同的key_id删除之间删除带有SLEEP()的chucks以避免压倒性的服务器。不要忘记密切关注二进制日志以避免磁盘填充。

最快的方法是:

  1. 停止应用程序,以便不更改数据。
  2. 转储VALUE表中的key_id和value_id,只使用KEY表中匹配的key_id

    mysqldump YOUR_DATABASE_NAME值--where =“key_id in(从YOUR_DATABASE_NAME.key中选择key_id)” - lock-all --opt --quick --quote-names --skip-extended-insert&gt; VALUE_DATA.txt

  3. 截断VALUE表

  4. 加载在步骤2中导出的数据
  5. 启动申请
  6. 与往常一样,在Dev / Test环境中使用Prod数据和相同的基础架构进行尝试,这样您就可以计算停机时间。

    希望这有帮助。

答案 10 :(得分:0)

我只是好奇在表VALUE中为key_id添加非唯一索引会产生什么影响。选择性根本不高(~0.001),但我很好奇这将如何影响连接性能。

答案 11 :(得分:0)

为什么不按照某些规则将VALUE表分成几个,例如key_id模块的某些幂为2(例如256)?