次のテーブルがあります (ここでは Entity Framework でモデル化されていますが、私の質問は EF とは関係ありません):
ご覧のとおり、これはバージョン管理されたProduct
テーブルです。Id
列は主キーですが、組み合わせも(EntityId, VersionId)
主キーになる可能性があります。EntityId
エンティティの異なるバージョン間で一定であるエンティティの ID を示します。で行を書き込むと、エンティティが削除されIsDeleted = 1
ます。
データ操作を担当するストアド プロシージャは、最初にデータ操作が正常かどうかを確認します。たとえば、UPDATE SP は、エンティティが既に削除されているかどうかを確認します。これらのチェックが成功すると、SP はバージョン テーブルに新しい行を生成し、続いてテーブルに新しい行を生成しProduct
ます。
(pseudo-code):
sp_Product_Update:
(1) IF EXISTS (SELECT Id FROM Product WHERE IsDeleted = 1 AND EntityId = @ProductId)
RAISERROR "Entity has already been deleted"
RETURN
(2) INSERT INTO Versions ...
(3) INSERT INTO Product ... (IsDeleted = 0)
sp_Product_Delete:
(1) IF EXISTS (SELECT Id FROM Product WHERE IsDeleted = 1 AND EntityId = @ProductId)
RAISERROR "Entity has already been deleted"
RETURN
(2) INSERT INTO Versions ...
(3) INSERT INTO Product ... (IsDeleted = 1)
これはすべてうまくいきます。
現在、並行性の問題についてこれを分析しています。同じエンティティに対して 2 つの SP が同時に呼び出される、次の同時実行シナリオを想像してください。
Transaction 1 Transaction 2
sp_Product_Update sp_Product_Delete
(1) Check succeeds, entity has not yet been deleted.
(1) Same check.
(2) INSERT INTO Versions...
(3) INSERT INTO Product.. (IsDeleted = 1)
(2) INSERT INTO Versions...
(3) INSERT INTO Product ... (IsDeleted = 0)
ご覧のとおり、この競合状態はデータの不整合、つまりエントリの後のIsDeleted = 0
行につながります。IsDeleted = 1
したがって、この競合状態を回避するために必要な分離のレベルを決定する必要があります。
- (1) で読み込まれたデータはダーティではないため、これはDirty Readではないようです。
- どちらのトランザクションにも 2 回の読み取りはありません。
- Phantom Readについても同じことが言えます。どちらのトランザクションにも 2 つのクエリはありません。
だから私は2つの質問が残っています:
- 私の分析は正しいですか?
- この種の問題を回避するには、どの分離レベルが必要ですか?