模式是(没有错误处理):
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE #TProductSales SET StockQty = @StockQty, ETA1 = @ETA1
WHERE ProductID = @ProductID;
IF @@ROWCOUNT = 0
BEGIN
INSERT #TProductSales(ProductID, StockQTY, ETA1)
VALUES(@ProductID, @StockQTY, @ETA1);
END
COMMIT TRANSACTION;
您无需在此处额外读取#temp 表。您已经通过尝试更新来做到这一点。为了防止竞争条件,您可以执行与保护要隔离的两个或多个语句的任何块相同的操作:您将其包装在具有适当隔离级别的事务中(可能在此处可序列化,尽管仅当我们不是在谈论 #temp 表时才有意义,因为根据定义,它是序列化的)。
通过添加IF EXISTS 检查(和you would need to add locking hints to make that safe / serializable anyway),您不会进一步领先,但您可能会落后,具体取决于您更新现有行与插入新行的次数。这可能会增加很多额外的 I/O。
人们可能会告诉你使用MERGE(这实际上是在幕后的多个操作,以及also needs to be protected with serializable),我劝你不要。我和其他人在这里阐述了原因:
对于多行模式(如 TVP),我会以完全相同的方式处理此问题,但没有一种实用的方法可以像使用单行情况一样避免第二次读取。不,MERGE 也没有避免它。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
UPDATE t SET t.col = tvp.col
FROM dbo.TargetTable AS t
INNER JOIN @TVP AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @TVP AS tvp
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.TargetTable
WHERE ProductID = tvp.ProductID
);
COMMIT TRANSACTION;
好吧,我想有办法做到这一点,但我还没有彻底测试过:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
DECLARE @exist TABLE(ProductID int PRIMARY KEY);
UPDATE t SET t.col = tvp.col
OUTPUT deleted.ProductID INTO @exist
FROM dbo.TargetTable AS t
INNER JOIN @tvp AS tvp
ON t.ProductID = tvp.ProductID;
INSERT dbo.TargetTable(ProductID, othercols)
SELECT ProductID, othercols
FROM @tvp AS t
WHERE NOT EXISTS
(
SELECT 1 FROM @exist
WHERE ProductID = t.ProductID
);
COMMIT TRANSACTION;
无论哪种情况,你都要先执行更新,否则你会更新你刚刚插入的所有行,这会很浪费。