sql >> Base de Datos >  >> RDS >> Database

Tutorial de transacciones SQL

En SQL, las transacciones se utilizan para mantener la integridad de los datos al garantizar que una secuencia de instrucciones SQL se ejecute por completo o no se ejecute en absoluto.

Las transacciones gestionan secuencias de sentencias SQL que deben ejecutarse como una sola unidad de trabajo, de modo que la base de datos nunca contenga los resultados de operaciones parciales.

Cuando una transacción realiza varios cambios en la base de datos, todos los cambios se realizan correctamente cuando se confirma la transacción o todos los cambios se deshacen cuando se revierte la transacción.

¿Cuándo usar una transacción?

Las transacciones son primordiales en situaciones en las que la integridad de los datos estaría en riesgo en caso de que fallara cualquiera de una secuencia de instrucciones SQL.

Por ejemplo, si estuviera moviendo dinero de una cuenta bancaria a otra, necesitaría deducir dinero de una cuenta y agregarlo a la otra. No querrá que falle a la mitad, de lo contrario, el dinero podría debitarse de una cuenta pero no acreditarse en la otra.

Las posibles razones de la falla podrían incluir fondos insuficientes, número de cuenta no válido, falla de hardware, etc.

Así que no quiero estar en una situación en la que permanezca así:

Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

Eso sería realmente malo. La base de datos tendría datos inconsistentes y el dinero desaparecería en el aire. Entonces el banco perdería un cliente (el banco probablemente perdería a todos sus clientes si esto siguiera sucediendo) y usted perdería su trabajo.

Para guardar su trabajo, podría usar una transacción que sería algo como esto:

START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION 

Podría escribir lógica condicional dentro de esa transacción que revierta la transacción si algo sale mal.

Por ejemplo, si algo sale mal entre el débito de la cuenta 1 y el crédito de la cuenta 2, se revierte toda la transacción.

Por lo tanto, solo habría dos resultados posibles:

Debit account 1 (Not Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

O:

Debit account 1 (Done)
Credit account 2 (Done)
Record transaction in transaction journal (Done)

Esta es una descripción simplificada, pero es una ilustración clásica de cómo funcionan las transacciones SQL. Las transacciones SQL tienen ACID.

Tipos de transacciones

Las transacciones SQL se pueden ejecutar en los siguientes modos.

Modo de transacción Descripción
Transacción de confirmación automática Cada declaración individual es una transacción.
Transacción implícita Una nueva transacción se inicia implícitamente cuando se completa la transacción anterior, pero cada transacción se completa explícitamente, normalmente con un COMMIT o ROLLBACK declaración dependiendo del DBMS.
Transacción explícita Comenzó explícitamente con una línea como START TRANSACTION , BEGIN TRANSACTION o similar, según el DBMS, y confirmado o revertido explícitamente con las declaraciones relevantes.
Transacción por lotes Aplicable solo a múltiples conjuntos de resultados activos (MARS). Una transacción explícita o implícita que se inicia en una sesión de MARS se convierte en una transacción por lotes.

Los modos exactos y las opciones disponibles pueden depender del DBMS. Esta tabla describe los modos de transacción disponibles en SQL Server.

En este artículo, nos centramos principalmente en las transacciones explícitas.

Consulte Cómo funcionan las transacciones implícitas en SQL Server para ver una discusión sobre la diferencia entre las transacciones implícitas y la confirmación automática.

Sytnax

La siguiente tabla describe la sintaxis básica para iniciar y finalizar una transacción explícita en algunos de los DBMS más populares.

DBMS Sintaxis de transacciones explícitas
MySQL, MariaDB, PostgreSQL Las transacciones explícitas comienzan con START TRANSACTION o BEGIN declaración. COMMIT confirma la transacción actual, haciendo que sus cambios sean permanentes. ROLLBACK revierte la transacción actual, cancelando sus cambios.
SQLite Las transacciones explícitas comienzan con BEGIN TRANSACTION declaración y terminar con el COMMIT o ROLLBACK declaración. También puede terminar con END declaración.
Servidor SQL Las transacciones explícitas comienzan con BEGIN TRANSACTION declaración y terminar con el COMMIT o ROLLBACK declaración.
Oráculo Las transacciones explícitas comienzan con SET TRANSACTION declaración y terminar con el COMMIT o ROLLBACK declaración.

En muchos casos, ciertas palabras clave son opcionales cuando se usan transacciones explícitas. Por ejemplo, en SQL Server y SQLite, simplemente podría usar BEGIN (en lugar de BEGIN TRANSACTION ) y/o podría terminar con COMMIT TRANSACTION (a diferencia de simplemente COMMIT ).

También hay varias otras palabras clave y opciones que puede especificar al crear una transacción, así que consulte la documentación de su DBMS para conocer la sintaxis completa.

Ejemplo de transacción SQL

Aquí hay un ejemplo de una transacción simple en SQL Server:

BEGIN TRANSACTION
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;

En este caso, la información del pedido se elimina de dos tablas. Ambas declaraciones se tratan como una unidad de trabajo.

Podríamos escribir lógica condicional en nuestra transacción para hacerla retroceder en caso de error.

Nombrar una transacción

Algunos DBMS le permiten proporcionar un nombre para sus transacciones. En SQL Server, puede agregar el nombre elegido después de BEGIN y COMMIT declaraciones.

BEGIN TRANSACTION MyTransaction
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;

Ejemplo 1 de reversión de transacciones SQL

Aquí está el ejemplo anterior de nuevo, pero con algo de código extra. El código adicional se utiliza para deshacer la transacción en caso de error.:

BEGIN TRANSACTION MyTransaction

  BEGIN TRY

    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;

    COMMIT TRANSACTION MyTransaction

  END TRY

  BEGIN CATCH

      ROLLBACK TRANSACTION MyTransaction

  END CATCH

El TRY...CATCH La declaración implementa el manejo de errores en SQL Server. Puede encerrar cualquier grupo de declaraciones T-SQL en un TRY cuadra. Luego, si ocurre un error en el TRY bloque, el control se pasa a otro grupo de sentencias que está encerrado en un CATCH bloquear.

En este caso, usamos el CATCH bloquear para deshacer la transacción. Dado que está en el CATCH bloqueo, la reversión solo ocurre si hay un error.

Ejemplo 2 de reversión de transacciones SQL

Echemos un vistazo más de cerca a la base de datos de la que acabamos de eliminar filas.

En el ejemplo anterior, eliminamos filas de Orders y OrderItems tablas en la siguiente base de datos:

En esta base de datos, cada vez que un cliente realiza un pedido, se inserta una fila en Orders tabla y una o más filas en OrderItems mesa. El número de filas insertadas en OrderItems depende de cuántos productos diferentes pida el cliente.

Además, si se trata de un nuevo cliente, se inserta una nueva fila en Customers mesa.

En ese caso, las filas deben insertarse en tres tablas.

En caso de falla, no nos gustaría tener una fila insertada en Orders tabla pero sin filas correspondientes en OrderItems mesa. Eso daría como resultado un pedido sin ningún artículo de pedido. Básicamente, queremos que ambas tablas estén completamente actualizadas o nada en absoluto.

Fue lo mismo cuando eliminamos las filas. Queríamos eliminar todas las filas o ninguna.

En SQL Server, podríamos escribir la siguiente transacción para INSERT declaraciones.

BEGIN TRANSACTION
    BEGIN TRY 
        INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
        VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');

        INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
        VALUES ( 5006, SYSDATETIME(), 1006 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 1, 1, 20, 25.99 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 2, 7, 120, 9.99 );

        COMMIT TRANSACTION;
        
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
    END CATCH

Este ejemplo asume que hay una lógica en otro lugar que determina si el cliente ya existe o no en la base de datos.

El cliente podría haber sido insertado fuera de esta transacción:


INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');

BEGIN TRANSACTION
    BEGIN TRY 

        INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
        VALUES ( 5006, SYSDATETIME(), 1006 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 1, 1, 20, 25.99 );
        
        INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
        VALUES ( 5006, 2, 7, 120, 9.99 );

        COMMIT TRANSACTION;
        
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
    END CATCH

Si la transacción falla, el cliente aún estaría en la base de datos (pero sin ningún pedido). La aplicación necesitaría verificar si el cliente ya existe antes de realizar la transacción.

Transacción SQL con puntos de guardado

Un punto de guardado define una ubicación a la que una transacción puede regresar si parte de la transacción se cancela condicionalmente. En SQL Server, especificamos un punto de guardado con SAVE TRANSACTION savepoint_name (donde nombre_guardarpunto es el nombre que le damos al savepoint).

Reescribamos el ejemplo anterior para incluir un punto de guardado:


BEGIN TRANSACTION
    INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
    VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
    SAVE TRANSACTION StartOrder;

    INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
    VALUES ( 5006, SYSDATETIME(), 1006 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 1, 1, 20, 25.99 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 2, 7, 120, 9.99 );
    ROLLBACK TRANSACTION StartOrder;
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;

Aquí, hemos establecido un punto de guardado justo después del cliente INSERT declaración. Más adelante en la transacción, uso el ROLLBACK instrucción para indicar a la transacción que retroceda a ese punto de guardado.

Cuando ejecuto esa declaración, se inserta el cliente, pero no se inserta ninguna información del pedido.

Si una transacción se retrotrae a un punto de guardado, debe continuar hasta completarse con más sentencias SQL si es necesario y una COMMIT TRANSACTION estado de cuenta, o debe cancelarse por completo revirtiendo toda la transacción.

Si muevo el ROLLBACK declaración de vuelta al anterior INSERT declaración, así:

BEGIN TRANSACTION
    INSERT INTO Customers ( CustomerId, CustomerName, PostalAddress, City, StateProvince, ZipCode, Country, Phone )
    VALUES (1006, 'Hi-Five Solutionists', '5 High Street', 'Highlands', 'HI', '1254', 'AUS', '(415) 413-5182');
    SAVE TRANSACTION StartOrder;

    INSERT INTO Orders ( OrderId, OrderDate, CustomerId )
    VALUES ( 5006, SYSDATETIME(), 1006 );
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 1, 1, 20, 25.99 );
    ROLLBACK TRANSACTION StartOrder;
    
    INSERT INTO OrderItems ( OrderId, OrderItemId, ProductId, Quantity, ItemPrice )
    VALUES ( 5006, 2, 7, 120, 9.99 );
COMMIT TRANSACTION;
SELECT @@TRANCOUNT;

Esto produce un error de conflicto de clave externa. Específicamente, recibo el siguiente error:

(1 row affected)
(1 row affected)
(1 row affected)
Msg 547, Level 16, State 0, Line 13
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_OrderItems_Orders". The conflict occurred in database "KrankyKranes", table "dbo.Orders", column 'OrderId'.
The statement has been terminated.
(1 row affected)

Esto ocurrió porque, aunque el pedido ya se había insertado, esa operación se deshizo cuando retrocedimos al punto de guardado. Entonces la transacción procedió a su finalización. Pero cuando encontró el artículo del pedido final, no había ningún pedido correspondiente (porque se había deshecho) y obtuvimos el error.

Cuando verifiqué la base de datos, se insertó el cliente, pero nuevamente, no se insertó ninguna información del pedido.

Puede hacer referencia al mismo punto de guardado desde varios lugares en la transacción si es necesario.

En la práctica, usaría la programación condicional para devolver la transacción a un punto de guardado.

Transacciones anidadas

También puede anidar transacciones dentro de otras transacciones si es necesario.

Así:

BEGIN TRANSACTION Transaction1;  
    UPDATE table1 ...;
    BEGIN TRANSACTION Transaction2;
        UPDATE table2 ...;
        SELECT * from table1;
    COMMIT TRANSACTION Transaction2;
    UPDATE table3 ...;
COMMIT TRANSACTION Transaction1;

Como se mencionó, la sintaxis exacta que utilice para crear una transacción dependerá de su DBMS, así que consulte la documentación de su DBMS para obtener una imagen completa de sus opciones al crear transacciones en SQL.