¿Cuál es el mejor método para agregar manejo de errores en los procesos almacenados de SQL 2005?

11

¿Cuál es una buena manera de hacer que los procesos almacenados sean lo suficientemente robustos como para que puedan escalar muy bien y también contener el manejo de errores?

Además, ¿cuál es la mejor manera de manejar múltiples escenarios de error en un proceso almacenado y tener un sistema de retroalimentación inteligente que devolverá información de error significativa a las aplicaciones de llamada?

kacalapy
fuente
2
Intente usar el bloque TRY CATCH más reciente en SQL Server 2005. sommarskog.se/error_handling_2005.html
Sankar Reddy
Hola @Kacalapy ~ Me gustaría recomendar en el futuro hacer cada pregunta por derecho propio y de esa manera podemos tener respuestas específicas centradas en una pregunta a la vez. Te animo a que hagas eso con esta pregunta.
jcolebrand

Respuestas:

12

Alex Kuznetsov tiene un excelente capítulo en su libro Defensive Database Programming (Capítulo 8) que cubre T-SQL TRY ... CATCH, transacciones T-SQL y configuración de SET XACT_ABORT, y utiliza el manejo de errores del lado del cliente. Le ayudará mucho a decidir cuál de las opciones tiene más sentido para lo que necesita lograr.

Está disponible de forma gratuita en este sitio . De ninguna manera estoy afiliado a la compañía, pero soy dueño de la versión impresa de ese libro.

Hay muchos pequeños detalles sobre este tema que Alex explica muy bien.

Por solicitud de Nick ... (pero no todo esto está en el capítulo)

En términos de escalado, debe ser brutalmente honesto acerca de qué actividades deben estar en el código db y cuáles deben estar en la aplicación. ¿Alguna vez notó cómo el código de ejecución rápida tiende a volver a diseñar para una sola preocupación por método?

La forma más fácil de comunicarse sería con códigos de error personalizados (> 50,000). También es bastante rápido. Significa que tendría que mantener el código db y el código de la aplicación sincronizados. Con un código de error personalizado, también puede devolver información útil en la cadena del mensaje de error. Debido a que tiene un código de error estrictamente para esa situación, puede escribir un analizador en el código de la aplicación adaptado al formato de datos del error.

Además, ¿qué condiciones de error necesitan lógica de reintento en la base de datos? Si desea volver a intentarlo después de X segundos, es mejor que lo maneje en el código de la aplicación para que la transacción no se bloquee tanto. Si solo vuelve a enviar una operación DML de inmediato, repetirla en el SP podría ser más eficiente. Sin embargo, tenga en cuenta que posiblemente tendrá que duplicar el código o agregar una capa de SP para lograr un nuevo intento.

Realmente, ese es actualmente el mayor problema con la lógica TRY ... CATCH en SQL Server en este momento. Se puede hacer, pero es un poco tonto. Busque algunas mejoras para esto en SQL Server 2012, especialmente para volver a lanzar excepciones del sistema (preservar el número de error original). Además, hay FORMATMESSAGE , que agrega cierta flexibilidad en la construcción de mensajes de error, especialmente para fines de registro.

Phil Helmer
fuente
¡Un gran consejo y un muy buen libro!
Marian
Red Gate ofrece varios libros electrónicos gratuitos extremadamente útiles, y este es sin duda uno de los mejores. Gran sugerencia
Matt M
No todos sus libros hacen esto, pero la versión gratuita del libro "Defensivo ..." de Kuznetsov no contiene los últimos 2 capítulos sobre Niveles de aislamiento de transacciones y Modificaciones en desarrollo que sobreviven a la concurrencia. Para mi. el contenido allí valió la pena la compra.
Phil Helmer el
7

Esta es nuestra plantilla (registro de errores eliminado)

Notas:

  • Sin XACT_ABORT, todos los TXN comienzan y los commit / rollbacks deben estar emparejados
  • Un compromiso decrementa @@ TRANCOUNT
  • Una reversión devuelve @@ TRANCOUNT a cero para que obtenga el error 266
  • No puede ROLLBACK solo la capa actual (por ejemplo, decrement @@ TRANCOUNT en rollback)
  • XACT_ABORT suprime el error 266
  • Cada proceso almacenado debe cumplir con la misma plantilla para que cada llamada sea atómica
  • La verificación de reversión es realmente redundante debido a XACT_ABORT. Sin embargo, me hace sentir mejor, se ve extraño sin él y permite situaciones en las que no quieres
  • Esto permite TXN del lado del cliente (como LINQ)
  • Remus Rusanu tiene un caparazón similar que usa puntos de guardado. Prefiero una llamada de base de datos atómica y no uso actualizaciones parciales como su artículo

... así que no cree más TXN de los que necesita

Sin embargo,

CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0
        BEGIN TRANSACTION

       [...Perform work, call nested procedures...]

    IF @starttrancount = 0 
        COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION
    RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO
gbn
fuente
¿Qué pasa si @@ TRANCOUNT es mayor que 0? ¿no trabajas o tienes algún comentario?
kacalapy
@kacalapy: no existe una transacción anidada, por lo que no iniciamos
gbn
3

Uso Try / Catch, pero también recopilo tanta información como sea posible y la escribo en un registro de errores DESPUÉS de la reversión. En este ejemplo, "LogEvent" es un procedimiento almacenado que escribe en una tabla EventLog, que contiene los detalles de lo que sucedió. GetErrorInfo () es una llamada de función que devuelve el mensaje de error exacto.

Cuando se produce un error, se recopila la información, el procedimiento salta a la sección de manejo de errores y emite una reversión. La información se escribe en el registro, luego sale el procedimiento.

Teniendo en cuenta las llamadas de procedimiento / función adicionales involucradas, parece un poco exagerado. SIN EMBARGO, este método es tremendamente útil cuando se trata de depurar el problema.

ejecutivo LogEvent @Process, @Database, 'Intentando insertar bla, bla, bla'
COMIENCE A PROBAR
  insertar en MyTable
  seleccionar valores
    de MyOtherTable

  seleccione @rowcount = @@ ROWCOUNT
FIN INTENTAR
-- Manejo de errores
COMIENZA LA CAPTURA
  seleccione @error = ERROR_NUMBER (),
         @rowcount = -1,
         @TableAction = 'insertar',
         @TableName = @Database + '.MyTable',
         @AdditionalInfo = '(Intentando insertar bla, bla, bla)' + dbo.GetErrorInfo ()
   GOTO TableAccessError
FIN DE LA CAPTURA

.
.
.
.

TableAccessError:
IF (@@ TRANCOUNT> 0) ROLLBACK
seleccione @output = upper (@TableAction) + 
       'ERROR: se produjo un error mientras' '+ 
       case (@TableAction)
         cuando 'actualizar' y luego 'actualizar'
         cuando 'eliminar' y luego 'eliminar'
         sino @TableAction + 'ing'
       fin + 
       'registros' + 
       case (@TableAction) 
         cuando 'seleccionar' y luego 'desde' 
         cuando 'actualizar' luego 'en' 
         cuando 'insertar' luego 'en'
         más 'de'   
         fin + 
         'la tabla' + @TableName + '.'
seleccione @output = @output + '@@ ERROR:' + convert (varchar (8), @ error) 
seleccione @output = @output + '@@ ROWCOUNT:' + convert (varchar (8), @ rowcount) 

seleccione @output = @output + isnull (@AdditionalInfo, '')
ejecutivo LogEvent @Process, @Database, @Output
RAISERROR (@ salida, 16,1) con registro
seleccione @ReturnCode = -1
GOTO THE_EXIT


datagod
fuente