ストアド プロシージャ内のループ内のトランザクション
-
20-09-2019 - |
質問
私は、ローカル データベースのレコードを使用して、リモート サーバー上の多数のアイテムを更新する手順に取り組んでいます。これが疑似コードです。
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 でのみ利用可能であることに注意してください。
解決
編集: 以下の Remus によって指摘されているように、カーソルはデフォルトではトランザクションを開きません。したがって、これはOPによって提起された質問に対する答えではありません。私はカーソルよりも優れたオプションがあるとまだ思っていますが、それでは質問の答えにはなりません。
スチュ
元の答え:
あなたが説明する特定の症状は、カーソルがデフォルトでトランザクションを開くという事実によるものです。したがって、どのように操作しても、カーソルを使用している限り、トランザクションが長時間実行されることになります(ロックを回避しない限り)。これも悪い考えです)。
他の人が指摘しているように、カーソルは最悪です。99.9999% の確率でそれらは必要ありません。
SQL Server を使用してデータベース レベルでこれを実行する場合、実際には 2 つのオプションがあります。
SSIS を使用して操作を実行します。非常に高速ですが、SQL Server の特定の種類では利用できない場合があります。
リモート サーバーを扱っており、接続性が心配なため、ループ メカニズムを使用する必要がある場合があるため、代わりに WHILE を使用し、一度にバッチをコミットします。WHILE にはカーソルと同じ問題が多くあります (ループは SQL に悪影響を及ぼします) が、外部トランザクションの作成を回避できます。
スチュ
他のヒント
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 が混在しないため、これは分散トランザクションには適用されません。
ヨーヨーは、SQLサーバ内から、またはアプリからこれを実行していますか?もしそうなら、リストを取得処理する、などのサブセットのための唯一のプロセスへのアプリで、ループが必要です。
次に、トランザクションアプリによって処理されなければならない、とだけ項目がでている/ページを更新中のアイテムをロックする必要があります。
あなたがトランザクション作業を行っているループ内で一度に一つの項目を処理しないでください。あなたは、レコードをループそれらのグループを処理しますが、これまで、一度に1つのレコードを行うことはできません。代わりに、セットベースの挿入を行うと、あなたのパフォーマンスが数分あるいは秒に時間から変更されます。あなたは更新を挿入または削除するには、カーソルを使用している場合、それはあなたが間違ったことをやっている(atimeのではない1)各文で、少なくとも1000年ROWAを処理していません。カーソルはそのようなことのために非常に悪い習慣です。
ただのアイデアです..
- プロシージャが呼び出されたときに、いくつかの項目のみを処理します (例:処理対象の上位 10 項目のみを取得します)
- それらを処理します
これで取引が終了することを願っています。
次に、実行すべき作業がまだある限りプロシージャを呼び出すラッパーを作成します (単純な count(..) を使用して項目があるかどうかを確認するか、プロシージャが実行すべき作業がまだあることを示す true を返すようにします)。
これが機能するかどうかはわかりませんが、おそらくこのアイデアは役立つでしょう。