sql >> Database >  >> RDS >> Sqlserver

Kan UPDATE met OUTPUT-component niet gebruiken wanneer een trigger op de tafel staat

Zichtbaarheidswaarschuwing :Geef de ander geen antwoord. Het geeft onjuiste waarden. Lees verder voor waarom het verkeerd is.

Gezien de kludge die nodig is om UPDATE . te maken met OUTPUT werk in SQL Server 2008 R2, ik heb mijn vraag gewijzigd van:

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

naar:

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

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

Eigenlijk ben ik gestopt met het gebruik van OUTPUT . Dit is niet zo erg als Entity Framework zelf gebruikt dezelfde hack!

Hopelijk 2012 2014 2016 2018 2019 2020 zal een betere implementatie hebben.

Update:het gebruik van OUTPUT is schadelijk

Het probleem waarmee we begonnen was het gebruik van de OUTPUT clausule om de "na" . op te halen waarden in een tabel:

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

Dat raakt dan de bekende beperking ("will't-fix" bug) in SQL Server:

De doeltabel 'BatchReports' van de DML-instructie mag geen geactiveerde triggers hebben als de instructie een OUTPUT-clausule bevat zonder INTO-clausule

Oplossingpoging #1

Dus we proberen iets waarbij we een tussenliggende TABLE . zullen gebruiken variabele om de OUTPUT . vast te houden resultaten:

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

Behalve dat dat niet lukt omdat je geen timestamp mag invoegen in de tabel (zelfs een tijdelijke tabelvariabele).

Oplossingpoging #2

We weten stiekem dat een timestamp is eigenlijk een 64-bits (ook bekend als 8 byte) geheel getal zonder teken. We kunnen onze tijdelijke tabeldefinitie wijzigen om binary(8) te gebruiken in plaats van 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

En dat werkt, behalve dat de waarde verkeerd is .

Het tijdstempel RowVersion we return is niet de waarde van het tijdstempel zoals het bestond nadat de UPDATE voltooid was:

  • tijdstempel geretourneerd :0x0000000001B71692
  • werkelijke tijdstempel :0x0000000001B71693

Dat komt omdat de waarden OUTPUT in onze tabel zijn niet de waarden zoals ze waren aan het einde van de UPDATE-instructie:

  • UPDATE-instructie die begint
    • wijzigt rij
      • tijdstempel is bijgewerkt (bijv. 2 → 3)
    • OUTPUT haalt nieuwe tijdstempel op (d.w.z. 3)
    • trigger loopt
      • wijzigt rij opnieuw
        • tijdstempel is bijgewerkt (bijv. 3 → 4)
  • UPDATE-instructie voltooid
  • OUTPUT retourneert 3 (de verkeerde waarde)

Dit betekent:

  • We krijgen de tijdstempel niet zoals deze bestaat aan het einde van de UPDATE-instructie (4 )
  • In plaats daarvan krijgen we de tijdstempel zoals deze was in het onbepaalde midden van de UPDATE-instructie (3 )
  • We krijgen niet de juiste tijdstempel

Hetzelfde geldt voor elke trigger die elke . wijzigt waarde in de rij. De OUTPUT zal de waarde niet UITVOEREN vanaf het einde van de UPDATE.

Dit betekent dat je OUTPUT niet kunt vertrouwen om ooit de juiste waarden te retourneren.

Deze pijnlijke realiteit is vastgelegd in de BOL:

Kolommen die worden geretourneerd door OUTPUT geven de gegevens weer zoals deze zijn nadat de INSERT-, UPDATE- of DELETE-instructie is voltooid, maar voordat triggers worden uitgevoerd.

Hoe loste Entity Framework het op?

Het .NET Entity Framework gebruikt rowversion voor Optimistic Concurrency. De EF is afhankelijk van het kennen van de waarde van de timestamp zoals het bestaat nadat ze een UPDATE hebben uitgegeven.

Aangezien u OUTPUT . niet kunt gebruiken voor alle belangrijke gegevens gebruikt het Entity Framework van Microsoft dezelfde oplossing als ik:

Oplossing #3 - Laatste - Gebruik de OUTPUT-clausule niet

Om de na . op te halen waarden, Entity Framework-problemen:

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

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

Gebruik geen OUTPUT .

Ja, het lijdt aan een raceconditie, maar dat is het beste wat SQL Server kan doen.

Hoe zit het met INSERT's

Doe wat Entity Framework doet:

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

Nogmaals, ze gebruiken een SELECT statement om de rij te lezen, in plaats van vertrouwen te stellen in de OUTPUT-clausule.



  1. Postgres Query Plan waarom rijschatting zo verkeerd is

  2. sql voegt zich bij als venn-diagram

  3. Veelvoorkomende fouten van DBA in MS SQL Server

  4. Best Practice voor het maken van indexen op uw MySQL-tabellen - Rolling Index Builds