从巨大的 MySQL innoDB Table 中删除记录
Deleting Records from huge MySQL innoDB Table
我知道这个问题已被问过一百次,但不幸的是 none 这些答案有所帮助,因为大多数都已经很多年了,应该证明另一个答案是正确的。
我有两个表,records
和 +140kk rows/+24GB
以及 extra
和 +89kk rows/+70GB
。
每个 extra
行与 records
有一个外键关系,在两个表之间正确设置了索引。 records
中的删除将删除级联到相关的 extra
行。
我需要在制作过程中清除旧记录。
运行 DELETE FROM records WHERE WHERE created < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) ORDER BY id LIMIT 1000;
需要无穷无尽(我在 20 分钟后它还在 init
的地方杀了它)。对 SELECT
执行相同操作仅需几毫秒即可完成。
因为 SELECT
太快了,我用 id IN (SELECT id subquery^)
试了一下,不幸的是这并没有改变什么,所以我在 15 分钟后就把它杀了。
由于删除单个记录的速度很快,我最终采用了以下解决方法:
for i in `seq 1 100000`; do
mysql database -e "SELECT id FROM records WHERE created < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) ORDER BY id LIMIT 1000;" | sed 's;/|;;g' | awk '{if(NR>1)print "DELETE FROM records WHERE id = ",,";" }' | mysql database;
now=$(date +"%Y/%m/%d %T")
echo "[ $now ] $i.000"
done
开始时每秒可以处理一千条记录,但经过几次循环后,它会下降到每 10-20 秒一千条。由于我需要清除 10 万行,这将需要将近一个月的时间才能完成,这与要清除的记录的时间范围大致相同,因此它永远不会完成(特别是因为有多个数据库需要它)。
数据库存储在 SSD Crucial_CT500MX200SSD1
上,软件 MariaDB 10.1
带有 InnoDB
引擎。 innodb_flush_log_at_trx_commit
设置为 0
以避免不必要的磁盘使用。
根据 atop
瓶颈是磁盘,CPU 几乎处于休眠状态,大部分内存在系统缓存中。
结构转储:https://gist.github.com/Slind14/0da34e09dba91cf411db2ead5ad666ef
见http://mysql.rjweb.org/doc.php/deletebig
它讨论了从大 tables 中有效删除大量行的各种方法。
可能最有效的方法是根据 PRIMARY KEY
(它有这样的?)遍历 table,删除该块中符合条件的任何行。
之所以高效,是因为它一次查看 100 行,处理它们,然后才继续处理下 100 行。相反,使用 LIMIT
和无限 WHERE
子句可能需要一遍又一遍地扫描相同的行。
您提到了 JOIN
。根据成本的高低,100 可能是一个很好的限制。如果JOIN
不是太繁琐的话,1000或许也行。
如果这是一项重复性任务,请考虑使用 PARTITIONing
,以便 DROP PARTITION
可用于 'instantly' 执行任务。
我知道这个问题已被问过一百次,但不幸的是 none 这些答案有所帮助,因为大多数都已经很多年了,应该证明另一个答案是正确的。
我有两个表,records
和 +140kk rows/+24GB
以及 extra
和 +89kk rows/+70GB
。
每个 extra
行与 records
有一个外键关系,在两个表之间正确设置了索引。 records
中的删除将删除级联到相关的 extra
行。
我需要在制作过程中清除旧记录。
运行 DELETE FROM records WHERE WHERE created < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) ORDER BY id LIMIT 1000;
需要无穷无尽(我在 20 分钟后它还在 init
的地方杀了它)。对 SELECT
执行相同操作仅需几毫秒即可完成。
因为 SELECT
太快了,我用 id IN (SELECT id subquery^)
试了一下,不幸的是这并没有改变什么,所以我在 15 分钟后就把它杀了。
由于删除单个记录的速度很快,我最终采用了以下解决方法:
for i in `seq 1 100000`; do
mysql database -e "SELECT id FROM records WHERE created < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY)) ORDER BY id LIMIT 1000;" | sed 's;/|;;g' | awk '{if(NR>1)print "DELETE FROM records WHERE id = ",,";" }' | mysql database;
now=$(date +"%Y/%m/%d %T")
echo "[ $now ] $i.000"
done
开始时每秒可以处理一千条记录,但经过几次循环后,它会下降到每 10-20 秒一千条。由于我需要清除 10 万行,这将需要将近一个月的时间才能完成,这与要清除的记录的时间范围大致相同,因此它永远不会完成(特别是因为有多个数据库需要它)。
数据库存储在 SSD Crucial_CT500MX200SSD1
上,软件 MariaDB 10.1
带有 InnoDB
引擎。 innodb_flush_log_at_trx_commit
设置为 0
以避免不必要的磁盘使用。
根据 atop
瓶颈是磁盘,CPU 几乎处于休眠状态,大部分内存在系统缓存中。
结构转储:https://gist.github.com/Slind14/0da34e09dba91cf411db2ead5ad666ef
见http://mysql.rjweb.org/doc.php/deletebig
它讨论了从大 tables 中有效删除大量行的各种方法。
可能最有效的方法是根据 PRIMARY KEY
(它有这样的?)遍历 table,删除该块中符合条件的任何行。
之所以高效,是因为它一次查看 100 行,处理它们,然后才继续处理下 100 行。相反,使用 LIMIT
和无限 WHERE
子句可能需要一遍又一遍地扫描相同的行。
您提到了 JOIN
。根据成本的高低,100 可能是一个很好的限制。如果JOIN
不是太繁琐的话,1000或许也行。
如果这是一项重复性任务,请考虑使用 PARTITIONing
,以便 DROP PARTITION
可用于 'instantly' 执行任务。