2016-07-19 15 views
1

Ich benutze SQLServer 2014 und ich habe eine einfache db mit einer Tabelle, die eine ID und eine Varchar-Spalte namens Daten hat. Es gibt einige seltsamen Verhalten, wenn ich die folgende Anweisung ausführen:Transaktionen brechen bei der Verwendung von SP_ExecuteSQL

SET XACT_ABORT ON 
BEGIN TRANSACTION 
    exec sp_executesql N'some nonsense' 
    insert into testTable values ('b') 

COMMIT 

SSMS zeigt, dass es ein Fehler war, weil ich versuchte, eine falsche Abfrage im sp_executesql Aufruf zu laufen. Es zeigt jedoch auch 1 row(s) affected. Wenn ich eine Select-Abfrage auf der TestTable ausführen, kann ich sehen, dass der Wert "b" eingefügt wurde.

Wenn ich die Anweisungen in einem TRY/CATCH Block alles einwickeln wie erwartet funktioniert, und die gesamte Transaktion Aktion wird rückgängig gemacht:

BEGIN TRANSACTION 
BEGIN TRY 
    exec sp_executesql N'some nonsense' 
    insert into testTable values ('b') 

    COMMIT 
END TRY 
BEGIN CATCH 
    SELECT ERROR_MESSAGE() 
    ROLLBACK 
END CATCH 

Sollte nicht gewährleisten die SET XACT_ABORT ON, dass die gesamte Transaktion zurückgesetzt wird, wenn etwas geht falsch? Gibt es eine Einstellung, die ich vermisse?

Dank

+2

Sieht aus wie es hängt davon ab, was "etwas Unsinn" ist, wenn es ein Kompilierzeitfehler ist, verhält es sich wie du sagst. Wenn es sich um einen Laufzeitfehler handelt (zB 'exec sp_executesql N'SELECT 1/0'') verhält es sich wie gewünscht. –

+0

Ok, ich wusste nicht, dass das einen Unterschied machen würde. Ich hatte einen Syntaxfehler, und das würde es erklären. Gibt es jedoch eine Dokumentation, die erklären würde, warum das so ist? Ich würde immer noch erwarten, dass die gesamte Transaktion zurückgesetzt wird, selbst wenn es sich um einen Fehler bei der Kompilierung handelte. Oder behandelt die Option XACT_ABORT nur Laufzeitfehler? – Pinetree

+0

Ich war mir dieses Verhalten vorher nicht bewusst. Ich weiß nicht, ob es "von Entwurf" oder nicht ist. –

Antwort

2

Dies geschieht, weil ein Laufzeitsyntaxfehler, die nicht in einem nicht TRY/CATCH gewickelt ist keine aktive Transaktion ON abbrechen, auch mit XACT_ABORT Satz. Die genauen Regeln für das, was abbricht und nicht abbricht, und unter welchen Umständen, sind nicht einfach oder offensichtlich. Erland Sommarskog hat an excellent write-up on error handling in general und the rules of what does and doesn't abort in particular.

Ich werde alle nicht reproduzieren, die hier, aber hier ist das Problem auf das Wesentliche kocht:

SET XACT_ABORT ON -- or OFF, it makes no difference 
BEGIN TRANSACTION 
EXEC ('SELECT')  -- Incorrect syntax near 'SELECT' 
PRINT @@TRANCOUNT -- Prints 1, transaction is still going 
COMMIT 
PRINT @@TRANCOUNT -- Prints 0, transaction succeeded 

Trotz XACT_ABORT ON, Ausführung nicht nur nicht zu stoppen, wird die Transaktion nicht einmal abgebrochen. Hinzufügen TRY/CATCH ändert die Regeln:

SET XACT_ABORT ON 
BEGIN TRANSACTION 
BEGIN TRY 
    EXEC ('SELECT')    -- Incorrect syntax near 'SELECT' 
    PRINT 'After bad statement.' -- Does not print 
    COMMIT 
END TRY 
BEGIN CATCH 
    PRINT @@TRANCOUNT   -- Prints 1, transaction is still going, but it's doomed 
END CATCH 
-- Error here: 
-- 'Uncommittable transaction is detected at the end of the batch. 
-- The transaction is rolled back.' 

Jetzt wird die Transaktion zum Scheitern verurteilt, und wenn wir es uns nicht zurückrollen, SQL Server macht es für uns (mit einem Fehler). Dieses dooming ist mit freundlicher Genehmigung vollständig aus den XACT_ABORT, weil es aus Ausbeuten etwas andere noch einmal drehen:

SET XACT_ABORT OFF 
BEGIN TRANSACTION 
BEGIN TRY 
    EXEC ('SELECT')    -- Incorrect syntax near 'SELECT' 
    PRINT 'After bad statement.' -- Does not print 
    COMMIT 
END TRY 
BEGIN CATCH 
    PRINT @@TRANCOUNT    -- Prints 1, transaction is still going 
END CATCH 
PRINT @@TRANCOUNT     -- Prints 1, transaction is still going! 
ROLLBACK 

Die Moral der Geschichte ist: richtige Fehlerbehandlung in T-SQL ist sehr schwierig. Was normalerweise für mich funktioniert, ist SET XACT_ABORT ON für alle nicht-trivialen Batch-Anweisungen zu tun und Transaktionen (außerhalb des SQL-Servers) zu initiieren und zu committen oder rückgängig zu machen (über Client-Code). Dies umgeht viele der Schwierigkeiten beim Verständnis dessen, was eine Transaktion stoppt und unterbindet, da jeder Fehler, den SQL Server an den Client zurückgibt, letztendlich zu einem Rollback führt. Aber selbst das ist natürlich keine Wunderwaffe.