【问题标题】:SQLITE - unable to combine thousands of delete statements into single transactionSQLITE - 无法将数千个删除语句组合成单个事务
【发布时间】:2021-06-30 08:46:24
【问题描述】:

我面临一个问题,即从单个表中删除数千行需要很长时间(14k 记录超过 2 分钟),而插入相同的记录几乎是即时的(200 毫秒)。插入和删除语句的处理方式相同 - 循环生成语句并将它们附加到列表中,然后将列表传递给打开事务的单独函数,执行所有语句,然后以提交结束。至少这是我在开始使用伪代码测试之前的印象——但看起来我误解了手动打开事务的必要性。

我已经阅读了有关交易的信息 (https://www.sqlite.org/faq.html#q19),但由于插入几乎是即时的,所以我不确定这里是否是这种情况。

我的理解是事务 == 提交,如果这是正确的,那么看起来所有删除语句都在一个事务中 - 在处理过程中我可以看到所有已删除的行,直到最终提交,之后它们是实际删除。即下面的常见问题链接中的情况应该不同 - 因为没有提交发生。但是速度慢表明它仍在做一些其他事情,这会减慢速度,就好像每个删除语句都是一个单独的事务一样。

运行伪代码后,似乎在发送显式提交(通过 conn.commit())之前不会提交更改,但循环前面的“开始”或“开始事务”没有任何效果。我认为这是因为 sqlite3 在后台自动发送“开始”(Merge SQLite files into one db file, and 'begin/commit' question

用伪代码测试一下:

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}')

一个奇怪的事情是行数成倍增加删除时间(2s 删除 10k 行,7s 删除 20k 行,43s 删除 50k 行)而无论行数如何,插入时间都是即时的。

编辑: 最初的问题是 - 为什么 delete 语句比 insert 语句花费更多时间,以及如何加快它的速度,以便插入和删除行的速度相似。

根据snakecharmerb 的建议,一种解决方法是这样做:

rows = 100000
delete_ids = ''
for i in range(rows):
    if delete_ids:
        delete_ids += f',"{i}"'
    else:
        delete_ids += f'"{i}"'
delete_str = f'''DELETE from test_table where column1 IN ({delete_ids});'''
conn.execute(delete_str)
conn.commit()

虽然这很可能违反所有最佳实践,但它似乎确实有效 - 删除 100 万行大约需要 2 秒。

【问题讨论】:

  • 1) 为什么删除语句比插入语句花费更多时间? 2) 如何加快删除进程?
  • 我不知道为什么它们需要更长的时间(在我的机器上,您的删除脚本需要 6 秒,插入 0 秒)。为了加快速度,您可以尝试:(1)DELETE FROM tbl WHERE id IN (1,2,3...) 每个语句可能有一个最佳的 id 数量(对于 Postgres,我认为大约是 80;不知道对于 sqlite)(2)cursor.executemany(3)中间提交(在每删除 1000 行之后说)
  • 是的,正如我在原帖中提到的,如果你增加第 5 行的行数,它会呈指数级增长。虽然仍然处于缓慢的一边,我什至会忍受不到 10 秒的时间来删除30k 条记录,但在实时系统上,删除 14k 行大约需要 2 分钟,这是不可接受的。我将测试 IN(1,2,3..) 语法以及中间提交,如果他们有帮助,我会更新这篇文章。

标签: python sqlite


【解决方案1】:

我尝试批量删除 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 似乎会使性能更差。

【讨论】:

  • 看来IN () 语法是正确的方法,至少就解决方法而言。虽然它仍然困扰着我为什么插入行的“相同”方法没有任何问题。旁注:我还没有测试过executemany,但我确实测试了executescript,并通过了由;分隔的所有删除语句的str。但这对速度没有任何影响如果是删除所有行的情况,那么查询定义的删除将起作用,但不幸的是在这种情况下它不适合我的需要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-18
  • 1970-01-01
  • 2014-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多