2つのテーブルがあります
親との外部キー関係を持つ子。
データベースレベルの読み取りコミットスナップショット分離を有効にしました。
親と子の行のみを挿入および削除します(更新なし)
子(次に親)から古いデータを削除する1つのプロセス(トランザクション)があります。
親(そして子)に新しいデータを挿入する他の複数のプロセス(トランザクション)があります。
削除プロセスは定期的に(ただし、常にではない)ロールバックされます。ただし、挿入プロセスは、削除したい親行を参照する新しい子行を挿入しません-それは新しいParent行と、新しいParentを参照する1つ以上の新しいChild行を作成するだけです
親行を削除するときのエラーは次のとおりです。
更新の競合により、スナップショット分離トランザクションが中止されました。スナップショット分離を使用して、データベース 'Test'のテーブル 'dbo.Child'に直接または間接的にアクセスして、別のトランザクションによって変更または削除された行を更新、削除、または挿入することはできません。トランザクションを再試行するか、更新/削除ステートメントの分離レベルを変更してください。
私は人々が外部キー列にインデックスを付けることを提案していることを知っています-これを機能させる唯一の確実な方法でない限り、これを理想的に(スペース/パフォーマンス上の理由から)する必要はありません。
これに注意してください: https://stackoverflow.com/questions/10718668/snapshot-isolation-transaction-aborted-due-to-update-conflict
そしてかなり良い記事: https://sqlperformance.com/2014/06/sql-performance/the-snapshot-isolation-level
しかし、これらのどちらも私が理解したいと思っている理解を私に与えません:)
親テーブルから削除する場合、SQL Serverはその行を参照するFK子行の存在を確認する必要があります。適切な子インデックスがない場合、このチェックは子テーブルのフルスキャンを実行します。
削除コマンドのスナップショットトランザクションの開始以降に変更された行がスキャンで検出された場合、(定義により)更新の競合で失敗します。フルスキャンは明らかにテーブルのすべての行に影響します。
適切なインデックスを使用すると、SQL Serverは、削除される親と一致する可能性のある子テーブルの行のみを見つけてテストできます。これらの特定の行が変更されていない場合、更新の競合は発生しません。
行バージョン管理分離レベルでの外部キーチェックは、更新の競合を検出するだけでなく、(正確さのために)共有ロックを取得することに注意してください。たとえば、上記の子テーブルアクセスの内部ヒントは次のとおりです。
PhyOp_Range TBL:[dbo]。[Child] ヒント(READ-COMMITTEDLOCK FORCEDINDEX DETECT-SNAPSHOT-CONFLICT)
悲しいことに、これは現在実行計画では公開されていません。
私の関連記事:
Microsoftの男性が スレッド で同様の質問をしているときにこの返信に出くわしましたが、かなり洞察力があると思いました。
CustomerContactPersonのサポートインデックスがない場合、ステートメント
DELETE FROM ContactPerson WHERE ID = @ID;削除されたContactPerson行を参照するCustomerContactPerson行がないことを確認するために、CustomerContactPersonのすべての行の「現在の」読み取りが必要になります。インデックスを使用すると、DELETEは、他のトランザクションの影響を受ける行を読み取らずに、CustomerContactPersonに関連する行がないことを確認できます。
さらに、スナップショットトランザクションでは、方向転換して更新するデータを読み取るためのパターンは、読み取り時にUPDLOCKを取ることです。これにより、「一貫性のある」(スナップショット)データではなく「現在の」データに基づいて更新が行われ、DMLを発行するときにデータがロックされず、意図せずに上書きされなくなります。別のセッションの変更。
開発チームからアップデートを受け取りました。問題についての私の理解は正しいようです。
ここにその説明があります。 SNAPSHOT分離により、データベースの単一の一貫したバージョンが確実に表示されます。トランザクションの開始時にCustomerContactPerson行を読み取ると、それ以降のバージョンの行を読み取ることができなくなります。 ContactPersonのDELETEでは、トランザクションのスナップショットよりも後のバージョンのCustomerContactPerson行を読み取る必要があるため、更新の競合が発生します。 CustomerContactPerson行を実際に更新しなくても、FKが同じであることを検証するためにそれを読み取ることは問題ではありません。
さらに、テーブルスキャンが他のトランザクションの影響を受けるレコードに遭遇した場合は、更新する行を読み取りながらロックすることにより、競合を回避できます。
一方、スナップショット分離は、変更されるデータが実際には事前にロックされていないため、本当に楽観的ですが、変更のために選択されるとデータはロックされます。データ行が更新条件を満たす場合、スナップショットトランザクションは、スナップショットトランザクションの開始後に、データが別のトランザクションによって変更されていないことを確認します。データが別のトランザクションによって変更されていない場合、スナップショットトランザクションはデータをロックし、データを更新し、ロックを解放してから続行します。データが別のトランザクションによって変更された場合、更新の競合が発生し、スナップショットトランザクションがロールバックします。
エラーメッセージは一般的な修正を提供し、SqlWorldWideはコメントで問題に対する1つの回答を提案しました(「代わりにシリアライズ可能な分離を使用してください」)。問題はトランザクション分離レベルです。修正は、そのトランザクションの分離レベルを変更することです。 Microsoftは という名前の記事でこのすべてを説明していますレッスン1:使用可能なトランザクション分離レベルを理解する
この記事の中で、MicrosoftはUpdate Conflictsという名前のセクションで以下のように述べています。
スナップショット分離レベルに固有であるため、まだ言及されていない追加の同時実行性の問題があります。特定の行(または行のバージョン)がスナップショット分離で読み取られる場合、SQL Serverは、トランザクションの後半でクエリを発行した場合に同じ行が取得されることを保証します。後のクエリがUPDATEまたはDELETEステートメントであり、最初に読み取られてから行が変更された場合はどうなりますか? SQL Serverは、スナップショットトランザクションがアクティブなときに行が変更されないという約束を破るので、更新のベースとして現在のバージョンの行を使用できません。また、スナップショットトランザクションで使用された行バージョンをベースとして使用することはできません。行を更新または削除した他のトランザクションでは更新が失われるためです(SQL Serverでは許可されていないか、サポートされていません)。代わりに、スナップショットトランザクションがロールバックされ、次のエラーメッセージが表示されます:
メッセージ3960、レベル16、状態4、行1スナップショット分離トランザクションは、更新の競合のために中止されました。スナップショット分離を使用して、データベース「TestDatabase」内のテーブル「Test.TestTran」に直接または間接的にアクセスして、別のトランザクションによって変更または削除された行を更新、削除、または挿入することはできません。トランザクションを再試行するか、更新/削除ステートメントの分離レベルを変更してください。
これは、受け取ったエラーに似ています。
私は推測していますが、これは起こっていることだと思います。親行を削除するとき、エンジンは何でも強制する必要がありますON DELETE
ルールは外部キーで定義されています-yoに関係なく、すべての子行が削除されたことを知っていると、エンジンはそれを知る方法がありません。あなたが言うように、(パフォーマンス上の理由で)子テーブルの外部キー列にインデックスがないので、エンジンはクラスター化インデックススキャンに頼ります(私は子テーブルにPKがあると仮定しています) )そして、最初の古い行に遭遇するとすぐに、トランザクションを中止します。これは、対象のスナップショットの外部に挿入された外部キーの値を認識できないためです。
子テーブルの外部キー列にインデックスがあった場合、サーバーは影響を受ける可能性のある行のみに選択的にアクセスできます(つまり、行を削除していないため)、スナップショットの競合を回避できます- andクラスタ化インデックススキャン。
スナップショット分離を使用している必要がありますnotスナップショット読み取りがコミットされました。スナップショット更新の競合は、スナップショット分離を使用している場合にのみ発生し、スナップショット読み取りコミットを使用している場合はnotが発生します。
コミットされたスナップショット読み取りを使用できる場合、それはこの問題の非常に簡単な修正になります。
スナップショット読み取りコミット分離では、ロックを使用して(各ステートメントの前に行バージョン情報を取得して)、スナップショット更新の競合を不可能にします。
スナップショット更新の競合は、スナップショット分離(スナップショット読み取りコミットではない)で発生します。これは、トランザクションが変更をコミットしようとしたときに、トランザクションの開始以降にバージョンが変更されたデータに変更をコミットしようとしているためです。あなたが概説したシナリオを考えると、この問題が発生している理由を正確に理解することは困難であり、おそらくそれはテーブルスキャンと、FKに適切なインデックスがある場合のインデックスシークとの関係に関連している可能性があります。
主なポイントは、SNAPSHOT READ COMMITTED分離ではなく、SNAPSHOTを使用する必要があることです。これは、SNAPSHOT READ COMMITTEDを使用して修正できます。
SNAPSHOTを取得できる唯一の方法は、トランザクションの開始時に分離レベルをスナップショットに設定することです。 SNAPSHOT READ COMMITTEDを使用するには、データベースでSNAPSHOT READ COMMITTEDを有効にし、次にnotを実行して、クエリまたはsprocの分離レベルを任意に設定する必要があります。