我面临的问题是,从单个表中删除数千行需要很长时间(14k 条记录需要超过 2 分钟),而插入相同的记录几乎是即时的(200 毫秒)。插入和删除语句的处理方式相同 - 循环生成语句并将它们附加到列表中,然后将该列表传递给一个单独的函数,该函数打开一个事务,执行所有语句,然后以提交结束。至少这是我在开始用伪代码测试之前的印象 - 但看起来我误解了手动打开交易的需要。
我读过有关事务的内容(https://www.sqlite.org/faq.html#q19),但由于插入几乎是即时的,所以我不确定这里是否是这种情况。
我的理解是事务==提交,如果这是正确的,那么看起来所有删除语句都在一个事务中 - 在处理过程中我可以看到所有已删除的行,直到最终提交,之后它们实际上被删除。即下面的常见问题解答链接中的情况应该有所不同 - 因为没有发生提交。但缓慢的速度表明它仍在做其他事情,这会减慢速度,就好像每个删除语句都是一个单独的事务一样。
运行伪代码后,虽然在发送显式提交(通过 conn.commit())之前更改不会提交,但循环前面的“开始”或“开始事务”没有任何效果。我认为这是因为 sqlite3 在后台自动发送“开始”(将 SQLite 文件合并到一个数据库文件中,以及“开始/提交”问题)
用于测试这一点的伪代码:
import sqlite3
from datetime import datetime
insert_queries = []
delete_queries = []
rows = 30000
for i in range(rows):
insert_queries.append(f'''INSERT INTO test_table ("column1") VALUES ("{i}");''')
for i in range(rows):
delete_queries.append(f'''DELETE from test_table where column1 ="{i}";''')
conn = sqlite3.connect('/data/test.db', check_same_thread=False)
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
print('*'*50)
print(f'Starting inserts: {timestamp}')
# conn.execute('BEGIN TRANSACTION')
for query in insert_queries:
conn.execute(query)
conn.commit()
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
print(f'Finished inserts: {timestamp}')
print('*'*50)
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
print(f'Starting deletes: {timestamp}')
# conn.isolation_level = None
# conn.execute('BEGIN;')
# conn.execute('BEGIN TRANSACTION;')
for query in delete_queries:
conn.execute(query)
conn.commit()
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S")
print(f'Finished deletes: {timestamp}')
一个奇怪的事情是,行数呈指数级增加删除时间(删除 10k 行需要 2 秒,删除 20k 行需要 7 秒,删除 50k 行需要 43 秒),而无论行数如何,插入时间都是即时的。
我尝试以 50 个为一组进行批量删除:
...
batches = []
batch = []
for i in range(rows):
batch.append(str(i))
if len(batch) == 50:
batches.append(batch)
batch = []
if batch:
batches.append(batch)
...
base = 'DELETE FROM test_table WHERE column1 IN ({})'
for batch in batches:
placeholders = ','.join(['?'] * len(batch))
sql = base.format(placeholders)
conn.execute(sql, batch)
conn.commit()
...
这将持续时间减少到 1 - 2 秒(从原来的 6 - 8 秒)。
将此方法与
executemany
相结合,持续时间为 1 秒。
使用查询来定义已删除的列几乎是即时的
DELETE FROM test_table WHERE column1 IN (SELECT column1 FROM test_table)
但 Sqlite 可能会识别出此查询与裸的
DELETE FROM test_table
相同并进行优化。
关闭 secure_delete PRAGMA 似乎会使性能变得更糟。