在MySQL中删除数百万行
我最近发现并修复了一个我正在工作的站点中的一个bug,该bug导致表中有数百万个重复的数据行,即使没有这些数据行,该表也会非常大(仍然有数百万)。我可以很容易地找到这些重复的行,并可以运行一个删除查询将它们全部删除。问题是,试图一次删除这么多行会将表锁定很长一段时间,如果可能的话,我希望避免这种情况。在不关闭站点(通过锁定表)的情况下,我能看到的摆脱这些行的唯一方法是:在MySQL中删除数百万行,mysql,query-performance,maintenance,sql-delete,Mysql,Query Performance,Maintenance,Sql Delete,我最近发现并修复了一个我正在工作的站点中的一个bug,该bug导致表中有数百万个重复的数据行,即使没有这些数据行,该表也会非常大(仍然有数百万)。我可以很容易地找到这些重复的行,并可以运行一个删除查询将它们全部删除。问题是,试图一次删除这么多行会将表锁定很长一段时间,如果可能的话,我希望避免这种情况。在不关闭站点(通过锁定表)的情况下,我能看到的摆脱这些行的唯一方法是: 编写一个脚本,在循环中执行数千个较小的删除查询。从理论上讲,这将绕过锁定表问题,因为其他查询将能够进入队列并在删除之间运行。但
DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000
清洗,漂洗,重复,直到零行受到影响。可能在一个脚本中,迭代之间会休眠一秒或三秒。一次可以成批执行2000行。在两者之间作出承诺。一百万行并没有那么多,而且这会很快,除非您的表上有很多索引。我还建议您在表中添加一些约束,以确保不会再次发生这种情况。100万行,每拍1000行,需要1000次脚本重复才能完成。如果脚本每3.6秒运行一次,您将在一小时内完成。别担心。您的客户不太可能注意到。我从优秀的实用程序包(用于MySQL管理的一组Perl脚本)中使用的Maatkit来自Baron Schwartz,他是O'Reilly“高性能MySQL”一书的作者 目标是低冲击,仅向前 将旧数据从数据库中删除的作业 不影响OLTP查询的表 很您可以将数据插入到另一个数据库中 表,不必在同一个表上 服务器。你也可以把它写到 文件的格式适合于加载 数据填充。或者你两个都不能做,在美国 在这种情况下,它只是一个增量 删除 它已经为小批量归档不需要的行而构建,并且作为一个额外功能,它可以将删除的行保存到一个文件中,以防您在选择要删除的行的查询中出错 无需安装,只需抓取并在其上运行perldoc(或阅读网站)即可获取文档。根据,
截断表
是从
删除的快速替代方法。试试这个:
TRUNCATE TABLE table_name
截断表名
我在50米的排上试过这个,两分钟内就完成了
注意:截断操作不是事务安全的;在活动事务或活动表锁定过程中尝试一条记录时出错。以下操作每次删除1000000条记录
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
您可以将它们组合在一起并删除表名,在(id1,id2,…idN)中我肯定太难了我有一个在MySQL的25M+行表中删除1M+行的用例。 尝试了不同的方法,如批量删除(如上所述)。
我发现最快的方法(将所需记录复制到新表):
整个过程耗时约1小时。在我的用例中,简单地删除100条记录的批次需要10分钟。对于我们来说,
删除其中的%s ORDER BY%s LIMIT%d
答案不是一个选项,因为WHERE条件很慢(非索引列),并且会击中master
从读取副本中选择要删除的主键列表。以这种格式导出:
00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35
使用以下bash脚本获取此输入并将其分块到DELETE语句中[需要bash≥ 4由于mapfile
内置]:
sqlchunker.sh
(记住chmod+x
me,并将shebang更改为指向bash4可执行文件):
#/usr/local/ceral/bash/4.4.12/bin/bash
#预期输入格式:
:
这样调用:
/sql-chunker.sh 1000 ids.txt>批处理\u 1000.sql
这将为您提供一个输出格式如下的文件(我使用的批处理大小为2):
从my_cool_表中删除,其中id位于('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
从我的“酷”表中删除,其中id位于('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-80
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
#!/bin/bash
#######################
#
i_size=1000
max_delete_queries=10
sleep_interval=15
min_operations=8
max_query_time=1000
USER="user"
PASS="super_secret_password"
log_max_size=1000000
log_file="/var/tmp/clean_up.log"
#
#######################
touch $log_file
log_file_size=`stat -c%s "$log_file"`
if (( $log_file_size > $log_max_size ))
then
rm -f "$log_file"
fi
delete_queries=`mysql -u user -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l`
## -- here the hanging DELETE queries will be stopped
mysql-u $USER -p$PASS -e "SELECT ID FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %'and TIME>$max_query_time;" |grep -v ID| while read -r id ; do
echo "delete query stopped on `date`" >> $log_file
mysql -u $USER -p$PASS -e "KILL $id;"
done
if (( $delete_queries > $max_delete_queries ))
then
sleep $sleep_interval
delete_queries=`mysql-u $USER -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l`
if (( $delete_queries > $max_delete_queries ))
then
sleep $sleep_interval
delete_queries=`mysql -u $USER -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l`
# -- if there are too many delete queries after the second wait
# the table will be cleaned up by the next cron job
if (( $delete_queries > $max_delete_queries ))
then
echo "clean-up skipped on `date`" >> $log_file
exit 1
fi
fi
fi
running_operations=`mysql-u $USER -p$PASS -p -e "SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE COMMAND != 'Sleep';"| wc -l`
if (( $running_operations < $min_operations ))
then
# -- if the database is not too busy this bigger batch can be processed
batch_size=$(($i_size * 5))
else
batch_size=$i_size
fi
echo "starting clean-up on `date`" >> $log_file
mysql-u $USER -p$PASS -e 'DELETE FROM big.table WHERE result_timestamp < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 31 DAY))*1000 limit '"$batch_size"';'
if [ $? -eq 0 ]; then
# -- if the sql command exited normally the exit code will be 0
echo "delete finished successfully on `date`" >> $log_file
else
echo "delete failed on `date`" >> $log_file
fi
CREATE TABLE mytable_temp AS SELECT * FROM my_original_table WHERE my_condition;
TRUNCATE TABLE my_original_table;
INSERT INTO my_original_table SELECT * FROM mytable_temp;