如果 RCSI 下不存在则插入

Insert if not exist under RCSI

我有一个数据库 READ_COMMITTED_SNAPSHOT_ISOLATION 设置为开启(无法更改)。

我在许多并行会话中将新行插入 table, 但前提是它们还不存在(经典的左连接检查)。

插入代码如下所示:

INSERT INTO dbo.Destination(OrderID)
SELECT DISTINCT s.OrderID
FROM dbo.Source s
LEFT JOIN dbo.Destination d ON d.OrderID = s.OrderID
WHERE d.OrderID IS NULL;

如果我 运行 在许多并行会话中这样做,我会遇到很多重复键错误, 因为不同的会话尝试一遍又一遍地插入相同的 OrderID。

这是预期的,因为 RCSI 下缺少共享锁。

此处推荐的解决方案(根据我的研究)是使用 READCOMMITTEDLOCK 提示,如下所示:

LEFT JOIN dbo.Destination d WITH (READCOMMITTEDLOCK) ON d.OrderID = s.OrderID

这有点管用,因为它大大减少了重复键错误,但(令我惊讶的是)并没有完全消除它们。

作为实验,我删除了目标 table 上的唯一约束,发现许多重复项在同一毫秒内从不同的会话进入 table。

似乎尽管 table 提示,我仍然在存在性检查中得到误报,并且冗余插入触发。

我尝试了不同的提示(SERIALIZABLE),但它使情况变得更糟,并使我陷入僵局。

我怎样才能使这个插入在 RCSI 下工作?

读取要插入的 table 的正确锁提示是 (UPDLOCK,HOLDLOCK),它将在您读取行时在行上放置 U 锁,并且还会放置 SERIALIZABLE 样式的范围如果该行不存在则锁定。

您的方法的问题是每个客户端都试图插入一批行,并且每批要么完全成功要么失败。如果您使用行级锁定,您将始终遇到会话成功插入一行但随后被阻塞等待读取或插入后续行的情况。这不可避免地会导致 PK 失败或死锁,具体取决于所使用的行锁类型。

解决方案是:

1) 逐行插入,检查并插入下一行时不要持有一行的锁。

2) 只需升级到 tablockx,或 Applciation Lock 以强制您的并发会话通过这段代码进行序列化。

所以你可以有高并发加载,也可以有批量加载,但不能两者兼有。好吧,主要是。

3) 您可以在索引上打开 IGNORE_DUP_KEY,这样插入时将跳过任何重复项而不是错误。