0

次のような長期にわたる複数行の更新があります。

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

テーブルが大きい場合、この更新には数秒または数分かかる場合があります。この間、このテーブルの他のすべてのクエリは、接続タイムアウトの期限が切れた後、「データベースがロックされています」で失敗します (現在、私のタイムアウトは 5 秒です)。

この更新クエリを、たとえば 3 秒後に停止してから再開したいと思います。うまくいけば、数回再起動すると、テーブル全体が更新されます。別のオプションは、他のリクエストを行う前にこの更新クエリを停止することです (これにはプロセス間の協力が必要ですが、実行可能な場合があります)。

しかし、以前に更新されたすべてのレコードをロールバックせずに更新クエリを停止する方法が見つかりません。割り込みを呼び出して、progress_handler から非 0 を返してみました。これらのアプローチは両方とも、更新コマンドを中止し、すべての変更をロールバックします。そのため、sqlite はこの更新をトランザクションとして扱っているように見えますが、この場合はすべての行が独立しているためあまり意味がありません。しかし、行ごとに新しいトランザクションを開始することはできません。

interrupt と progress_handler が役に立たない場合、他に何ができますか?

LIMIT と WHERE custom_condition(C1) で UPDATE も試しました。これらのアプローチにより、更新を早期に終了できますが、通常の更新よりも大幅に遅くなり、特定の時間 (別の接続タイムアウトが期限切れになる前) にクエリを終了できません。

他のアイデアはありますか?この複数行の更新は非常に一般的な操作であるため、他の人が適切な解決策を持っていることを願っています。

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

You mentioned poor performance with LIMIT. Do you have a lastupdated column with an index on it? At the top of your procedure you would get the COMMENCED_DATETIME and use it for every batch in the run:

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. With respect to slowness:

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

If you're willing to give other processes access to stale data, and your update is designed so as not to hog system resources, why is there a need to have the update complete within a certain amount of time? There seems to be no need to worry about perfomance in absolute terms. The concern should be relative to other processes -- make sure they're not blocked.

于 2013-06-14T10:59:59.887 に答える
0

また、この質問をhttp://thread.gmane.org/gmane.comp.db.sqlite.general/81946に投稿した ところ、次のような興味深い回答がいくつか得られました。

  • 行 ID の範囲をスライスに分割し、一度に 1 つのスライスを更新します

  • AUTOINCREMENT 機能を使用して、前回の更新が終了した場所から新しい更新を開始します (LIMIT 10000 まで)

  • select raise(fail, ...) を呼び出してロールバックせずに更新を中止するトリガーを作成する

于 2014-06-04T02:30:09.537 に答える