大量の削除を実行しているT-SQLスクリプトを実行しているときに、他の接続の一部のテーブルにデータを挿入できないようなロックの競合が発生するという不愉快な経験がありました。私は、包括的なトランザクションとロックを回避することを期待して、リクエストをより小さなトランザクションに分割することでこれを修正しようとしました:
set implicit_transactions off
declare @idsToDelete table (id bigint)
declare @currentId bigint
declare @deleteCursor cursor
begin transaction
insert into @idsToDelete
select someId from someTable with (nolock) where someCondition = 'some value'
commit
set @deleteCursor = cursor for select id from @idsToDelete
open @deleteCursor
fetch next from @deleteCursor into @currentId
while @@fetch_status = 0 begin
begin transaction
delete from relatedTable
where id = @currentId
delete from someTable
where id = @currentId
commit
fetch next from @deleteCursor into @currentId
end
close @deleteCursor
私の理解はset implicit_transactions off
はnotがトップレベルのトランザクションを持つことを可能にする必要があります。したがって、それぞれの削除が実行されて完了し、これで完了です。行のセットが少しずつ削除されていく様子を見ることができたので、すべて良かったと思いました。ただし、実際には、このテーブルへの挿入はブロックされていました。そしてさらに悪いことに、実行に気づいてキャンセルしたとき、全体がロールバックし、ブロックしたようにブロックし続けました!
これを独立したトランザクションでロックなしで実行する方法はありますか(行ロックはOKですが)。注意しないと、同様の問題が発生する可能性がある大きな更新スクリプトを作成する必要があります。
@ジェイコブ-私たちは私たちの人生のある時点で私たち全員が不愉快な経験をしたと思いますので、打ちのめさないでください。あなたが指導を求め、そこから学んでいるのは立派です。だからあなたに嫌い。
ターゲットにするいくつかの重要なことは、影響を受ける行数かもしれません。
重要な行数を持つテーブルパラメーターに対するパフォーマンスの低下と、そのロックヒントなしに各関連テーブルから数千行の行を削除する可能性の組み合わせにより、クロールして永続的なブロックパターンが表示されると思います。
トランザクションについては、トランザクションを削除ステートメントのみを削除するようにするか、tsqlをストアドプロシージャに利用/ラップすることを検討します。使用方法にもよりますが、パフォーマンスとベストプラクティスのために、通常はカーソルから離れることをお勧めします。
参考になれば、私も例をあげることができます。 :)
そして、それでも確信が持てない場合、2つ目の目を見つけることができれば、それもいくつかの節約になります!
乾杯!
-skibunnysqldiva
いくつかのこと:
_ 実際には、あなたがそれらを望まないので、これはおそらく最良のものです。この設定は、「トップレベルのトランザクション」とは関係ありません。 _set implicit_transactions off
_を削除しますimplicit_transactions
_がON
の場合、INSERTおよびDELETE(および others )はトランザクションを自動的に開始します。
テーブル変数の周りにBEGIN TRAN/COMMITは必要ありません。作業/スクラッチテーブルにデータを入力するためだけにトランザクションを要求することは意味がないだけでなく、テーブル変数はそもそもトランザクションにバインドされていません;-)。
DELETE
ステートメントにWITH (ROWLOCK)
ヒントを追加できます。ヒントがないと、低レベルのロックがテーブルロックにアップグレードされ、5000回の変更で発生するロックエスカレーションに遭遇する可能性があります。
一連の削除を実行するように切り替える方がよい場合があります。この場合、_@idsToDelete
_テーブル変数のすべてを同時に削除するわけではないため、IDのバッチを保持する別のテーブル変数または一時テーブルが必要になります。テーブル変数は通常、1行しかないように見えるため、JOINingには適していませんが、通常はTOP (n)
を使用することで回避できます。ただし、ローカル一時テーブルの方が簡単な場合があります。
たとえば、CURSOR
を介して行ごとに移動するのではなく、完了するまでWHILE
ループを実行します。
_CREATE #TempIDs (ID BIGINT NOT NULL);
declare @idsToDelete table (id bigint);
insert into @idsToDelete
select someId
from someTable with (nolock)
where someCondition = 'some value';
WHILE (1 = 1)
BEGIN
BEGIN TRAN
DELETE TOP (500) tmp
OUTPUT DELETED.ID
INTO #TempIDs (ID)
FROM @idsToDelete;
IF (@@ROWCOUNT = 0)
BEGIN
ROLLBACK TRAN;
BREAK; -- exit the loop cuz we done
END;
delete tab
from relatedTable tab WITH (ROWLOCK)
INNER JOIN #TempIDs tmp
ON tmp.ID = tab.ID;
delete tab
from someTable tab WITH (ROWLOCK)
INNER JOIN #TempIDs tmp
ON tmp.ID = tab.ID;
COMMIT;
TRUNCATE TABLE #TempIDs; -- clear out for the next run
END;
_