El patrón es (sin manejo de errores):
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;
No necesita realizar una lectura adicional de la tabla #temp aquí. Ya lo estás haciendo al probar la actualización. Para protegerse de las condiciones de carrera, haga lo mismo que protegería cualquier bloque de dos o más declaraciones que desee aislar:lo envolvería en una transacción con un nivel de aislamiento apropiado (probablemente serializable aquí, aunque todo eso solo tiene sentido cuando no estamos hablando de una tabla #temp, ya que por definición está serializada).
No está más adelantado agregando un IF EXISTS
verifique (y necesitaría agregar sugerencias de bloqueo para que sea seguro / serializable de todos modos), pero podría estar más atrasado, dependiendo de cuántas veces actualice las filas existentes versus inserte nuevas. Eso podría sumar una gran cantidad de E/S adicionales.
La gente probablemente te dirá que uses MERGE
(que en realidad son múltiples operaciones detrás de escena, y también deben protegerse con serializable), le insto a que no lo haga. Expongo por qué aquí:
- Tenga cuidado con la instrucción MERGE de SQL Server
Para un patrón de varias filas (como un TVP), manejaría esto de la misma manera, pero no hay una forma práctica de evitar la segunda lectura como se puede hacer con el caso de una sola fila. Y no, MERGE
tampoco lo evita.
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;
Bueno, supongo que hay una manera de hacerlo, pero no lo he probado a fondo:
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;
En cualquier caso, realice la actualización primero; de lo contrario, actualizará todas las filas que acaba de insertar, lo que sería un desperdicio.