Joe*_*Joe 5 sql-server concurrency unique-constraint
在表中Orders
,我存储了我们从所有商店收到的订单。由于一个订单可以有多个行,列中有OrderID
和OrderLineID
在那里OrderID
可以复制,但OrderLineID
必须是一个顺序中是唯一的。
由于订单可以修改,存储过程首先检查收到的订单OrderLineID
是否已经存在于表中,然后决定插入还是更新。为此,我们:
然后是主表:
IF NOT EXISTS (Select 1 from Orders where OrderLineID=@OrderLineID ......)
INSERT INTO Orders () VALUES ()
ELSE UPDATE Orders SET ... WHERE OrderLineID=@OrderLineID
Run Code Online (Sandbox Code Playgroud)
或者该MERGE
功能是否提供更好的性能/控制?
但问题如下:
由于线路问题/服务器繁忙等,Order
消息(或修改)可能会被多次发送,我们不知道按哪个顺序发送。因此,为了避免Order
修改后到达,从而覆盖修改,我们添加了一个时间列:
IF NOT EXISTS (Select 1 from Orders where OrderLineID=@OrderLineID)
INSERT INTO Orders () VALUES ()
ELSE UPDATE Orders SET ... WHERE OrderLineID=@OrderLineID AND LastModified<@CreatedTime
Run Code Online (Sandbox Code Playgroud)
这样,如果后一条消息比前一条消息旧,则对表没有影响。
但是,消息及其修改可能会在很短的时间内发送两次(或更多),以至于后一条消息在保存前一条消息之前到达。因此,对于存储过程的两次执行,它IF NOT EXISTS (Select 1 from Orders where OrderLineID=@OrderLineID)
都是TRUE,并且两次它都会生成一个INSERT
并且我们找到重复的行。
也许这可以通过简单地设置OrderLineID
为唯一键来避免?
我还阅读了有关使用的内容:
set transaction isolation level serializable
Run Code Online (Sandbox Code Playgroud)
但我不确定这是如何处理的。我不仅要避免重复行,还要确保后面的消息执行UPDATE
查询而不是抛出唯一键冲突。
小智 12
MERGE
有一些您应该注意的问题,请参阅Aaron Bertrand对 SQL Server 的 MERGE 语句使用小心。
更具体地说,对于您目前的情况,MERGE
除了自动包含事务之外,没有针对并发问题提供额外的保护。您还需要在语句中添加一个SERIALIZABLE
提示(或其同义词,HOLDLOCK
)MERGE
,请参阅Dan Guzman 的“UPSERT” Race Condition With MERGE。请注意,UPDLOCK
提示不是必需的MERGE
。
有关问题和选项的更一般概述,请参阅 Michael J. Swart 的以下内容:
一个流行且健壮的解决方案是WITH (UPDLOCK, SERIALIZABLE)
在NOT EXISTS
检查中添加表访问提示,并在整个UPSERT
操作中包含一个事务。
使用您的示例:
BEGIN TRANSACTION;
IF NOT EXISTS
(
SELECT *
FROM Orders WITH (UPDLOCK, SERIALIZABLE)
WHERE OrderLineID=@OrderLineID
)
INSERT INTO Orders () VALUES ()
ELSE
UPDATE Orders
SET ...
WHERE OrderLineID=@OrderLineID
AND LastModified<@CreatedTime;
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
或者同样:
BEGIN TRANSACTION;
INSERT INTO Orders ()
SELECT ...
WHERE NOT EXISTS
(
SELECT *
FROM Orders WITH (UPDLOCK, SERIALIZABLE)
WHERE OrderLineID=@OrderLineID
);
IF @@ROWCOUNT = 0
BEGIN
UPDATE Orders
SET ...
WHERE OrderLineID=@OrderLineID
AND LastModified<@CreatedTime;
END;
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
可以切换INSERT
和UPDATE
操作的顺序,以便首先尝试您期望最常成功的操作。提示必须始终应用于第一个操作。
例如,如果预计更新数量最多:
BEGIN TRANSACTION;
UPDATE Orders WITH (UPDLOCK, SERIALIZABLE)
SET ...
WHERE OrderLineID=@OrderLineID
AND LastModified<@CreatedTime;
IF @@ROWCOUNT = 0
BEGIN
INSERT INTO Orders ()
SELECT ...
END;
COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)
问题中提到的唯一性约束一般来说是保持一致性的好主意,但对于此处提到的方法并不是严格需要的。如果您要使用 Michael Swart 的Just Do It模式,则需要唯一性保证才能正确操作。
提醒一下,如果您MERGE
出于任何原因喜欢,您必须SERIALIZABLE
对目标表使用提示。
虽然通常应该尽可能避免锁定提示,但这是有效用例的一个示例。
归档时间: |
|
查看次数: |
1191 次 |
最近记录: |