sql >> Base de Datos >  >> RDS >> Sqlserver

Implementación del manejo de errores y transacciones en SQL Server

Introducción

No importa cuánto nos esforcemos en diseñar y desarrollar aplicaciones, siempre se producirán errores. Hay dos categorías generales:los errores de sintaxis o lógicos pueden ser errores de programación o consecuencias de un diseño de base de datos incorrecto. De lo contrario, podría recibir un error debido a una entrada de usuario incorrecta.

T-SQL (el lenguaje de programación de SQL Server) permite manejar ambos tipos de errores. Puede depurar la aplicación y decidir qué debe hacer para evitar errores en el futuro.

La mayoría de las aplicaciones requieren que registre los errores, implemente informes de errores fáciles de usar y, cuando sea posible, maneje los errores y continúe con la ejecución de la aplicación.

Los usuarios manejan los errores a nivel de declaración. Significa que cuando ejecuta un lote de comandos SQL y el problema ocurre en la última declaración, todo lo que precede a ese problema se confirmará en la base de datos como transacciones implícitas. Puede que esto no sea lo que deseas.

Las bases de datos relacionales están optimizadas para la ejecución de sentencias por lotes. Por lo tanto, debe ejecutar un lote de declaraciones como una unidad y fallar todas las declaraciones si falla una declaración. Puede lograr esto mediante el uso de transacciones. Este artículo se centrará tanto en el manejo de errores como en las transacciones, ya que estos temas están estrechamente relacionados.

Manejo de errores de SQL

Para simular excepciones, necesitamos producirlas de manera repetible. Comencemos con el ejemplo más simple:división por cero:

SELECT 1/0

El resultado describe el error arrojado:Dividir por cero error encontrado . Pero este error no se manejó, registró ni personalizó para producir un mensaje fácil de usar.

El manejo de excepciones comienza colocando las declaraciones que desea ejecutar en el bloque BEGIN TRY…END TRY.

SQL Server maneja (captura) los errores en el bloque BEGIN CATCH…END CATCH, donde puede ingresar una lógica personalizada para el registro o procesamiento de errores.

La sentencia BEGIN CATCH tiene que seguir inmediatamente después de la sentencia END TRY. Luego, la ejecución pasa del bloque TRY al bloque CATCH en el primer error.

Aquí puede decidir cómo manejar los errores, ya sea que desee registrar los datos sobre las excepciones generadas o crear un mensaje fácil de usar.

SQL Server tiene funciones integradas que pueden ayudarlo a extraer detalles del error:

  • ERROR_NUMBER():Devuelve el número de errores de SQL.
  • ERROR_SEVERITY():Devuelve el nivel de gravedad que indica el tipo de problema encontrado y su nivel. Los niveles 11 a 16 pueden ser manejados por el usuario.
  • ERROR_STATE():Devuelve el número de estado de error y brinda más detalles sobre la excepción lanzada. Utilice el número de error para buscar en la base de conocimientos de Microsoft detalles específicos del error.
  • ERROR_PROCEDURE():Devuelve el nombre del procedimiento o disparador en el que se generó el error, o NULL si el error no ocurrió en el procedimiento o disparador.
  • ERROR_LINE():Devuelve el número de línea en el que ocurrió el error. Podría ser el número de línea de procedimientos o disparadores o el número de línea en el lote.
  • ERROR_MESSAGE():Devuelve el texto del mensaje de error.

El siguiente ejemplo muestra cómo manejar los errores. El primer ejemplo contiene la División por cero error, mientras que la segunda afirmación es correcta.

BEGIN TRY
   PRINT 1/0  
   SELECT 'Correct text'
END TRY
BEGIN CATCH
   SELECT ERROR_NUMBER() AS ERR_NO
   ,      ERROR_SEVERITY() AS ERR_SEV
   ,      ERROR_STATE() AS ERR_STATE
   ,      ERROR_LINE() AS ERR_LINE
   ,      ERROR_MESSAGE() AS ERR_MESSAGE
END CATCH

Si la segunda declaración se ejecuta sin manejo de errores (SELECCIONE 'Texto correcto'), tendrá éxito.

Dado que implementamos el manejo de errores personalizado en el bloque TRY-CATCH, la ejecución del programa se pasa al bloque CATCH después del error en la primera instrucción y la segunda instrucción nunca se ejecutó.

De esta manera, puedes modificar el texto que se le da al usuario y controlar mejor lo que sucede si se produce un error. Por ejemplo, registramos los errores en una tabla de registro para su posterior análisis.

Uso de transacciones

La lógica empresarial puede determinar que la inserción de la primera declaración falla cuando falla la segunda declaración, o que es posible que deba repetir los cambios de la primera declaración en la falla de la segunda declaración. El uso de transacciones le permite ejecutar un lote de declaraciones como una unidad que falla o tiene éxito.

El siguiente ejemplo demuestra el uso de transacciones.

Primero, creamos una tabla para probar los datos almacenados. Luego usamos dos transacciones dentro del bloque TRY-CATCH para simular lo que sucede si una parte de la transacción falla.

Usaremos la sentencia CATCH con la sentencia XACT_STATE(). La función XACT_STATE() se utiliza para verificar si la transacción aún existe. En caso de que la transacción se revierta automáticamente, ROLLBACK TRANSACTION produciría una nueva excepción.

Obtenga un botín en el siguiente código:

-- CREATE TABLE TEST_TRAN(VALS INT)

BEGIN TRY
   BEGIN TRANSACTION
       INSERT INTO TEST_TRAN(VALS) VALUES(1);
   COMMIT TRANSACTION  

   BEGIN TRANSACTION
       INSERT INTO TEST_TRAN(VALS) VALUES(2);
       INSERT INTO TEST_TRAN(VALS) VALUES('A'); 
       INSERT INTO TEST_TRAN(VALS) VALUES(3);
   COMMIT TRANSACTION
END TRY
BEGIN CATCH  
   IF XACT_STATE() > 0 ROLLBACK TRANSACTION

   SELECT ERROR_NUMBER() AS ERR_NO
   ,      ERROR_SEVERITY() AS ERR_SEV
   ,      ERROR_STATE() AS ERR_STATE
   ,      ERROR_LINE() AS ERR_LINE
   ,      ERROR_MESSAGE() AS ERR_MESSAGE

END CATCH

SELECT * FROM TEST_TRAN

-- DROP TABLE TEST_TRAN

La imagen muestra los valores en la tabla TEST_TRAN y mensajes de error:

Como puede ver, solo se comprometió el primer valor. En la segunda transacción, tuvimos un error de conversión de tipo en la segunda fila. Por lo tanto, todo el lote retrocedió.

De esta manera, puede controlar qué datos ingresan a la base de datos y cómo se procesan los lotes.

Generando un mensaje de error personalizado en SQL

A veces, queremos crear mensajes de error personalizados. Por lo general, están destinados a escenarios en los que sabemos que podría ocurrir un problema. Podemos producir nuestros propios mensajes personalizados diciendo que ha ocurrido algo malo sin mostrar detalles técnicos. Para eso, estamos usando la palabra clave THROW.

BEGIN TRY
   IF ( SELECT COUNT(sys.all_objects) > 1 )
	THROW ‘More than one object is ALL_OBJECTS system table’
END TRY
BEGIN CATCH
   SELECT ERROR_NUMBER() AS ERR_NO
   ,      ERROR_SEVERITY() AS ERR_SEV
   ,      ERROR_STATE() AS ERR_STATE
   ,      ERROR_LINE() AS ERR_LINE
   ,      ERROR_MESSAGE() AS ERR_MESSAGE
END CATCH

O bien, nos gustaría tener un catálogo de mensajes de error personalizados para la categorización y la coherencia de la supervisión y el informe de errores. SQL Server nos permite predefinir el código del mensaje de error, la gravedad y el estado.

Se utiliza un procedimiento almacenado llamado "sys.sp_addmessage" para agregar mensajes de error personalizados. Podemos usarlo para llamar al mensaje de error en varios lugares.

Podemos llamar a RAISERROR y enviar el número de mensaje como un parámetro en lugar de codificar los mismos detalles del error en varios lugares del código.

Al ejecutar el código seleccionado desde abajo, agregamos el error personalizado en SQL Server, lo generamos y luego usamos sys.sp_dropmessage para eliminar el mensaje de error definido por el usuario especificado:

exec sys.sp_addmessage @msgnum=55000, @severity = 11, 
                                          @msgtext = 'My custom error message'
GO

RAISERROR(55000,11,1)
GO

exec sys.sp_dropmessage @msgnum=55000
GO

Además, podemos ver todos los mensajes en SQL Server ejecutando el formulario de consulta a continuación. Nuestro mensaje de error personalizado es visible como el primer elemento en el conjunto de resultados:

SELECT * FROM master.dbo.sysmessages

Crear un sistema para registrar errores

Siempre es útil registrar errores para su posterior depuración y procesamiento. También puede poner disparadores en estas tablas registradas e incluso configurar una cuenta de correo electrónico y ser un poco creativo en la forma de notificar a las personas cuando ocurre un error.

Para registrar errores, creamos una tabla llamada DBError_Log , que se puede utilizar para almacenar los datos de detalle del registro:

CREATE TABLE DBError_Log
(
    DBError_Log_ID    INT IDENTITY(1, 1) PRIMARY KEY,
    UserName              VARCHAR(100),
    ErrorNumber    INT,
    ErrorState     INT,
    ErrorSeverity  INT,
    ErrorLine      INT,
    ErrorProcedure VARCHAR(MAX),
    ErrorMessage   VARCHAR(MAX),
    ErrorDateTime  DATETIME
);

Para simular el mecanismo de registro, estamos creando el GenError procedimiento almacenado que genera la División por cero error y registra el error en DBError_Log tabla:

CREATE PROCEDURE dbo.GenError
AS
  BEGIN TRY
    SELECT 1/0
  END TRY
  BEGIN CATCH
    INSERT INTO dbo.DBError_Log
    VALUES
    (SUSER_SNAME(),
     ERROR_NUMBER(),
     ERROR_STATE(),
     ERROR_SEVERITY(),
     ERROR_LINE(),
     ERROR_PROCEDURE(),
     ERROR_MESSAGE(),
     GETDATE()
	);
  END CATCH
GO

EXEC dbo.GenError
SELECT * FROM  dbo.DBError_Log

El DBError_Log La tabla contiene toda la información que necesitamos para depurar el error. Además, proporciona información adicional sobre el procedimiento que provocó el error. Aunque esto puede parecer un ejemplo trivial, puede ampliar esta tabla con campos adicionales o usarla para llenarla con excepciones personalizadas.

Conclusión

Si queremos mantener y depurar aplicaciones, al menos queremos informar que algo salió mal y también registrarlo bajo el capó. Cuando tenemos una aplicación de nivel de producción utilizada por millones de usuarios, el manejo de errores consistente y reportable es la clave para depurar problemas en tiempo de ejecución.

Si bien pudimos registrar el error original en el registro de errores de la base de datos, los usuarios deberían ver un mensaje más amigable. Por lo tanto, sería una buena idea implementar mensajes de error personalizados que se envían a las aplicaciones de llamada.

Sea cual sea el diseño que implemente, debe registrar y manejar las excepciones del usuario y del sistema. Esta tarea no es difícil con SQL Server, pero debe planificarla desde el principio.

Agregar las operaciones de manejo de errores en bases de datos que ya se están ejecutando en producción puede implicar una refactorización de código grave y problemas de rendimiento difíciles de encontrar.