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.