4

ローカル データベースのレコードを使用して、リモート サーバー上の多数のアイテムを更新する手順に取り組んでいます。これが疑似コードです。

CREATE PROCEDURE UpdateRemoteServer
    pre-processing
    get cursor with ID's of records to be updated
    while on cursor
        process the item

どれだけ最適化しても、ルーチンには時間がかかるため、すべてを 1 つのトランザクションとして処理することは望ましくありません。アイテムは処理後にフラグが立てられるため、処理が中断された場合でも中断したところから再開できるはずです。

ループの内容 (「アイテムを処理する」) を begin/commit tran でラップしてもうまくいきません... ステートメント全体が

EXEC UpdateRemoteServer

単一のトランザクションとして扱われます。各アイテムを完全な個別のトランザクションとして処理するにはどうすればよいですか?

これらを「非トランザクション更新」として実行したいのですが、そのオプションは (私の知る限り) 2008 年にしか利用できないことに注意してください。

4

5 に答える 5

3

EXEC プロシージャはトランザクションを作成しません。非常に簡単なテストでこれが示されます。

create procedure usp_foo
as
begin
  select @@trancount;
end
go

exec usp_foo;

usp_foo 内の @@trancount は 0 であるため、EXEC ステートメントは暗黙的なトランザクションを開始しません。UpdateRemoteServer に入ったときにトランザクションが開始された場合、誰かがそのトランザクションを開始したことを意味します。誰とは言えません。

そうは言っても、リモート サーバーと DTC を使用してアイテムを更新すると、パフォーマンスが大幅に低下します。他のサーバーも少なくとも SQL Server 2005 ですか? おそらく、更新要求をキューに入れ、ローカル サーバーとリモート サーバーの間でメッセージングを使用し、メッセージからの情報に基づいてリモート サーバーに更新を実行させることができます。両方のサーバーがローカル トランザクションのみを処理する必要があるため、パフォーマンスが大幅に向上し、キューに入れられたメッセージングの疎結合により可用性が大幅に向上します。

更新しました

カーソルは実際にはトランザクションを開始しません。典型的なカーソル ベースのバッチ処理は通常、カーソルに基づいており、更新を特定のサイズのトランザクションにバッチ処理します。これは、夜間のジョブではかなり一般的です。これにより、パフォーマンスが向上し (トランザクション サイズが大きくなるため、ログ フラッシュのスループットが向上します)、ジョブを中断したり、すべてを失うことなく再開したりできるためです。バッチ処理ループの単純化されたバージョンは、通常、次のようになります。

create procedure usp_UpdateRemoteServer
as
begin
  declare @id int, @batch int;
  set nocount on;
  set @batch = 0;

  declare crsFoo cursor 
    forward_only static read_only 
    for 
    select object_id 
    from sys.objects;

  open crsFoo;

  begin transaction
  fetch next from crsFoo into @id ;
  while @@fetch_status = 0
  begin

    -- process here

    declare @transactionId int;
    SELECT @transactionId = transaction_id 
      FROM sys.dm_tran_current_transaction;
    print @transactionId;

    set @batch = @batch + 1
    if @batch > 10
    begin
      commit;
      print @@trancount;
      set @batch = 0;
      begin transaction;
    end
    fetch next from crsFoo into @id ;
  end
  commit;
  close crsFoo;

  deallocate crsFoo;
end
go

exec usp_UpdateRemoteServer;

エラー処理部分 (begin try/begin catch) と派手な @@fetch_status チェック (実際には静的カーソルは必要ありません) を省略しました。このデモ コードは、実行中にいくつかの異なるトランザクションが開始されることを示しています (異なるトランザクション ID)。多くの場合、バッチは処理される各アイテムにトランザクション セーブポイントも展開するため、リンクにあるパターンと同様のパターンを使用して、例外を引き起こすアイテムを安全にスキップできますが、セーブポイントと DTC が混在しないため、これは分散トランザクションには適用されません。 .

于 2009-10-08T17:53:07.403 に答える
1

編集:以下の Remus が指摘したように、カーソルはデフォルトではトランザクションを開きません。したがって、これはOPによって提起された質問に対する答えではありません。カーソルよりも優れたオプションがあると思いますが、それは質問の答えにはなりません。

ストゥ

元の答え:

あなたが説明する特定の症状は、カーソルがデフォルトでトランザクションを開くという事実によるものです。したがって、どのように操作しても、カーソルを使用している限り、実行時間の長いトランザクションが発生します(ロックを回避しない限り)これは別の悪い考えです)。

他の人が指摘しているように、カーソルは吸う。99.9999% の時間は必要ありません。

SQL Server を使用してデータベース レベルでこれを行う場合、実際には 2 つのオプションがあります。

  1. SSIS を使用して操作を実行します。非常に高速ですが、特定の種類の SQL Server では利用できない場合があります。

  2. リモートサーバーを扱っており、接続が心配なため、ループメカニズムを使用する必要がある場合があるため、代わりに WHILE を使用して一度にバッチをコミットしてください。WHILE にはカーソルと同じ問題が数多くありますが (SQL ではループは依然としてうまくいきません)、外側のトランザクションを作成することは避けます。

ストゥ

于 2009-10-08T18:02:05.607 に答える
0

ただのアイデア..

  • プロシージャが呼び出されたときに少数のアイテムのみを処理します (たとえば、処理する上位 10 アイテムのみを取得します)。
  • それらを処理する

うまくいけば、これでトランザクションは終了します。

次に、実行する作業がある限りプロシージャを呼び出すラッパーを作成します (単純な count(..) を使用してアイテムがあるかどうかを確認するか、プロシージャが true を返して、実行する作業がまだあることを示します。

これが機能するかどうかはわかりませんが、アイデアが役立つかもしれません。

于 2009-10-08T18:08:48.293 に答える
0

これをSQLサーバー内からのみ実行していますか、それともアプリから実行していますか? その場合は、処理するリストを取得し、アプリでループして、必要に応じてサブセットのみを処理します。

次に、トランザクションはアプリによって処理され、更新されているアイテム/アイテムが含まれているページのみをロックする必要があります。

于 2009-10-08T17:33:52.950 に答える
0

トランザクション作業を行っているときは、ループ内で一度に 1 つのアイテムを処理しないでください。それらのグループを処理するレコードをループすることはできますが、一度に 1 つのレコードを実行することはありません。代わりにセット ベースの挿入を行うと、パフォーマンスが数時間から数分、さらには数秒に変化します。カーソルを使用して更新または削除を挿入していて、各ステートメントで少なくとも 1000 行を (一度に 1 つではなく) 処理していない場合は、間違ったことをしています。カーソルは、そのようなことには非常に貧弱な方法です。

于 2009-10-08T17:52:29.433 に答える