sql >> Base de Datos >  >> RDS >> Sqlserver

¿Es una sola declaración de SQL Server atómica y consistente?

He estado operando bajo la suposición de que una sola declaración en SQL Server es consistente

Esa suposición es incorrecta. Las dos transacciones siguientes tienen una semántica de bloqueo idéntica:

STATEMENT

BEGIN TRAN; STATEMENT; COMMIT

No hay diferencia en absoluto. Las declaraciones individuales y las confirmaciones automáticas no cambian nada.

Por lo tanto, fusionar toda la lógica en una declaración no ayuda (si lo hace, fue por accidente porque el plan cambió).

Arreglemos el problema en cuestión. SERIALIZABLE arreglará la inconsistencia que está viendo porque garantiza que sus transacciones se comporten como si se ejecutaran con un solo subproceso. De manera equivalente, se comportan como si se ejecutaran instantáneamente.

Obtendrá interbloqueos. Si está de acuerdo con un bucle de reintento, ya ha terminado.

Si desea invertir más tiempo, aplique sugerencias de bloqueo para forzar el acceso exclusivo a los datos relevantes:

UPDATE Gifts  -- U-locked anyway
SET GivenAway = 1
WHERE GiftID = (
   SELECT TOP 1 GiftID
   FROM Gifts WITH (UPDLOCK, HOLDLOCK) --this normally just S-locks.
   WHERE g2.GivenAway = 0
    AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5
   ORDER BY g2.GiftValue DESC
)

Ahora verá una simultaneidad reducida. Eso podría estar totalmente bien dependiendo de tu carga.

La naturaleza misma de su problema hace que sea difícil lograr la concurrencia. Si necesita una solución para eso, necesitaríamos aplicar técnicas más invasivas.

Puede simplificar un poco la ACTUALIZACIÓN:

WITH g AS (
   SELECT TOP 1 Gifts.*
   FROM Gifts
   WHERE g2.GivenAway = 0
    AND (SELECT COUNT(*) FROM Gifts g2 WITH (UPDLOCK, HOLDLOCK) WHERE g2.GivenAway = 1) < 5
   ORDER BY g2.GiftValue DESC
)
UPDATE g  -- U-locked anyway
SET GivenAway = 1

Esto elimina una unión innecesaria.