0

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

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

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

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

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

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

我还尝试使用 LIMIT 以及 WHERE custom_condition(C1) 进行更新。这些方法确实允许我提前终止更新,但它们比常规更新慢得多,并且它们不能在特定时间终止查询(在另一个连接超时到期之前)。

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

4

3 回答 3

3

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

不,这实际上很有意义,因为您没有执行多个独立的更新。您正在执行单个更新语句。精美的手册说

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

如果可以确定所涉及的键的范围,则可以执行多个更新语句。例如,如果一个键是一个整数,并且您确定范围是从 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 秒内插入 10 万行,并在 420 毫秒内通过简单的计算更新 10 万行。如果您在手机上运行,​​那可能无关紧要。

于 2013-06-14T02:00:18.893 回答
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
) 

PS关于缓慢:

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...

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

于 2013-06-14T10:59:59.887 回答
0

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

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

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

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

于 2014-06-04T02:30:09.537 回答