Database
 sql >> Database >  >> RDS >> Database

Esercitazione sulle transazioni SQL

In SQL, le transazioni vengono utilizzate per mantenere l'integrità dei dati assicurando che una sequenza di istruzioni SQL venga eseguita completamente o non venga eseguita affatto.

Le transazioni gestiscono sequenze di istruzioni SQL che devono essere eseguite come una singola unità di lavoro, in modo che il database non contenga mai i risultati di operazioni parziali.

Quando una transazione apporta più modifiche al database, tutte le modifiche hanno esito positivo quando viene eseguito il commit della transazione oppure tutte le modifiche vengono annullate quando viene eseguito il rollback della transazione.

Quando utilizzare una transazione?

Le transazioni sono fondamentali in situazioni in cui l'integrità dei dati sarebbe a rischio nel caso in cui una qualsiasi di una sequenza di istruzioni SQL dovesse fallire.

Ad esempio, se stai spostando denaro da un conto bancario a un altro, dovresti detrarre denaro da un conto e aggiungerlo all'altro. Non vorresti che fallisse a metà, altrimenti, il denaro potrebbe essere addebitato da un conto ma non accreditato sull'altro.

I possibili motivi del fallimento potrebbero includere fondi insufficienti, numero di conto non valido, guasto hardware, ecc.

Quindi non voglio essere in una situazione in cui rimane così:

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

Sarebbe davvero cattivo. Il database avrebbe dati incoerenti e il denaro scomparirebbe nel nulla. Quindi la banca perderebbe un cliente (la banca perderebbe probabilmente tutti i suoi clienti se ciò continuasse) e tu perderesti il ​​lavoro.

Per salvare il tuo lavoro, potresti utilizzare una transazione che sarebbe simile a questa:

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

Potresti scrivere una logica condizionale all'interno di quella transazione che ripristina la transazione se qualcosa va storto.

Ad esempio, se qualcosa va storto tra l'addebito sul conto 1 e l'accredito sul conto 2, l'intera transazione viene annullata.

Pertanto, ci sarebbero solo due possibili esiti:

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

Oppure:

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

Questa è una rappresentazione semplificata, ma è una classica illustrazione di come funzionano le transazioni SQL. Le transazioni SQL hanno ACID.

Tipi di transazione

Le transazioni SQL possono essere eseguite nelle seguenti modalità.

Modalità transazione Descrizione
Autocommit transazione Ogni singolo estratto conto è una transazione.
Transazione implicita Una nuova transazione viene avviata implicitamente al completamento della transazione precedente, ma ogni transazione viene completata in modo esplicito, in genere con un COMMIT o ROLLBACK istruzione a seconda del DBMS.
Transazione esplicita Iniziato esplicitamente con una riga come START TRANSACTION , BEGIN TRANSACTION o simili, a seconda del DBMS, e esplicitamente impegnati o annullati con le dichiarazioni pertinenti.
Transazione con ambito batch Applicabile solo a più set di risultati attivi (MARS). Una transazione esplicita o implicita che inizia in una sessione MARS diventa una transazione con ambito batch.

Le modalità e le opzioni esatte disponibili possono dipendere dal DBMS. Questa tabella illustra le modalità di transazione disponibili in SQL Server.

In questo articolo, ci concentriamo principalmente sulle transazioni esplicite.

Vedere Come funzionano le transazioni implicite in SQL Server per una discussione sulla differenza tra transazioni implicite e autocommit.

Sytnax

La tabella seguente delinea la sintassi di base per avviare e terminare una transazione esplicita in alcuni dei DBMS più diffusi.

DBMS Sintassi della transazione esplicita
MySQL, MariaDB, PostgreSQL Le transazioni esplicite iniziano con START TRANSACTION o BEGIN dichiarazione. COMMIT esegue il commit della transazione in corso, rendendo permanenti le sue modifiche. ROLLBACK annulla la transazione corrente, annullandone le modifiche.
SQLite Le transazioni esplicite iniziano con BEGIN TRANSACTION istruzione e terminare con il COMMIT o ROLLBACK dichiarazione. Può anche terminare con END dichiarazione.
SQL Server Le transazioni esplicite iniziano con BEGIN TRANSACTION istruzione e terminare con il COMMIT o ROLLBACK dichiarazione.
Oracolo Le transazioni esplicite iniziano con SET TRANSACTION istruzione e terminare con il COMMIT o ROLLBACK dichiarazione.

In molti casi, alcune parole chiave sono facoltative quando si utilizzano transazioni esplicite. Ad esempio in SQL Server e SQLite, puoi semplicemente usare BEGIN (anziché BEGIN TRANSACTION ) e/o potresti terminare con COMMIT TRANSACTION (al contrario del solo COMMIT ).

Ci sono anche varie altre parole chiave e opzioni che puoi specificare durante la creazione di una transazione, quindi consulta la documentazione del tuo DBMS per la sintassi completa.

Esempio di transazione SQL

Ecco un esempio di una semplice transazione in SQL Server:

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

In questo caso, le informazioni sull'ordine vengono eliminate da due tabelle. Entrambe le affermazioni sono trattate come un'unità di lavoro.

Potremmo scrivere la logica condizionale nella nostra transazione per eseguirne il rollback in caso di errore.

Denominare una transazione

Alcuni DBMS ti consentono di fornire un nome per le tue transazioni. In SQL Server puoi aggiungere il nome che hai scelto dopo BEGIN e COMMIT dichiarazioni.

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

Esempio di rollback della transazione SQL 1

Ecco di nuovo l'esempio precedente, ma con del codice aggiuntivo. Il codice aggiuntivo viene utilizzato per eseguire il rollback della transazione in caso di errore.:

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

Il TRY...CATCH istruzione implementa la gestione degli errori in SQL Server. Puoi racchiudere qualsiasi gruppo di istruzioni T-SQL in un TRY bloccare. Quindi, se si verifica un errore in TRY blocco, il controllo viene passato a un altro gruppo di istruzioni racchiuso in un CATCH blocco.

In questo caso, utilizziamo il CATCH blocco per eseguire il rollback della transazione. Dato che è nel CATCH blocco, il rollback si verifica solo in caso di errore.

Esempio 2 di rollback della transazione SQL

Diamo un'occhiata più da vicino al database da cui abbiamo appena eliminato le righe.

Nell'esempio precedente, abbiamo eliminato le righe da Orders e OrderItems tabelle nel seguente database:

In questo database, ogni volta che un cliente effettua un ordine, viene inserita una riga in Orders tabella e una o più righe in OrderItems tavolo. Il numero di righe inserite in OrderItems dipende da quanti prodotti diversi ordina il cliente.

Inoltre, se si tratta di un nuovo cliente, viene inserita una nuova riga in Customers tabella.

In tal caso, le righe devono essere inserite in tre tabelle.

In caso di errore, non vorremmo avere una riga inserita negli Orders tabella ma nessuna riga corrispondente in OrderItems tavolo. Ciò risulterebbe in un ordine senza articoli dell'ordine. Fondamentalmente, vogliamo che entrambe le tabelle siano completamente aggiornate o niente affatto.

È stato lo stesso quando abbiamo eliminato le righe. Volevamo che tutte le righe fossero eliminate o nessuna.

In SQL Server, potremmo scrivere la seguente transazione per INSERT dichiarazioni.

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

Questo esempio presuppone che esista una logica altrove che determina se il cliente esiste già o meno nel database.

Il cliente potrebbe essere stato inserito al di fuori di questa transazione:


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 la transazione non è andata a buon fine, il cliente sarebbe ancora nel database (ma senza alcun ordine). L'applicazione dovrebbe verificare se il cliente esiste già prima di effettuare la transazione.

Transazione SQL con punti di salvataggio

Un punto di salvataggio definisce una posizione in cui una transazione può tornare se una parte della transazione viene annullata in modo condizionale. In SQL Server, specifichiamo un punto di salvataggio con SAVE TRANSACTION savepoint_name (dove nome_punto_di_salvataggio è il nome che diamo al punto di salvataggio).

Riscriviamo l'esempio precedente per includere un punto di salvataggio:


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;

Qui abbiamo impostato un punto di salvataggio subito dopo il cliente INSERT dichiarazione. Più avanti nella transazione, utilizzo il ROLLBACK per indicare alla transazione di tornare a quel punto di salvataggio.

Quando eseguo tale istruzione, il cliente viene inserito, ma nessuna delle informazioni sull'ordine viene inserita.

Se una transazione viene ripristinata a un punto di salvataggio, deve procedere al completamento con più istruzioni SQL se necessario e un COMMIT TRANSACTION o deve essere annullato del tutto annullando l'intera transazione.

Se sposto il ROLLBACK istruzione torna al precedente INSERT dichiarazione, in questo modo:

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;

Ciò produce un errore di conflitto di chiave esterna. In particolare, ottengo il seguente errore:

(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)

Ciò si è verificato perché, anche se l'ordine era già stato inserito, l'operazione è stata annullata quando siamo tornati al punto di salvataggio. Quindi la transazione è proseguita fino al completamento. Ma quando ha incontrato l'articolo dell'ordine finale, non c'era un ordine corrispondente (perché era stato annullato) e abbiamo ricevuto l'errore.

Quando controllo il database, il cliente è stato inserito, ma ancora una volta nessuna delle informazioni sull'ordine è stata inserita.

Se necessario, puoi fare riferimento allo stesso punto di salvataggio da più punti della transazione.

In pratica, utilizzeresti la programmazione condizionale per restituire la transazione a un savepont.

Transazioni nidificate

Puoi anche annidare le transazioni all'interno di altre transazioni, se necessario.

In questo modo:

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

Come accennato, l'esatta sintassi che utilizzi per creare una transazione dipenderà dal tuo DBMS, quindi controlla la documentazione del tuo DBMS per un quadro completo delle tue opzioni durante la creazione di transazioni in SQL.