sql >> Databáze >  >> RDS >> Sqlserver

Nelze použít UPDATE s klauzulí OUTPUT, když je na stole spouštěč

Varování viditelnosti :Neodpovídejte. Udává nesprávné hodnoty. Přečtěte si, proč je to špatně.

Vzhledem k objemu potřebnému k provedení UPDATE s OUTPUT práce v SQL Server 2008 R2, změnil jsem svůj dotaz z:

UPDATE BatchReports  
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid

komu:

SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid

UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid

V podstatě jsem přestal používat OUTPUT . Není to tak špatné jako samotný Entity Framework používá stejný hack!

Doufám, že 2012 2014 2016 2018 2019 2020 bude mít lepší implementaci.

Aktualizace:používání OUTPUT je škodlivé

Problém, se kterým jsme začali, byl pokus použít OUTPUT klauzule k načtení "po" hodnoty v tabulce:

UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid

To pak naráží na dobře známé omezení („neopraví“ bug) na serveru SQL:

Cílová tabulka 'BatchReports' příkazu DML nemůže mít žádné povolené spouštěče, pokud příkaz obsahuje klauzuli OUTPUT bez klauzule INTO

Pokus o řešení č. 1

Zkusíme tedy něco, kde použijeme mezilehlou TABLE proměnnou pro uložení OUTPUT výsledky:

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion timestamp, 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

Až na to, že to selže, protože nemáte povoleno vložit timestamp do tabulky (dokonce i dočasné proměnné tabulky).

Pokus o řešení č. 2

Tajně víme, že timestamp je ve skutečnosti 64bitové (aka 8 bajtů) celé číslo bez znaménka. Můžeme změnit naši definici dočasné tabulky tak, aby používala binary(8) spíše než timestamp :

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion binary(8), 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

A to funguje,až na to, že hodnoty jsou špatné .

Časové razítko RowVersion we return není hodnota časového razítka, jak existovala po dokončení UPDATE:

  • vrácené časové razítko :0x0000000001B71692
  • skutečné časové razítko :0x0000000001B71693

Je to proto, že hodnoty OUTPUT do naší tabulky nejsou hodnoty tak, jak byly na konci příkazu UPDATE:

  • Spuštění příkazu UPDATE
    • upraví řádek
      • Časové razítko je aktualizováno (např. 2 → 3)
    • OUTPUT načte nové časové razítko (tj. 3)
    • běhy spuštění
      • znovu upraví řádek
        • Časové razítko je aktualizováno (např. 3 → 4)
  • Prohlášení UPDATE dokončeno
  • OUTPUT vrací 3 (nesprávná hodnota)

To znamená:

  • Nezískáme časové razítko tak, jak existuje na konci příkazu UPDATE (4 )
  • Místo toho dostaneme časové razítko tak, jak bylo v neurčitém středu příkazu UPDATE (3 )
  • Nedostali jsme správné časové razítko

Totéž platí pro jakékoli spouštěč, který upravuje jakýkoli hodnota v řádku. OUTPUT nebude VYSTUPOVAT hodnotu ke konci AKTUALIZACE.

To znamená, že nemůžete důvěřovat OUTPUT, že vrátí nějaké správné hodnoty.

Tato bolestivá realita je zdokumentována v BOL:

Sloupce vrácené z OUTPUT odrážejí data tak, jak jsou po dokončení příkazů INSERT, UPDATE nebo DELETE, ale před provedením spouštěčů.

Jak to Entity Framework vyřešil?

.NET Entity Framework používá rowversion pro Optimistic Concurrency. EF závisí na znalosti hodnoty timestamp jak existuje poté, co vydají UPDATE.

Protože nemůžete použít OUTPUT pro všechna důležitá data používá Entity Framework společnosti Microsoft stejné řešení jako já:

Řešení č. 3 – Konečné – Nepoužívejte klauzuli OUTPUT

Chcete-li načíst po hodnoty, problémy Entity Framework:

UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))

SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1

Nepoužívejte OUTPUT .

Ano, trpí sporem, ale to je to nejlepší, co může SQL Server udělat.

A co INSERTs

Dělejte to, co dělá Entity Framework:

SET NOCOUNT ON;

DECLARE @generated_keys table([CustomerID] int)

INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')

SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
   INNER JOIN Customers AS t
   ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0

Opět používají SELECT k přečtení řádku, místo aby klauzuli OUTPUT důvěřoval.



  1. Způsoby, jak vám tento přístup může ušetřit peníze vaší firmy

  2. SQL dotaz:Smazat všechny záznamy z tabulky kromě posledních N?

  3. Převeďte název měsíce na číslo měsíce v PostgreSQL

  4. Oprava chyby R2 2008, která porušuje RCSI