Cómo registrar detalles de error al usar try / catch para comandos de copia de seguridad SQL dinámica

10

Al emitir un comando de respaldo dentro de un procedimiento almacenado que utiliza un try catch y sql dinámico, los mensajes de error son muy generales en comparación con la ejecución directa del comando de respaldo.

Probar / Atrapar dentro de SP:

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

Resultados en

50000: usp_Backup: 117: BACKUP DATABASE está terminando anormalmente.

cuando emitimos el comando sin procesar:

    backup DATABASE someDb to disk...

Resultados en mejores detalles:

Error de búsqueda: error de la base de datos de SQL Server: se produjo un error de E / S no recuperable en el archivo "H: \ FolderName \ Filename.bak:" 112 (No hay suficiente espacio en el disco).

¿Hay alguna manera de capturar estos detalles en variables dentro del procedimiento almacenado (para iniciar sesión, pasar de nuevo a la persona que llama, para volver a intentar la lógica)? Parece que los detalles están llegando en el canal de mensajes, pero me gustaría que estén disponibles en el SP.

crokusek
fuente
Es posible que desee ver esto: stackoverflow.com/questions/5966670/…
8kb

Respuestas:

13

Cuando BACKUP DATABASEgenera un error, en realidad genera dos. Lamentablemente TRY/CATCHno es capaz de capturar el primer error; solo captura el segundo error.

Sospecho que su mejor opción para capturar la verdadera razón detrás de una copia de seguridad fallida es automatizar sus copias de seguridad a través de SQLCMD ( -opara enviar la salida a un archivo), SSIS, C #, PowerShell, etc. Todo lo cual le dará un control mucho mayor sobre la captura de todos de los errores

La respuesta SO en el comentario sugiere usar DBCC OUTPUTBUFFER, aunque es posible, esto no parece un juego de niños en absoluto. Siéntase libre de divertirse con este procedimiento desde el sitio de Erland Sommarskog , pero aún así no parece funcionar bien en combinación TRY/CATCH.

La única forma en la que parecía capaz de capturar el mensaje de error spGET_LastErrorMessagees si se produce el error real. Si lo envuelve en un TRY/CATCHerror, el error se traga y el procedimiento almacenado no hace nada:

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

En SQL Server <2012 no puede volver a generar el error usted mismo, pero puede hacerlo en SQL Server 2012 y versiones posteriores. Entonces estas dos variaciones funcionan:

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;

O en 2012 y en adelante, esto funciona, pero en gran medida frustra el propósito de TRY/CATCH, ya que el error original aún se produce:

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;

En ambos casos, el error todavía se arroja al cliente, por supuesto. Entonces, si está utilizando TRY/CATCHpara evitar eso, a menos que haya algún vacío legal en el que no esté pensando, me temo que tendrá que tomar una decisión ... ya sea dar el error al usuario y poder capturar detalles sobre o suprima tanto el error como la razón real.

Aaron Bertrand
fuente
Por ridículo que sea, el enfoque de Sommarskog no parece descartado si solo quisiera proporcionar un contexto a la persona que llama dentro de una interfaz. Mejor que comenzar un proceso separado. ¿Estás diciendo que no funcionará dentro de un TRY / CATCH?
crokusek
@crokusek Intenté una variación y el resultado quedó vacío. Le daré otra oportunidad hoy.
Aaron Bertrand
En caso de que no haya ningún error, ¿LastErrorMessage () obtiene los resultados de algún error anterior de la sesión? Luego, si los dos últimos ejecutivos se ejecutan como script, tal vez el primer ejecutivo se pueda incluir dentro de un try / catch y dentro del catch, establecer una variable y luego volver a lanzar. Entonces se llama a LastError solo si se establece la variable. Asume que el relanzamiento no omite la segunda llamada, lo que creo que generalmente es cierto dentro de un contexto de secuencias de comandos. Es posible que todavía no pueda utilizar este enfoque al final porque no se puede colocar todo dentro de un SP si lo entiendo correctamente. Gracias sin embargo!
crokusek
2

Bueno, sé que este es un hilo antiguo, y sé que lo que voy a proponer es un truco complicado, pero en caso de que pueda ayudar a alguien, aquí va: dado que estos errores de copia de seguridad se registran, puede usar xp_readerrorlog en la captura bloque para raspar el registro para el mensaje relacionado (error o información). Puede buscar en Google los parámetros xp_readerrorlog, pero en resumen puede especificar una cadena de búsqueda y un filtro de tiempo de inicio que son útiles en este caso. No estoy seguro de si esto ayudaría a su lógica de reintento, pero para capturar información o errores para iniciar sesión, se me ocurrió algo como esto ...

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
fuente
Esto funciona muy bien para algunos errores comunes, pero hay algunos errores que aparentemente solo se lanzan directamente al cliente. El registro de sp_readerrorlog incluirá un mensaje que dice referirse al "registro de la aplicación", donde supongo que por "aplicación" se refieren al proceso externo que emite comandos. SO Link
crokusek
0

Puede registrar los detalles del error en una tabla. También puede crear un archivo de registro, pero eso puede requerir un CLR o xp_cmdshell para hacerlo. También puede enviar correo de la base de datos, pero eso puede causar problemas de spam y no es un registro adecuado.

La tabla es más simple.

  1. Crear una tabla para almacenar los errores.
  2. Cree un procedimiento almacenado que se inserte en la tabla de errores
  3. Llame al procedimiento almacenado en el bloque catch

Mire el ejemplo de Jeremy Kadlec provisto en el siguiente enlace:

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

brian
fuente
3
El problema no es qué hacer con los errores, es que el mensaje de error apropiado no está disponible para algunos comandos dentro CATCH. Esto se debe a que solo se devuelve el último mensaje de error en ERROR_MESSAGE()...
Aaron Bertrand