API do obsługi błędów to zestaw mechanizmów umożliwiających przechwytywanie i reagowanie na wyjątki w kodzie Transact-SQL.
Rola API obsługi błędów w Transact-SQL
API do obsługi błędów w Transact-SQL oznacza zestaw wbudowanych konstrukcji języka SQL Server, które pozwalają wykrywać wyjątki, przechwytywać informacje o ich wystąpieniu i podejmować kontrolowane działania. W praktyce najważniejszym mechanizmem jest para bloków BEGIN TRY i BEGIN CATCH. Instrukcje umieszczone w części TRY są wykonywane normalnie, natomiast błąd kwalifikujący się do przechwycenia powoduje przejście sterowania do odpowiadającego mu bloku CATCH. Dzięki temu procedura składowana lub skrypt może zakończyć transakcję, zapisać informacje diagnostyczne albo przekazać błąd wyżej, zamiast pozostawiać aplikację bez jednoznacznego wyniku.
Mechanizm nie jest osobnym serwerem ani usługą sieciową. Jest częścią środowiska wykonawczego aparatu SQL Server i języka Transact-SQL. Obsługuje błędy powstające między innymi podczas wykonywania zapytań, modyfikacji danych, wywoływania procedur oraz operacji transakcyjnych. Nie każdy problem zachowuje się jednak identycznie: część błędów występuje na poziomie kompilacji, część ma charakter ostrzeżenia, a część przerywa wykonywanie całej partii instrukcji. Dlatego poprawna implementacja powinna uwzględniać zakres przechwytywania konkretnego błędu, a nie zakładać, że każdy komunikat zostanie obsłużony przez dowolny blok CATCH.
TRY, CATCH i informacje diagnostyczne
Wewnątrz bloku CATCH SQL Server udostępnia funkcje opisujące ostatni przechwycony błąd. ERROR_NUMBER() zwraca numer komunikatu, ERROR_SEVERITY() jego poziom ważności, a ERROR_STATE() stan błędu. ERROR_PROCEDURE() wskazuje nazwę procedury lub wyzwalacza, w którym wystąpił problem, natomiast ERROR_LINE() zwraca numer wiersza. Treść komunikatu można pobrać za pomocą ERROR_MESSAGE(). Funkcje te zwracają wartości w kontekście bloku CATCH; poza nim nie dostarczają informacji o konkretnym, właśnie przechwyconym wyjątku.
Typowy blok obsługi błędu zapisuje wymienione wartości do tabeli dziennika albo przekazuje je do warstwy aplikacyjnej. Warto zachować nie tylko sam tekst komunikatu, lecz także numer błędu, procedurę i numer wiersza, ponieważ tekst może nie wystarczyć do odtworzenia ścieżki wykonania. Sam blok CATCH nie powinien bezwarunkowo zakładać, że transakcja nadaje się do zatwierdzenia. Jeżeli błąd uszkodził stan transakcji, należy sprawdzić XACT_STATE(). Wynik równy 1 oznacza transakcję aktywną i możliwą do zatwierdzenia, -1 oznacza transakcję aktywną, ale niemożliwą do zatwierdzenia, a 0 oznacza brak aktywnej transakcji.
Przekazywanie błędu i spójność transakcji
Do ponownego zgłoszenia przechwyconego wyjątku służy instrukcja THROW. W postaci bez parametrów, użytej wewnątrz bloku CATCH, przekazuje ona dalej pierwotny błąd wraz z jego numerem, stanem i informacją o lokalizacji. Możliwe jest także jawne zgłoszenie błędu przez składnię THROW numer_błędu, komunikat, stan, przy czym numer dla nowego błędu musi spełniać wymagania SQL Server, a komunikat nie może przekraczać limitu określonego przez składnię tej instrukcji. THROW wymaga zakończenia poprzedzającej instrukcji średnikiem, dlatego często stosuje się zapis ; THROW.
Starszym mechanizmem jest RAISERROR. Nadal może służyć do generowania komunikatów o określonej ważności i stanie, ale w nowym kodzie do ponownego propagowania wyjątku preferuje się THROW. Obsługa transakcji powinna mieć jasno określony schemat: po błędzie należy wykonać ROLLBACK TRANSACTION, jeżeli XACT_STATE() wskazuje transakcję aktywną, a następnie użyć THROW. Przy ustawieniu SET XACT_ABORT ON większość błędów wykonawczych powoduje automatyczne zakończenie i wycofanie transakcji, co ogranicza ryzyko pozostawienia częściowo wykonanej operacji. To ustawienie nie zastępuje jednak bloku CATCH, ponieważ nadal potrzebne może być logowanie i zwrócenie kontrolowanego komunikatu.
Granice mechanizmu i praktyczna weryfikacja
Obsługa błędów powinna być testowana dla różnych klas problemów, a nie tylko dla jednego błędnego parametru. Należy sprawdzić między innymi naruszenie ograniczenia klucza podstawowego, naruszenie klucza obcego, próbę zapisania wartości niezgodnej z typem lub ograniczeniem NOT NULL, konflikt blokad oraz błąd występujący w procedurze wywołanej z innej procedury. Dla każdego przypadku trzeba ustalić, czy blok CATCH został wykonany, czy transakcja została wycofana i czy warstwa wyżej otrzymała numer oraz tekst właściwego błędu.
Nie należy zastępować całego mechanizmu prostym sprawdzaniem @@ERROR. Zmienna @@ERROR zawiera numer błędu wygenerowanego przez poprzednią instrukcję, więc jej wartość trzeba odczytać natychmiast, zanim wykona się następne polecenie. TRY...CATCH zapewnia czytelniejszy zakres i dostęp do pełniejszego zestawu danych diagnostycznych. Podczas analizy problemu na komputerze, na którym działa aplikacja korzystająca z SQL Server, warto rozdzielić błąd zapytania, błąd procedury i błąd połączenia. Dziennik po stronie aplikacji może wskazywać jedynie nieudaną operację, podczas gdy funkcje ERROR_*() w SQL Server pozwalają ustalić jej rzeczywistą przyczynę.