同様の問題が発生しました。FK制約を削除しなくても問題を解決できるはずです。
具体的には、このシナリオでは、READCOMMITTEDトランザクションで親テーブルが頻繁に更新されました。また、親テーブルへのFKを使用して子テーブルに行を挿入する必要がある同時(長時間実行)スナップショットトランザクションが頻繁に発生しました。したがって、SEREALIZABLEトランザクションの代わりにREAD COMMITTEDを使用したことを除いて、基本的には同じシナリオです。
この問題を解決するには、FK列のプライマリテーブルに新しいUNIQUENONCLUSTERED制約を作成します。さらに、一意の制約を作成した後でFKを再作成する必要があります。これにより、FKが(クラスター化されたキーではなく)制約を参照するようになります。
注:欠点は、親テーブルが更新されたときにSQLサーバーによって維持される必要があるテーブルに一見冗長な制約があることです。そうは言っても、別の/代替のクラスター化されたキーを検討する良い機会かもしれません...そして運が良ければ、このテーブルの別のインデックスの必要性を置き換えることさえできます...
残念ながら、一意の制約を作成することで問題が解決する理由について、Web上で適切な説明を見つけることができません。これが機能する理由を説明する最も簡単な方法は、FKが一意の制約のみを参照するようになり、親テーブル(つまり、FKで参照されていない列)を変更しても、FKとしてスナップショットトランザクションで更新の競合が発生しないためです。変更されていない一意の制約エントリを参照するようになりました。これを、親テーブルの任意の列への変更がこのテーブルの行バージョンに影響を与えるクラスター化キーと比較してください。FKは更新されたバージョン番号を確認するため、スナップショットトランザクションを中止する必要があります。
さらに、非スナップショットトランザクションで親行が削除されると、クラスター化された制約と一意の制約の両方が影響を受け、予想どおり、スナップショットトランザクションがロールバックされます(したがって、FKの整合性が維持されます)。
このブログエントリから採用した上記のサンプルコードを使用して、この問題を再現することができました。
---------------------- SETUP Test database
-- Creating Customers table without unique constraint
USE master;
go
IF EXISTS (SELECT * FROM sys.databases WHERE name = 'SnapshotTest')
BEGIN;
DROP DATABASE SnapshotTest;
END;
go
CREATE DATABASE SnapshotTest;
go
ALTER DATABASE SnapshotTest
SET ALLOW_SNAPSHOT_ISOLATION ON;
go
USE SnapshotTest;
go
CREATE TABLE Customers
(CustID int NOT NULL PRIMARY KEY,CustName varchar(40) NOT NULL);
CREATE TABLE Orders
(OrderID char(7) NOT NULL PRIMARY KEY,
OrderType char(1) CHECK (OrderType IN ('A', 'B')),
CustID int NOT NULL REFERENCES Customers (CustID)
);
INSERT INTO Customers (CustID, CustName) VALUES (1, 'First test customer');
INSERT INTO Customers (CustID, CustName) VALUES (2, 'Second test customer');
GO
---------------------- TEST 1: Run this test before test 2
USE SnapshotTest;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN TRANSACTION;
-- Check to see that the customer has no orders
SELECT * FROM Orders WHERE CustID = 1;
-- Update the customer
UPDATE Customers SET CustName='Updated customer' WHERE CustID = 1;
-- Twiddle thumbs for 10 seconds before commiting
WAITFOR DELAY '0:00:10';
COMMIT TRANSACTION;
go
-- Check results
SELECT * FROM Customers (NOLOCK);
SELECT * FROM Orders (NOLOCK);
GO
---------------------- TEST 2: Run this test in a new session shortly after test 1
USE SnapshotTest;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
BEGIN TRANSACTION;
SELECT * FROM Customers WHERE CustID = 1;
INSERT INTO Orders (OrderID, OrderType, CustID) VALUES ('Order01', 'A', 1);
-- Twiddle thumbs for 10 seconds before commiting
WAITFOR DELAY '0:00:10';
COMMIT TRANSACTION;
go
-- Check results
SELECT * FROM Customers (NOLOCK);
SELECT * FROM Orders (NOLOCK);
go
上記のシナリオを修正するには、テストデータベースを再セットアップします。次に、テスト1および2を実行する前に、次のスクリプトを実行します。
ALTER TABLE Customers
ADD CONSTRAINT UX_CustID_ForSnapshotFkUpdates UNIQUE NONCLUSTERED (CustID)
-- re-create the existing FK so it now references the constraint instead of clustered index (the existing FK probably has a different name in your DB)
ALTER TABLE [dbo].[Orders] DROP CONSTRAINT [FK__Orders__CustID__1367E606]
ALTER TABLE [dbo].[Orders] WITH CHECK ADD FOREIGN KEY([CustID])
REFERENCES [dbo].[Customers] ([CustID])
GO