删除MySQL中的数百万行

Ste*_*iec 68 mysql maintenance query-performance sql-delete

我最近发现并修复了我正在处理的网站中的一个错误,导致表中数百万个重复的数据行即使没有它们也会非常大(仍然是数百万).我可以很容易地找到这些重复的行,并可以运行单个删除查询来终止它们.问题是尝试一次性删除这么多行会长时间锁定表,如果可能的话我想避免这种情况.我可以看到摆脱这些行的唯一方法,而不是取下网站(通过锁定表):

  1. 编写一个脚本,在循环中执行数千个较小的删除查询.这理论上会解决锁定表问题,因为其他查询将能够进入队列并在删除之间运行.但它仍然会在数据库上加载相当多的负载,并且需要很长时间才能运行.
  2. 重命名表并重新创建现有表(它现在将为空).然后在重命名的表上进行清理.重命名新表,将旧表命名并将新行合并到重命名的表中.这需要采取相当多的步骤,但应该以最小的中断完成工作.这里唯一棘手的部分是所讨论的表格是一个报表,所以一旦它重新命名,而空的一个放在它的位置,所有历史报告都会消失,直到我把它放回原位.此外,由于存储的数据类型,合并过程可能会有点痛苦.总的来说,这是我现在可能的选择.

我只是想知道是否有其他人之前有这个问题,如果是这样,你如何处理它而不取下网站,并希望,如果有任何中断用户?如果我使用2号或类似的方法,我可以安排这些东西在深夜运行并在第二天早上进行合并,并且让用户提前知道,这不是什么大问题.我只是想看看是否有人有更好或更简单的方法来进行清理.

cha*_*aos 134

DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000
Run Code Online (Sandbox Code Playgroud)

洗涤,冲洗,重复直至零行受影响.也许在一个脚本中,在迭代之间休眠一两秒钟.

  • 如果你使用带有LIMIT的DELETE,你应该使用ORDER BY来使查询具有确定性; 不这样做会产生奇怪的效果(包括在某些情况下破坏复制) (43认同)
  • KISS总是赢. (6认同)
  • 请注意[一个不能组合](http://stackoverflow.com/a/6879716/2908724)`DELETE ... JOIN`与`ORDER BY`或`LIMIT`. (4认同)
  • 我仍然怀疑如果一个数据透视表不是最好的方式,但是,我做了一个程序,只是为了保持理智:https://hastebin.com/nabejehure.pas (3认同)
  • 这是一个实现这种方法的简单Python脚本:https://gist.github.com/tsauerwein/ffb159d1ab95d7fd91ef43b9609c471d (2认同)

Vol*_*man 13

我认为缓慢是由于 MySQl 的“聚集索引”,其中实际记录存储在主键索引中 - 按照主键索引的顺序。这意味着通过主键访问记录的速度非常快,因为它只需要一次磁盘读取,因为磁盘上的记录就在索引中找到正确主键的地方。

在没有聚集索引的其他数据库中,索引本身不保存记录,而只是指示记录在表文件中的位置的“偏移量”或“位置”,然后必须在该文件中进行第二次提取以检索实际数据。

你可以想象,当删除聚集索引中的一条记录时(就像MySQL使用的那样),索引(=表)中该记录之上的所有记录都必须向下移动,以避免在索引中创建大量的漏洞(这就是我记得的)至少几年前 - 8.x 版本可能已经改善了这个问题)。

有了上述“幕后”操作的知识,我们发现在 MySQL 5.x 中真正加速删除的是按相反的顺序执行删除。这会产生最少的记录移动量,因为您首先从末尾删除记录,这意味着后续删除需要重新定位的记录更少 - 符合逻辑吗?!

  • 我真的很喜欢这个想法!我喜欢它在视觉上有意义,就像孩子能理解的玩具一样。 (2认同)
  • 这对我来说真的很重要。在具有 5M 行的表中删除 10K 行最初需要 5 分钟。然后我在删除语句中添加了ORDER BY id DESC LIMIT 10000,只花了1秒。后来我把大小一次增加到1M。整个过程持续了10分钟。 (2认同)
  • @GaniSimsek 我总是很高兴听到其他人从我的一些“这太疯狂了,它可能会起作用”的想法中受益的案例:) (2认同)

duf*_*ymo 8

我还建议在您的表中添加一些约束,以确保不再发生这种情况.一百万行,每次射击1000次,将完成1000次重复的脚本.如果脚本每3.6秒运行一次,您将在一小时内完成.别担心.您的客户不太可能注意到.


ric*_*ch 7

以下内容一次删除1,000,000条记录.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done
Run Code Online (Sandbox Code Playgroud)

你可以将它们组合在一起并删除table_name,其中IN(id1,id2,.. idN)我确定太难了


use*_*144 7

我有一个用例在MySQL的25M +行表中删除1M +行.尝试了不同的方法,如批量删除(如上所述).
我发现了最快的方法(将所需记录复制到新表中):

  1. 创建仅包含ID的临时表.

CREATE TABLE id_temp_table(temp_id int);

  1. 插入应删除的ID:

插入到id_temp_table(temp_id)中选择.....

  1. 创建新表table_new

  2. 将所有记录从表插入到table_new,而不使用id_temp_table中的不必要的行

insert into table_new .... where table_id NOT IN(从id_temp_table中选择distinct(temp_id));

  1. 重命名表

整个过程耗时约1小时.在我的用例中,简单删除100条记录上的批量需要10分钟.


Tho*_*Tho 6

以下是推荐的做法:

rows_affected = 0
do {
 rows_affected = do_query(
   "DELETE FROM messages WHERE created < DATE_SUB(NOW(),INTERVAL 3 MONTH)
   LIMIT 10000"
 )
} while rows_affected > 0
Run Code Online (Sandbox Code Playgroud)

一次删除 10,000 行通常是一个足够大的任务,可以使每个查询高效,并且是一个足够短的任务,可以最大限度地减少对服务器的影响4(事务存储引擎可能会受益于较小的事务)。在 DELETE 语句之间添加一些睡眠时间也可能是一个好主意,以便随着时间的推移分散负载并减少持有锁的时间量。

参考MySQL 高性能