Jak rejestrować szczegółowe informacje o błędzie podczas korzystania z komendy try / catch dla dynamicznych poleceń tworzenia kopii zapasowych SQL

10

Podczas wydawania polecenia tworzenia kopii zapasowej w ramach procedury składowanej, która korzysta z try catch i dynamicznego SQL, komunikaty o błędach są bardzo ogólne w porównaniu z bezpośrednim uruchomieniem polecenia tworzenia kopii zapasowej.

Spróbuj / Złap w SP:

    begin try
        execute sp_executesql @sql;  -- a backup command
    end try
    begin catch  
        print ERROR_MESSAGE();  -- save to log, etc.
    end catch

Prowadzi do

50000: usp_Backup: 117: BACKUP DATABASE kończy się nieprawidłowo.

obszary wydające surowe polecenie:

    backup DATABASE someDb to disk...

Wyniki w lepszych szczegółach:

Błąd wyszukiwania - błąd bazy danych SQL Server: Wystąpił nieodwracalny błąd we / wy w pliku „H: \ FolderName \ Filename.bak:” 112 (na dysku jest za mało miejsca).

Czy istnieje sposób na przechwycenie tych szczegółów w zmiennych w ramach procedury składowanej (w celu zalogowania, przekazania z powrotem do osoby dzwoniącej, w celu ponownej logiki)? Wygląda na to, że szczegóły przechodzą na kanale wiadomości, ale chciałbym, żeby były dostępne w SP.

Crokusek
źródło
Możesz to zobaczyć: stackoverflow.com/questions/5966670/…
8kb

Odpowiedzi:

13

Gdy BACKUP DATABASEgeneruje błąd, w rzeczywistości generuje dwa. Niestety TRY/CATCHnie jest w stanie uchwycić pierwszego błędu; wychwytuje tylko drugi błąd.

Podejrzewam, że najlepszym rozwiązaniem, aby uchwycić prawdziwy powód nieudanej kopii zapasowej, jest zautomatyzowanie kopii zapasowych za pomocą SQLCMD (z -owysyłaniem danych wyjściowych do pliku), SSIS, C #, PowerShell itp. Wszystko to daje znacznie większą kontrolę nad przechwytywaniem wszystkich błędów.

Odpowiedź SO w komentarzu sugeruje użycie DBCC OUTPUTBUFFER- chociaż jest to możliwe, wcale nie wydaje się to dziecinną zabawą. Zapraszam do zabawy z tą procedurą ze strony Erlanda Sommarskoga , ale nadal nie działa to dobrze w połączeniu z TRY/CATCH.

Wydaje mi się, że jedynym sposobem na uchwycenie komunikatu o błędzie jest zgłoszenie spGET_LastErrorMessagebłędu. Jeśli otoczysz go, TRY/CATCHbłąd zostanie połknięty, a procedura składowana nie spowoduje:

BEGIN TRY
  EXEC sp_executesql N'backup that fails...';
END TRY
BEGIN CATCH
  EXEC dbo.spGet_LastErrorMessage;
END CATCH

W SQL Server <2012 nie można samodzielnie zgłosić błędu, ale można to zrobić w SQL Server 2012 i nowszych. Te dwie odmiany działają:

CREATE PROCEDURE dbo.dothebackup
AS
BEGIN
  SET NOCOUNT ON;
  EXEC sp_executesql N'backup that fails...';
END
GO

EXEC dbo.dothebackup;
EXEC dbo.spGET_LastErrorMessage;

Lub w 2012 roku i powyżej, to działa, ale w dużym stopniu nie spełnia celu TRY/CATCH, ponieważ pierwotny błąd wciąż jest generowany:

CREATE PROCEDURE dbo.dothebackup2
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRY
    EXEC sp_executesql N'backup that fails...';
  END TRY
  BEGIN CATCH
    THROW;
  END CATCH
END
GO

EXEC dbo.dothebackup2;
EXEC dbo.spGET_LastErrorMessage;

W obu przypadkach błąd jest nadal zgłaszany klientowi. Więc jeśli używasz TRY/CATCHtego, aby tego uniknąć, chyba że jest jakaś luka, o której nie myślę, obawiam się, że będziesz musiał dokonać wyboru ... albo dać użytkownikowi błąd i być w stanie uchwycić szczegółowe informacje na temat lub pomiń zarówno błąd, jak i rzeczywistą przyczynę.

Aaron Bertrand
źródło
Choć to niedorzeczne, podejście Sommarskog nie wydaje się wykluczone, jeśli chcę tylko przekazać kontekst dzwoniącemu w interfejsie. Lepsze niż rozpoczęcie oddzielnego procesu. Chcesz powiedzieć, że to nie zadziała w ramach TRY / CATCH?
crokusek
@crokusek Wypróbowałem jedną odmianę i wynik wyszedł pusty. Dam dzisiaj jeszcze jedną szansę.
Aaron Bertrand
W przypadku braku błędu, czy LastErrorMessage () pobiera wyniki poprzednich błędów z sesji? Następnie, jeśli dwa ostatnie exec są uruchamiane jako skrypt, być może pierwszy exec może zostać zawinięty w try / catch i wewnątrz catch, ustaw zmienną, a następnie ponownie wyrzuć. Następnie LastError jest wywoływany tylko wtedy, gdy zmienna jest ustawiona. Zakłada, że ​​powtórzenie rzutu nie pomija drugiego połączenia, które moim zdaniem jest zwykle prawdziwe w kontekście skryptów. Nadal mogę nie być w stanie zastosować tego podejścia, ponieważ nie wszystko może być umieszczone w SP, jeśli dobrze rozumiem. W każdym razie dzięki!
crokusek
2

Wiem, że to stary wątek i wiem, że zamierzam zaproponować zawiły hack, ale na wszelki wypadek może pomóc każdemu, oto: Ponieważ te błędy tworzenia kopii zapasowych są rejestrowane, możesz użyć xp_readerrorlog blok, aby zeskrobać dziennik dla powiązanego komunikatu (błąd lub informacje). Możesz wyszukiwać w sieci parametry xp_readerrorlog, ale w skrócie możesz podać ciąg wyszukiwania i filtr czasu rozpoczęcia, które są przydatne w tym przypadku. Nie jestem pewien, czy pomogłoby to w ponownej logice, ale aby uchwycić informacje lub błędy w logowaniu, wpadłem na coś takiego ...

IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results (LogDate datetime,ProcessInfo nvarchar(100),LogText nvarchar(4000))
BEGIN TRY
SELECT @begintime = GETDATE()
EXEC sp_executesql @SQL --your backup statement string
INSERT #Results
EXEC  xp_readerrorlog 0, 1, N'backed up',@databasename,@begintime
SELECT @result = LogText from #Results where ProcessInfo = 'Backup' order by logdate desc
END TRY
BEGIN CATCH
INSERT #Results
EXEC  xp_readerrorlog 0, 1, N'Backup',@databasename,@begintime
SELECT @result = LogText from #Results where ProcessInfo = 'spid'+cast(@@SPID as varchar(6)) order by logdate desc
END CATCH
PRINT @result

HTH

Andy McD
źródło
Działa to świetnie w przypadku niektórych typowych błędów, ale są pewne błędy, które najwyraźniej są zgłaszane tylko bezpośrednio do klienta. Dziennik sp_readerrorlog będzie zawierał komunikat „odniesienie do„ dziennika aplikacji ”, przy czym przez„ aplikację ”zakładam, że oznaczają one zewnętrzny proces, który wydaje polecenia. SO Link
crokusek
0

Możesz zapisać szczegóły błędu w tabeli. Możesz także utworzyć plik dziennika, ale może to wymagać wykonania CLR lub xp_cmdshell. Możesz również wysyłać pocztę z bazy danych, ale może to powodować problemy ze spamem i nie jest prawidłowym dziennikiem.

Stół jest najprostszy.

  1. Utwórz tabelę do przechowywania błędów
  2. Utwórz procedurę składowaną, która wstawia się do tabeli błędów
  3. Wywołaj procedurę przechowywaną w bloku catch

Spójrz na przykład Jeremy Kadlec podany w poniższym linku:

http://www.mssqltips.com/sqlservertip/1152/standardized-sql-server-error-handling-and-centralized-logging/

Brian
źródło
3
Problem nie polega na tym, co zrobić z błędami, chodzi o to, że odpowiedni komunikat o błędzie nie jest dostępny dla niektórych poleceń CATCH. Dzieje się tak, ponieważ zwracany jest tylko ostatni komunikat o błędzie w ERROR_MESSAGE()...
Aaron Bertrand