Obsługa błędów w SQL i transakcje Zacznijmy od najprostszego przykładu: CREATE PROCEDURE podziel1 Obsługa błędów w SQL Powyższa procedura w większości przypadków zadziała prawidłowo, lecz na przykład poniższe wywołanie: EXEC podziel1 1, 0 jak można się spodziewać zakończy się błędem. Jakim? Najprostszy i najłatwiejszy ale nie zawsze dla programisty sposób obsługi błędów, to pilnowanie aby te błędy nie wystąpiły już na etapie programowania, na przykład: CREATE PROCEDURE podziel2 IF @dzielnik = 0 SELECT Dzielenie przez ZERO! W przypadku bardziej skomplikowanych programów najwłaściwszym sposobem jest oczywiście przewidzenie wszystkich tych miejsc w kodzie, w których mogą wystąpić problemy, i wykonanie wcześniejszych walidacji. Niestety nie zawsze jest to możliwe i co wtedy? Zmodyfikujmy więc procedurę podziel2 w następujący sposób: CREATE PROCEDURE podziel3
IF @dzielnik = 0 RAISERROR Dzielenie przez ZERO!,14,1 Na pierwszy rzut oka procedura ta niewiele się różni od procedury podziel1, zwłaszcza w przypadku dzielenia przez zero, za wyjątkiem w części zrozumiałego dla polskojęzycznego użytkownika komunikatu. Polecanie RAISERROR mianowicie powoduje wygenerowanie komunikatu błędu, który na przykład może być dalej obsłużony w bloku TRY... CATCH, na przykład w poniższej procedurze: CREATE PROCEDURE podziel i pomnoz @a float, @b float TRY tu może wystąpić błąd EXEC podziel3 @a, @b ale równie dobrze podziel1 TRY CATCH PRINT ERROR MESSAGE ERROR MESSAGE zwraca komunikat ostatniego błędu PRINT... ale się tym nie przejmujemy i liczymy dalej... CATCH TRY tu może wystąpić błąd EXEC podziel3 @b, @a ale równie dobrze podziel1 TRY CATCH PRINT ERROR MESSAGE PRINT... ale się tym nie przejmujemy i dzielimy dalej... CATCH SELECT @a * @b Zadanie 1. Co robi poniższa procedura przykład z pomocy? Przy okazji proszę zwrócić uwagę na trzeci sposób zwracania wyniku przez procedurę! CREATE PROCEDURE TimeDelay hh mm ss @DelayLength char8 = 00:00:00
DECLARE @ReturnInfo varchar255 IF ISDATE 2000-01-01 + @DelayLength +.000 = 0 SELECT @ReturnInfo = Invalid time + @DelayLength +,hh:mm:ss, submitted. This PRINT statement is for testing, not use in production. PRINT @ReturnInfo RETURN1 WAITFOR DELAY @DelayLength SELECT @ReturnInfo = A total time of + @DelayLength +, hh:mm:ss, has elapsed! + Your time is up. This PRINT statement is for testing, not use in production. PRINT @ReturnInfo Poniżej przykład jej wywołania: EXEC TimeDelay hh mm ss 00:00:05 Zadanie 2. Zmodyfikować procedurę z zadania 1 używając blok TRY... CATCH tak, aby nie korzystać z funkcji ISDATE.... Transakcje UWAGA: Kolejne przykłady należy wykonywać w dwóch oknach query. Dobry refleks jest wskazany! Zanim przystąpimy do doświadczeń zdefiniujmy pomocnicze procedury pisz i czytaj oraz tabelę konta ze zdefiniowanymi dwoma kontami jak poniżej: CREATE TABLE konta saldo float, nazwa varchar10 PRIMARY KEY NOT NULL CREATE PROCEDURE czytaj @nazwa varchar10, @saldo float OUTPUT SELECT@saldo = saldo FROMkonta WHERE@nazwa = nazwa CREATE PROCEDURE pisz @nazwa varchar10, @kwota float IF EXISTS SELECT saldo FROM konta WHERE @nazwa = nazwa UPDATE konta SET saldo = @kwota WHERE @nazwa = nazwa INSERT kontanazwa, saldo VALUES@nazwa, @kwota
Na koniec dodajemy dane kont: EXEC pisz A, 100 EXEC pisz B, 150 Rozpatrzmy teraz pierwsze dwa zestawy instrukcji: LEVEL REPEATABLE READ LEVEL REPEATABLE READ TRAN SET @przelew = 50 SET @saldoa = @saldoa - @przelew WAITFOR DELAY 00:00:05 EXECczytaj B, @saldob OUTPUT SET@saldoB = @saldob + 50 EXEC pisz B, @saldob TRAN WAITFOR DELAY 00:00:03 SET @przelew = @saldoa * 0.1 SET @saldoa = @saldoa - @przelew EXEC czytaj B, @saldob OUTPUT WAITFOR DELAY 00:00:05 SET @saldob = @saldob + @przelew EXEC pisz B, @saldob Zadanie 3. Z jakimi konfliktami mamy do czynienia w powyższym przykładzie? Co należy zrobić, aby co najmniej jedna z transakcji zadziałała prawidłowo? Poniżej kolejny zestaw instrukcji: LEVEL READ UNCOMMITTED DECLARE @licznik int LEVEL READ UNCOMMITTED DECLARE @licznik int SET @licznik = 12 TRAN WHILE @licznik > 0 SELECT @saldoa WAITFOR DELAY 00:00:01 SET @licznik = @licznik - 1 SET @licznik = 6 TRAN WHILE @licznik > 0 WAITFOR DELAY 00:00:02 SET @saldoa = @saldoa - 10 SET @licznik = @licznik - 1
Zadanie 4. Przeprowadzić serię eksperymentów zarówno dla pierwszego jak i drugiego zestawu instrukcji. Zadanie 5. Zaproponować przykład ilustrujący zakleszczenie. Wskazówka: Wykorzystać poziom izolacji: READ COMMITTED. Na zakończenie przykładowa obsługa błędów w transakcji: TRAN TRY INSERT kontanazwa, saldo VALUES A, 100 TRY CATCH SELECT ERROR NUMBER ErrorNumber, ERROR SEVERITY ErrorSeverity, ERROR STATE ErrorState, ERROR PROCEDURE ErrorProcedure, ERROR LINE ErrorLine, ERROR MESSAGE ErrorMessage IF @@TRANCOUNT > 0 ROLLBACK TRAN CATCH IF @@TRANCOUNT > 0