Az SQL Server 2005-ben bevezetett TRY/CATCH hibakezelés óriási előrelépést jelentett az átlátható TSQL hibakezelés terén. Más nyelvekhez viszonyítva egy valami azonban hiányzott a képből, a THROW, amivel ki lehet váltani egy kivételt (exceptiont). Volt persze egy hasonló, a RAISERROR, ám ez kicsit más, mint a többi programnyelv throw-ja. A throw ugyanis általában kétféle dolgot tud. Az egyik, hogy feldob egy kivételt, a másik, hogy egy elkapott kivételt továbbdob. Ez utóbbi re-thrownak hívják, és azért nagyon hasznos, mert try-catch blokkban le tudjuk kezelni az eredeti hibát, pl. visszagörgetjük a tranzakciót, naplózunk vagy bármi egyéb akciót hajtunk végre, majd a catch ág végén újra feldobhatjuk az eredeti hibát. Így a kliens alkalmazás vagy SQL hívó pontosan megkapja az eredeti hiba részleteit, például a kivétel sora nem a catch ágba rakott (re)throw-ra mutat, hanem az eredeti hibát kiváltó SQL sorra.
A THROW tehát hasonló helyeken használatos, mint a RAISERROR, de van közöttük pár fontos különbség. A legfontosabbak: a THROW nem tud rendszer kivételeket generálni (SQL Server belső hibaüzeneteket), nem használja a sys.messages táblát (ebben vannak az előre definiált hibaüzenetek) és mindig 16-os severity levelű kivételeket dob.
Az alábbi példában egy update az, ami hibát dobhat, ha beleütközik egy idegen kulcsba. A kivételt elkapjuk, lelogoljuk, majd újradobjuk a kliens részére. A re-throw miatt a hibanaplózó logika nem szól bele a kliens által kapott hibarészletekbe.
BEGIN TRY --Ez hibázhat update HumanResources.Employee set BusinessEntityID = -1 where BusinessEntityID = 2; END TRY BEGIN CATCH --Naplózzuk a hibát INSERT INTO [dbo].[ErrorLog] ([ErrorTime] ,[UserName] ,[ErrorNumber] ,[ErrorSeverity] ,[ErrorState] ,[ErrorProcedure] ,[ErrorLine] ,[ErrorMessage]) VALUES(SYSDATETIME(), SUSER_NAME(), ERROR_NUMBER(), ERROR_SEVERITY(), ERROR_STATE(), ERROR_PROCEDURE(), ERROR_LINE(), ERROR_MESSAGE()); --Újradobjuk a kivételt a kliens részére THROW; END CATCH
A kliens ezt kapja:
Msg 547, Level 16, State 0, Line 3 The UPDATE statement conflicted with the REFERENCE constraint "FK_EmployeeDepartmentHistory_Employee_BusinessEntityID". The conflict occurred in database "AdventureWorks2012", table "HumanResources.EmployeeDepartmentHistory", column 'BusinessEntityID'.
Látható, hogy ez az eredeti hiba, a 3. sorra hivatkozik, nem a 29.-re, ahol valójában a THROW van. Az errorlog táblánkba ugyanez az információ kerül be:
Nyilván nem csak újradobásra jó a THROW, hanem olyan helyeken is, ahol eddig RAISERROR-ral dobtunk fel saját hibát.
Ez:
RAISERROR(N'Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok', 16, 1);
így néz ki THROW-val:
THROW 50000, N'Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok', 1;
A hívó mindkét esetben ezt kapja:
Msg 50000, Level 16, State 1, Line 1 Az alkalmazott nem törölhető, mert vannak hozzá kapcsolt élő feladatok
Összegezve, ha saját hibákat szeretnénk a kliensek felé közvetíteni, illetve, ha meg kell tartani az eredeti hibáról szóló információkat, akkor a THROW-t érdemes használni. Speciális esetekben, pl. nem 16-os szintű hibákhoz, vagy ha a sys.messagesben előre definiált hibaüzenetet kell feldobni, akkor továbbra is elérhető a RAISERROR.
Egyébként az a tény, hogy a THROW nem használja a sys.messages táblát igen fontos lépés, mert így könnyebb költöztetni az adatbázist másik szerverre, nem kell átvinni egyedi hibaüzeneteket a master táblák között. Ez kapcsolódik az itt nem tárgyalt, SQL 2012 újdonság Contained Databases újításhoz is.
Could you hire me? Contact me if you like what I’ve done in this article and think I can create value for your company with my skills.