使用python从大表中删除大量记录的有效方法
Posted
技术标签:
【中文标题】使用python从大表中删除大量记录的有效方法【英文标题】:Efficient way to delete a large amount of records from a big table using python 【发布时间】:2020-07-31 05:15:08 【问题描述】:我有一个大表(大约 1000 万行),我需要删除“早于”10 天的记录(根据 created_at 列)。我有一个 python 脚本,我运行它来执行此操作。 created_at 是一个 varchar(255) 并且具有诸如 for 之类的值。 1594267202000
import mysql.connector
import sys
from mysql.connector import Error
table = sys.argv[1]
deleteDays = sys.argv[2]
sql_select_query = """SELECT COUNT(*) FROM WHERE created_at / 1000 < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY))""".format(table)
sql_delete_query = """DELETE FROM WHERE created_at / 1000 < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY)) LIMIT 100""".format(table)
try:
connection = mysql.connector.connect(host=localhost,
database=myDatabase,
user=admin123,
password=password123)
cursor = connection.cursor()
#initial count of rows before deletion
cursor.execute(sql_select_query, (deleteDays,))
records = cursor.fetchone()[0]
while records >= 1:
# stuck at following below line and time out happens....
cursor.execute(sql_delete_query, (deleteDays,))
connection.commit()
cursor.execute(sql_select_query, (deleteDays,))
records = cursor.fetchone()[0]
#final count of rows after deletion
cursor.execute(sql_select_query, (deleteDays,))
records = cursor.fetchone()[0]
if records == 0:
print("\nRows deleted")
else:
print("\nRows NOT deleted")
except mysql.connector.Error as error:
print("Failed to delete: ".format(error))
finally:
if (connection.is_connected()):
cursor.close()
connection.close()
print("MySQL connection is closed")
当我运行这个脚本并且它运行 DELETE QUERY 但是......它失败了,原因是:
删除失败: 1205 (HY000): Lock wait timeout exceeded;尝试重启事务
我知道 innodb_lock_wait_timeout 当前设置为 50 秒,我可以增加它来克服这个问题,但是我宁愿不触碰超时......我想基本上删除也许是大块的?任何人都知道我可以在这里以我的代码为例吗?
【问题讨论】:
你有关于 created_at 的索引吗? created_at 是什么类型?如果它是一个日期时间并被索引,这将更有效。 created_at 没有索引,是一个 varchar(255) 创建 MySQL 存储过程,删除旧行并简单地从 python 代码中调用它。如果删除的行数很大,并且这会影响其他用户,则按块删除(每个块 100 或 100 行)。WHERE created_at / 1000 < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY))
- 这个条件是错误的,因为列值划分不允许使用该列的索引。将其转换为 WHERE created_at < 1000 * UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY))
- 所有算术运算都移至常数部分,因此只计算一次,而不是为每个单独的行执行计算的原始形式。尽可能在条件中使用未更改的列值。
【参考方案1】:
这里的一种方法可能是使用删除限制查询,以特定大小批量删除。假设批次为 100 条记录:
DELETE
FROM yourTable
WHERE created_at / 1000 < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %s DAY))
LIMIT 100;
请注意,严格来说,在使用LIMIT
时,您应该始终有一个ORDER BY
子句。我上面写的内容可能会删除 任何 100 条符合删除条件的记录。
【讨论】:
是的,我可以肯定添加一个 LIMIT 100 但我的查询会按原样执行一次吗?如何重复执行直到所有行都被删除? @Saffik 您可以循环并批量调用 delete,直到没有更多记录。最有可能的是,您使用的任何 SQL 驱动程序都会返回已删除/受影响的记录数,因此您可以使用此计数。 我更新了我的问题并添加了一个while循环....但超时仍然发生?【参考方案2】:created_at 没有索引,是一个 varchar(255) – Saffik 11 hours ago
你的问题。两个。
必须是indexed 才有希望表现出色。如果没有索引,MySQL 必须检查表中的每条记录。有了索引,它可以直接跳到匹配的那些。
虽然将整数存储为 varchar 是可行的,但 MySQL 会为您转换它,这是不好的做法;它浪费存储空间,允许不良数据,而且速度很慢。
将 created_at 更改为 bigint 以便将其存储为数字,然后对其进行索引。
alter table your_table modify column created_at bigint;
create index created_at_idx on your_table(created_at);
现在created_at
是一个索引的bigint,您的查询应该使用索引并且应该非常快。
请注意,created_at
应该是 datetime
,它以微秒精度存储时间。然后你可以使用MySQL's date functions,而无需转换。
但这会弄乱你的代码,它需要一个毫秒的纪元数,所以你被它困住了。为以后的表格记住这一点。
对于此表,您可以添加一个生成的created_at_datetime
列,以便更轻松地处理日期。当然,还要对其编制索引。
alter table your_table add column created_at_datetime datetime generated always as (from_unixtime(created_at/1000));
create index created_at_datetime on your_table(created_at_datetime);
那么你的where
子句就变得简单多了。
WHERE created_at_datetime < DATE_SUB(NOW(), INTERVAL %s DAY)
【讨论】:
以上是关于使用python从大表中删除大量记录的有效方法的主要内容,如果未能解决你的问题,请参考以下文章