Transacciones y TRY/CATCH: completar o revertir una unidad de trabajo

Cuando una operación modifica varias tablas, conservar solo la primera mitad puede dejar datos inconsistentes. Una transacción agrupa instrucciones para confirmarlas juntas con COMMIT o deshacerlas con ROLLBACK.

TRY/CATCH permite capturar errores y asegurar que una transacción pendiente no quede abierta silenciosamente.

La idea

  • BEGIN TRANSACTION inicia la unidad de trabajo.
  • COMMIT confirma los cambios.
  • ROLLBACK revierte los cambios todavía no confirmados.
  • TRY/CATCH separa la ejecución normal del manejo de errores.

Sintaxis

BEGIN TRY
    BEGIN TRANSACTION;

    -- Instrucciones relacionadas.

    COMMIT;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0
        ROLLBACK;

    THROW;
END CATCH;

THROW vuelve a entregar el error al proceso que ejecutó el código. Ocultarlo podría hacer creer a la aplicación que la operación terminó correctamente.

Ejemplo: crear un pedido y su partida

SET XACT_ABORT ON;

BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO dbo.Pedido
        (IdCliente, FechaPedido, Estado)
    VALUES
        (3, SYSDATETIME(), 'Pendiente');

    DECLARE @IdPedido BIGINT = SCOPE_IDENTITY();

    INSERT INTO dbo.PedidoDetalle
        (IdPedido, NumeroPartida, IdProducto,
         Cantidad, PrecioUnitario, Descuento)
    VALUES
        (@IdPedido, 1, 1, 2, 349.00, 0);

    COMMIT;
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0
        ROLLBACK;

    THROW;
END CATCH;

Si la partida no puede insertarse, el bloque CATCH revierte también el encabezado del pedido. SET XACT_ABORT ON hace que muchos errores de ejecución terminen la transacción en lugar de permitir que el lote continúe en un estado parcial.

XACT_STATE() devuelve 1 cuando existe una transacción que todavía puede confirmarse, 0 cuando no existe una transacción activa y -1 cuando solo puede revertirse. Dentro del CATCH, la condición <> 0 cubre los dos estados donde todavía existe una transacción.

Transacciones anidadas

Llamar varias veces a BEGIN TRANSACTION incrementa @@TRANCOUNT, pero no crea unidades independientes que puedan confirmarse por separado. Un ROLLBACK sin punto de guardado revierte la transacción completa.

Cuando un procedimiento puede ejecutarse dentro de otra transacción, debe acordarse quién es responsable de iniciarla, confirmarla y revertirla. Un patrón aislado no sustituye ese contrato.

Mantener la transacción corta

Mientras una transacción permanece abierta puede conservar bloqueos, aumentar el uso del log y hacer esperar a otras sesiones. Las validaciones que no necesitan protegerse dentro de la misma unidad deberían realizarse antes; la interacción humana nunca debe mantener abierta una transacción de producción.

Qué conviene comprobar

Una transacción garantiza atomicidad de las operaciones incluidas, pero no valida la lógica del negocio. Todavía necesitamos filtros correctos, restricciones, manejo de concurrencia y comprobación de filas afectadas.

También revisamos el nivel de aislamiento y los posibles bloqueos cuando varias sesiones modifican los mismos datos. Ampliar una transacción para sentir mayor seguridad puede producir el efecto contrario si aumenta innecesariamente la contención.

Relacionado con

  • INSERT, UPDATE y DELETE: forman la unidad que queremos confirmar o revertir.
  • OUTPUT: permite registrar las filas afectadas dentro de la transacción.
  • Bloqueos: una transacción abierta puede conservarlos hasta finalizar.
  • THROW: comunica el error original al consumidor del procedimiento.