Sqlite3:如何在不回滚的情况下中断长时间运行的更新?

时间:2013-06-14 00:47:22

标签: sqlite

我有一个长期运行的多行更新,例如:

 UPDATE T set C1 = calculation(C2) where C1 is NULL

如果表格很大,此更新可能需要几秒钟甚至几分钟。 在此期间,此表上的所有其他查询都失败并显示“数据库已锁定” 连接超时到期后(目前我的超时为5秒)。

我想在3秒之后停止此更新查询, 然后重新启动它。希望在几次重启后,整个表格都会更新。 另一个选择是在发出任何其他请求之前停止此更新查询 (这将需要进程间合作,但可能是可行的)。

但我无法找到一种方法来停止更新查询而不回滚 以前更新的所有记录。 我试过调用interrupt并从progress_handler返回非0。 这两种方法都会中止update命令 并回滚所有更改。 因此,似乎sqlite将此更新视为事务, 在这种情况下没有多大意义,因为所有行都是独立的。 但我无法为每一行开始新的交易,可以吗?

如果中断和progress_handler无法帮助我,我还能做些什么?

我还尝试使用LIMIT UPDATE和WHERE custom_condition(C1)。 这些方法允许我提前终止更新, 但它们明显慢于常规更新 并且他们无法在特定时间终止查询 (在另一个连接超时到期之前)。

还有其他想法吗? 这种多行更新是一种常见的操作 我希望,其他人有一个很好的解决方案。

3 个答案:

答案 0 :(得分:3)

  

因此,似乎sqlite将此更新视为事务,在这种情况下没有多大意义,因为所有行都是独立的。

不,这实际上非常有意义,因为您没有执行多个独立更新。您正在执行单个更新语句。 fine manual

  

除了在事务中,不能对数据库进行任何更改。   任何更改数据库的命令(基本上都是任何SQL命令   除SELECT之外,如果有的话,将自动启动一个事务   尚未生效。自动启动的交易是   在最后一个查询完成时提交。

如果您可以确定所涉及的密钥范围,则可以执行多个更新语句。例如,如果键是一个整数,并且您确定范围是1到1,000,000,则可以编写代码来执行这一系列更新。

begin transaction;
  UPDATE T set C1 = calculation(C2) 
  where C1 is NULL and your_key between 1 and 100000;
commit;
begin transaction;
  UPDATE T set C1 = calculation(C2) 
  where C1 is NULL and your_key between 100001 and 200000;
commit;

其他可能性。 。

  • 您可以在事务之间稍微休眠一下,以便为其他查询提供执行机会。
  • 您还可以使用应用程序代码执行时间,并计算范围值的最佳猜测,以避免超时并仍能提供良好的性能。
  • 您可以选择要更新的行的键,并使用它们的值来优化键的范围。

根据我的经验,以这种方式处理更新是不寻常的,但听起来它适合您的应用程序。

  

但我无法为每一行开始新的交易,可以吗?

嗯,你可以,但可能无法提供帮助。它与上面的方法基本相同,使用单个键而不是范围。不过,我不会解雇你进行测试。


在我的桌面上,我可以在1.455秒内插入100k行,并在420毫秒内通过简单计算更新100k行。如果您在手机上运行,​​那可能不相关。

答案 1 :(得分:0)

你提到LIMIT表现不佳。你有一个带有索引的lastupdated列吗?在您的程序的顶部,您将获得COMMENCED_DATETIME并将其用于运行中的每个批次:

update foo
set myvalue = 'x', lastupdated = UPDATE_COMMENCED
where id in
(
select id from foo where lastupdated < UPDATE_COMMENCED
limit SOME_REASONABLE_NUMBER
) 

P.S。关于缓慢:

I also tried UPDATE with LIMIT and also WHERE custom_condition(C1). These approaches do allow me to terminate update earlier, but they are significantly slower than regular update...

如果您愿意让其他进程访问陈旧数据,并且您的更新旨在不占用系统资源,为什么需要在一定时间内完成更新?似乎没有必要担心绝对术语中的性能。关注点应该是相对到其他进程 - 确保它们没有被阻止。

答案 2 :(得分:0)

我也发了这个问题 http://thread.gmane.org/gmane.comp.db.sqlite.general/81946 并获得了几个有趣的答案,例如:

  • 将rowid的范围划分为切片并一次更新一个切片

  • 使用AUTOINCREMENT功能在上次更新结束的地方开始新的更新(通过LIMIT 10000)

  • 创建一个触发器,调用select raise(fail,...)以中止更新而不回滚