Vzor je (bez zpracování chyb):
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;
Zde nemusíte provádět další čtení tabulky #temp. Už to děláte tak, že zkoušíte aktualizaci. Abyste se chránili před rasovými podmínkami, uděláte totéž, co byste chránili jakýkoli blok dvou nebo více příkazů, které chcete izolovat:zabalíte jej do transakce s vhodnou úrovní izolace (zde pravděpodobně serializovatelnou, i když všechny pouze dává smysl, když nehovoříme o #temp tabulce, protože ta je z definice serializovaná).
Přidáním IF EXISTS
už nebudete dále napřed check (a museli byste přidat uzamykací rady, aby to bylo bezpečné / serializovatelné tak jako tak), ale můžete být dále pozadu, v závislosti na tom, kolikrát aktualizujete existující řádky vs. vkládání nových. To by mohlo přidat spoustu dalších I/O.
Lidé vám pravděpodobně řeknou, abyste použili MERGE
(což je ve skutečnosti několik operací v zákulisí a také je třeba je chránit pomocí serializovatelných), vyzývám vás, abyste to nedělali. Zde uvádím proč:
- Používejte opatrně s příkazem MERGE serveru SQL Server
U víceřadého vzoru (jako TVP) bych to řešil úplně stejně, ale neexistuje praktický způsob, jak se vyhnout druhému čtení, jako u jednořadého pouzdra. A ne, MERGE
nevyhýbá se ani tomu.
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;
No, myslím, že existuje způsob, jak to udělat, ale důkladně jsem to netestoval:
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;
V obou případech nejprve provedete aktualizaci, jinak aktualizujete všechny řádky, které jste právě vložili, což by bylo zbytečné.