我最近发现并修复了我正在处理的网站中的一个错误,导致表中数百万个重复的数据行即使没有它们也会非常大(仍然是数百万)。我可以很容易地找到这些重复的行,并可以运行单个删除查询来杀死它们。问题是尝试一次性删除这么多行会长时间锁定表,如果可能的话我想避免这种情况。我可以看到摆脱这些行的唯一方法,而不是取下网站(通过锁定表):
我只是想知道是否有其他人之前有这个问题,如果是这样,你如何处理它而不取下网站,并希望,如果有任何中断用户?如果我使用2号或类似的方法,我可以安排这些东西深夜运行并在第二天早上进行合并,并提前告知用户,这不是什么大不了的事。我只是想看看是否有人有更好或更简单的方法来进行清理。
DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000
洗涤,冲洗,重复直至零行受影响。也许在一个脚本中,在迭代之间休眠一两秒钟。
我认为缓慢是由于MySQl的“聚集索引”,其中实际记录存储在主键索引中 - 按主键索引的顺序。这意味着通过主键访问记录非常快,因为它只需要一次磁盘提取,因为磁盘上的记录就在那里找到索引中正确的主键。
在没有聚簇索引的其他数据库中,索引本身不保存记录,而只是一个“偏移”或“位置”,指示记录在表文件中的位置,然后必须在该文件中进行第二次提取以检索实际数据。
您可以想象,当删除聚簇索引中的记录时,必须向下移动表中该记录以上的所有记录,以避免在索引中创建大量漏洞(这是我几年前至少记得的 - 以后的版本可能已经改变了这一点)。
知道上面我们发现在MySQL中真正加速删除的是以相反的顺序执行删除。这会产生最少量的记录移动,因为您首先从末尾删除记录,这意味着后续删除具有较少的要重定位的对象。
我没有编写任何脚本来执行此操作,并且正确地执行它绝对需要脚本,但另一个选项是创建一个新的重复表并选择要保留的所有行。在此过程完成时,使用触发器使其保持最新状态。当它同步时(减去你要删除的行),重命名一个事务中的两个表,以便新的表取代旧的。放下旧桌子,瞧!
这(显然)需要大量额外的磁盘空间,并且可能会对您的I / O资源造成负担,但除此之外,可能会更快。
根据数据的性质或在紧急情况下,您可以重命名旧表并在其中创建一个新的空表,并在闲暇时选择“保留”行到新表中...
我还建议在您的表中添加一些约束,以确保不再发生这种情况。一百万行,每次射击1000次,将完成1000次重复的脚本。如果脚本每3.6秒运行一次,您将在一小时内完成。别担心。您的客户不太可能注意到。
以下内容一次删除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
你可以将它们组合在一起并删除table_name,其中IN(id1,id2,.. idN)我确定太难了
我有一个用例在MySQL的25M +行表中删除1M +行。尝试了不同的方法,如批量删除(如上所述)。 我发现了最快的方法(将所需记录复制到新表中):
CREATE TABLE id_temp_table(temp_id int);
插入到id_temp_table(temp_id)中选择.....
insert into table_new .... where table_id NOT IN(从id_temp_table中选择distinct(temp_id));
整个过程耗时约1小时。在我的用例中,简单删除100条记录上的批量需要10分钟。
我使用来自优秀的mk-archiver实用程序包的Maatkit(一组用于MySQL管理的Perl脚本)Maatkit来自O'Reilly“High Performance MySQL”一书的作者Baron Schwartz。
目标是一个低影响,仅向前的工作,从表中剔除旧数据,而不会影响OLTP查询。您可以将数据插入到另一个表中,该表不必位于同一服务器上。您也可以以适合LOAD DATA INFILE的格式将其写入文件。或者你也不能做,在这种情况下,它只是一个增量DELETE。
它已经构建为以小批量存档您不需要的行,作为奖励,它可以将删除的行保存到文件中,以防您搞砸了选择要删除的行的查询。
无需安装,只需抓住http://www.maatkit.org/get/mk-archiver并在其上运行perldoc(或阅读网站)以获取文档。
分批做,一次说2000行。承诺介于两者之间。百万行不是那么多,这将是快速的,除非你在表上有很多索引。
根据mysql documentation,TRUNCATE TABLE
是DELETE FROM
的快速替代品。试试这个:
TRUNCATE TABLE table_name
我在50M行上尝试了这个,它在两分钟内完成。
注意:截断操作不是事务安全的;在活动事务或活动表锁定过程中尝试一个错误时发生错误
对我们来说,DELETE WHERE %s ORDER BY %s LIMIT %d
答案不是一个选项,因为WHERE标准很慢(非索引列),并且会击中master。
从读取副本中选择要删除的主键列表。使用这种格式导出:
00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35
使用以下bash脚本来获取此输入并将其块化为DELETE语句[由于mapfile
内置需要bash≥4]:
sql-chunker.sh
(记得chmod +x
我,并改变shebang指向你的bash 4可执行文件):
#!/usr/local/Cellar/bash/4.4.12/bin/bash
# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!
if [ -z "$1" ]
then
echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi
if [ -z "$2" ]
then
echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi
function join_by {
local d=$1
shift
echo -n "$1"
shift
printf "%s" "${@/#/$d}"
}
while mapfile -t -n "$1" ary && ((${#ary[@]})); do
printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"
像这样调用:
./sql-chunker.sh 1000 ids.txt > batch_1000.sql
这将为您提供一个格式如此的输出文件(我使用批量大小为2):
DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');
然后像这样执行语句:
mysql --login-path=master billing < batch_1000.sql
对于那些不熟悉login-path
的人来说,它只是一个快捷方式,无需在命令行输入密码即可登录。
我遇到了类似的问题。我们有一个非常大的表,大小约为500 GB,没有分区,在primary_key列上只有一个索引。我们的主人是一台机器,128个核心和512 GAG RAM,我们也有多个奴隶。我们尝试了一些技术来解决行的大规模删除问题。我会在这里列出所有我们发现的最差到最好的 -
所以,IMO,如果你能负担得起在你的桌子上创建分区的奢侈,那么选择#4选项,否则,你会遇到选项#3。