Database
 sql >> Base de Dados >  >> RDS >> Database

Tutorial de transações SQL


No SQL, as transações são usadas para manter a integridade dos dados, garantindo que uma sequência de instruções SQL seja executada completamente ou não seja executada.

As transações gerenciam sequências de instruções SQL que devem ser executadas como uma única unidade de trabalho, para que o banco de dados nunca contenha os resultados de operações parciais.

Quando uma transação faz várias alterações no banco de dados, todas as alterações são bem-sucedidas quando a transação é confirmada ou todas as alterações são desfeitas quando a transação é revertida.

Quando usar uma transação?


As transações são fundamentais em situações em que a integridade dos dados estaria em risco no caso de qualquer uma de uma sequência de instruções SQL falhar.

Por exemplo, se você estivesse transferindo dinheiro de uma conta bancária para outra, precisaria deduzir dinheiro de uma conta e adicioná-lo à outra. Você não gostaria que ele falhasse no meio do caminho, caso contrário, o dinheiro poderia ser debitado de uma conta, mas não creditado na outra.

As possíveis razões para a falha podem incluir fundos insuficientes, número de conta inválido, falha de hardware, etc.

Então você não quero estar em uma situação em que permaneça assim:
Debit account 1 (Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

Isso seria realmente ruim. O banco de dados teria dados inconsistentes e o dinheiro desapareceria no ar. Então o banco perderia um cliente (o banco provavelmente perderia todos os seus clientes se isso continuasse acontecendo) e você perderia seu emprego.

Para salvar seu trabalho, você pode usar uma transação que seria algo assim:
START TRANSACTION
Debit account 1
Credit account 2
Record transaction in transaction journal
END TRANSACTION 

Você pode escrever lógica condicional dentro dessa transação que reverte a transação se algo der errado.

Por exemplo, se algo der errado entre o débito da conta 1 e o crédito da conta 2, toda a transação será revertida.

Portanto, haveria apenas dois resultados possíveis:
Debit account 1 (Not Done)
Credit account 2 (Not Done)
Record transaction in transaction journal (Not Done)

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

Esta é uma representação simplificada, mas é uma ilustração clássica de como as transações SQL funcionam. As transações SQL têm ACID.

Tipos de transação


As transações SQL podem ser executadas nos seguintes modos.
Modo de transação Descrição
Transação de confirmação automática Cada extrato individual é uma transação.
Transação implícita Uma nova transação é iniciada implicitamente quando a transação anterior é concluída, mas cada transação é explicitamente concluída, normalmente com um COMMIT ou ROLLBACK declaração dependendo do DBMS.
Transação explícita Iniciado explicitamente com uma linha como START TRANSACTION , BEGIN TRANSACTION ou similar, dependendo do DBMS, e explicitamente confirmado ou revertido com as instruções relevantes.
Transação com escopo de lote Aplicável apenas a vários conjuntos de resultados ativos (MARS). Uma transação explícita ou implícita que começa em uma sessão MARS se torna uma transação com escopo de lote.

Os modos e opções exatos disponíveis podem depender do DBMS. Esta tabela descreve os modos de transação disponíveis no SQL Server.

Neste artigo, estamos focados principalmente em transações explícitas.

Consulte Como funcionam as transações implícitas no SQL Server para obter uma discussão sobre a diferença entre transações implícitas e confirmação automática.

Sytnax


A tabela a seguir descreve a sintaxe básica para iniciar e encerrar uma transação explícita em alguns dos DBMSs mais populares.
SGBD Sintaxe de transação explícita
MySQL, MariaDB, PostgreSQL As transações explícitas começam com o START TRANSACTION ou BEGIN demonstração. COMMIT confirma a transação atual, tornando suas alterações permanentes. ROLLBACK reverte a transação atual, cancelando suas alterações.
SQLite As transações explícitas começam com BEGIN TRANSACTION e termine com o COMMIT ou ROLLBACK demonstração. Também pode terminar com o END declaração.
SQL Server As transações explícitas começam com BEGIN TRANSACTION e termine com o COMMIT ou ROLLBACK declaração.
Oráculo As transações explícitas começam com o SET TRANSACTION e termine com o COMMIT ou ROLLBACK declaração.

Em muitos casos, certas palavras-chave são opcionais ao usar transações explícitas. Por exemplo, no SQL Server e SQLite, você pode simplesmente usar BEGIN (em vez de BEGIN TRANSACTION ) e/ou você pode terminar com COMMIT TRANSACTION (em oposição a apenas COMMIT ).

Existem também várias outras palavras-chave e opções que você pode especificar ao criar uma transação, portanto, consulte a documentação do seu DBMS para obter a sintaxe completa.

Exemplo de transação SQL


Aqui está um exemplo de uma transação simples no SQL Server:
BEGIN TRANSACTION
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION;

Nesse caso, as informações do pedido são excluídas de duas tabelas. Ambas as declarações são tratadas como uma unidade de trabalho.

Poderíamos escrever lógica condicional em nossa transação para reverter em caso de erro.

Nomeando uma transação


Alguns DBMSs permitem que você forneça um nome para suas transações. No SQL Server, você pode adicionar o nome escolhido após o BEGIN e COMMIT declarações.
BEGIN TRANSACTION MyTransaction
    DELETE OrderItems WHERE OrderId = 5006;
    DELETE Orders WHERE OrderId = 5006;
COMMIT TRANSACTION MyTransaction;

Exemplo de reversão de transação SQL 1


Aqui está o exemplo anterior novamente, mas com algum código extra. O código extra é usado para reverter a transação em caso de erro.:
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

O TRY...CATCH A instrução implementa o tratamento de erros no SQL Server. Você pode incluir qualquer grupo de instruções T-SQL em um TRY quadra. Então, se ocorrer um erro no TRY bloco, o controle é passado para outro grupo de instruções que está dentro de um CATCH quadra.

Neste caso, usamos o CATCH bloco para reverter a transação. Dado que está no CATCH block, a reversão só ocorre se houver um erro.

Exemplo de reversão de transação SQL 2


Vamos dar uma olhada mais de perto no banco de dados do qual acabamos de excluir as linhas.

No exemplo anterior, excluímos linhas de Orders e OrderItems tabelas no seguinte banco de dados:

Nesse banco de dados, cada vez que um cliente faz um pedido, uma linha é inserida no campo Orders tabela e uma ou mais linhas no OrderItems tabela. O número de linhas inseridas em OrderItems depende de quantos produtos diferentes o cliente pede.

Além disso, se for um novo cliente, uma nova linha será inserida em Customers tabela.

Nesse caso, as linhas precisam ser inseridas em três tabelas.

Em caso de falha, não gostaríamos de ter uma linha inserida em Orders mas nenhuma linha correspondente no OrderItems tabela. Isso resultaria em um pedido sem nenhum item do pedido. Basicamente, queremos que ambas as tabelas sejam completamente atualizadas ou nada.

Foi o mesmo quando excluímos as linhas. Queríamos que todas as linhas fossem excluídas ou nenhuma.

No SQL Server, poderíamos escrever a seguinte transação para o INSERT declarações.
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 exemplo pressupõe que há lógica em outro lugar que determina se o cliente já existe ou não no banco de dados.

O cliente poderia ter sido inserido fora desta transação:

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

Se a transação falhar, o cliente ainda estará no banco de dados (mas sem nenhum pedido). O aplicativo precisaria verificar se o cliente já existe antes de fazer a transação.

Transação SQL com Savepoints


Um ponto de salvamento define um local para o qual uma transação pode retornar se parte da transação for condicionalmente cancelada. No SQL Server, especificamos um ponto de salvamento com SAVE TRANSACTION savepoint_name (onde savepoint_name é o nome que damos ao savepoint).

Vamos reescrever o exemplo anterior para incluir um ponto de salvamento:

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;

Aqui, definimos um ponto de salvamento logo após o cliente INSERT demonstração. Mais tarde na transação, eu uso o ROLLBACK instrução para instruir a transação a reverter para esse ponto de salvamento.

Quando executo essa instrução, o cliente é inserido, mas nenhuma das informações do pedido é inserida.

Se uma transação for revertida para um ponto de salvamento, ela deve prosseguir até a conclusão com mais instruções SQL, se necessário, e um COMMIT TRANSACTION ou deve ser cancelado completamente revertendo toda a transação.

Se eu mover o ROLLBACK declaração de volta ao INSERT anterior declaração, assim:
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;

Isso produz um erro de conflito de chave estrangeira. Especificamente, recebo o seguinte erro:
(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)

Isso ocorreu porque, mesmo com o pedido já inserido, essa operação foi desfeita quando retornamos ao ponto de salvamento. Em seguida, a transação prosseguiu para a conclusão. Mas quando ele encontrou o item do pedido final, não havia pedido correspondente (porque ele havia sido desfeito) e recebemos o erro.

Quando eu verifico o banco de dados, o cliente foi inserido, mas novamente, nenhuma das informações do pedido foi inserida.

Você pode fazer referência ao mesmo ponto de salvamento de vários locais na transação, se necessário.

Na prática, você usaria programação condicional para retornar a transação a um savepont.

Transações aninhadas


Você também pode aninhar transações dentro de outras transações, se necessário.

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

Como mencionado, a sintaxe exata que você usa para criar uma transação dependerá do seu DBMS, portanto, verifique a documentação do seu DBMS para obter uma visão completa de suas opções ao criar transações em SQL.