我们遇到了类似的问题 - 您会很高兴知道您应该能够在不移除 FK 约束的情况下解决问题。
具体来说,在我们的场景中,我们在 READ COMMITTED 事务中频繁更新父表。我们还经常发生并发(长时间运行)快照事务,需要将行插入到具有 FK 到父表的子表中 - 所以基本上它与您的情况相同,除了我们使用 READ COMMITTED 而不是 SEREALIZABLE 事务。
要解决此问题,请在 FK 列上的主表上创建一个新的 UNIQUE NONCLUSTERED 约束。此外,您还必须在创建唯一约束后重新创建 FK,因为这将确保 FK 现在引用约束(而不是集群键)。
注意:缺点是当对父表进行更新时,您现在对需要由 SQL Server 维护的表有一个看似多余的约束。也就是说,这可能是您考虑使用不同/备用聚集键的好机会……如果幸运的话,它甚至可以取代此表上对另一个索引的需求……
不幸的是,我在网上找不到一个很好的解释,说明为什么创建唯一约束可以解决问题。我可以解释为什么这有效的最简单方法是因为 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