Devo eseguire il commit o il rollback di una transazione di lettura?


95

Ho una query di lettura che eseguo all'interno di una transazione in modo da poter specificare il livello di isolamento. Una volta completata la query, cosa devo fare?

  • Effettua il commit della transazione
  • Ripristina la transazione
  • Non fare nulla (ciò causerà il rollback della transazione alla fine del blocco using)

Quali sono le implicazioni di fare ciascuno?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDIT: La domanda non è se una transazione deve essere utilizzata o se ci sono altri modi per impostare il livello di transazione. La domanda è se fa qualche differenza che una transazione che non modifica nulla venga salvata o annullata. C'è una differenza di prestazioni? Influisce su altre connessioni? Altre differenze?


1
Probabilmente lo sai già, ma dato l'esempio che hai fornito, potresti ottenere risultati equivalenti eseguendo semplicemente la query: SELECT * FROM SomeTable with NOLOCK
JasonTrue

@Stefan, sembra che la maggior parte di noi si chieda perché ti stai preoccupando di effettuare transazioni su un'operazione di sola lettura. Puoi farci sapere se conosci NOLOCK e se lo fai, perché non hai seguito quella strada.
StingyJack

Conosco NOLOCK, ma questo sistema funziona su database diversi oltre a SQL Server, quindi sto cercando di evitare suggerimenti di blocco specifici di SQL Server. Questa è una domanda più per curiosità che altro perché l'applicazione funziona bene con il codice sopra.
Stefan Moser

Ah, in quel caso rimuovo il tag sqlserver, perché questo indica MSSqlServer come prodotto di destinazione.
StingyJack

@ StingyJack - Hai ragione, non avrei dovuto usare il tag sqlserver.
Stefan Moser

Risposte:


51

Ti impegni. Periodo. Non ci sono altre alternative sensate. Se hai avviato una transazione, dovresti chiuderla. Il commit rilascia eventuali blocchi che potresti aver avuto ed è altrettanto sensato con i livelli di isolamento ReadUncommitted o Serializable. Affidarsi al rollback implicito, sebbene forse tecnicamente equivalente, è solo una forma scadente.

Se questo non ti ha convinto, immagina il prossimo ragazzo che inserisce un'istruzione di aggiornamento nel mezzo del tuo codice e deve rintracciare il rollback implicito che si verifica e rimuove i suoi dati.


44
C'è un'alternativa sensata: il rollback. Rollback esplicito, cioè. Se non intendevi modificare nulla, il rollback garantisce che qualsiasi cosa sia annullata. Ovviamente non avrebbero dovuto esserci cambiamenti; rollback lo garantisce.
Jonathan Leffler

2
DBMS diversi possono avere semantica di "completamento della transazione implicita" diversa. IBM Informix (e credo DB2) esegue un rollback implicito; da voci, Oracle fa un commit implicito. Preferisco il rollback implicito.
Jonathan Leffler

8
Supponiamo che crei una tabella temporanea, la popolo con ID, la unisca a una tabella dati per selezionare i dati che vanno con gli ID, quindi elimini la tabella temporanea. Sto solo leggendo i dati e non mi interessa cosa succede alla tabella temporanea, poiché è temporanea ... ma dal punto di vista delle prestazioni, sarebbe più costoso eseguire il rollback della transazione o eseguirne il commit? Qual è l'effetto di un commit / rollback quando sono coinvolte solo tabelle temporanee e operazioni di lettura?
Triynko

4
@ Triynko - Intuitivamente, immagino che ROLLBACK sia più costoso. COMMIT è il normale caso d'uso e ROLLBACK il caso eccezionale. Ma, a parte accademicamente, chi se ne frega? Sono sicuro che ci sono 1000 punti di ottimizzazione migliori per la tua app. Se sei davvero curioso, puoi trovare il codice di gestione delle transazioni mySQL su bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett

3
@Triynko - L' unico modo per ottimizzare è profilare. È una modifica del codice così semplice, non c'è motivo di non profilare entrambi i metodi se vuoi davvero ottimizzarlo. Assicurati di aggiornarci con i risultati!
Mark Brackett

28

Se non hai cambiato nulla, puoi utilizzare un COMMIT o un ROLLBACK. O uno rilascerà qualsiasi blocco di lettura che hai acquisito e poiché non hai apportato altre modifiche, saranno equivalenti.


2
Grazie per avermi fatto sapere che sono equivalenti. A mio parere, questo risponde meglio alla domanda reale.
Chowey

darebbe la transazione è inattiva se usiamo commit senza aggiornamenti effettivi. L'ho appena affrontato sul mio sito live
Muhammad Omer Aslam

6

Se inizi una transazione, la migliore pratica è sempre quella di impegnarla. Se viene generata un'eccezione all'interno del blocco di utilizzo (transazione), la transazione verrà automaticamente annullata.


3

IMHO può avere senso racchiudere query di sola lettura nelle transazioni poiché (specialmente in Java) puoi dire alla transazione di essere "di sola lettura" che a sua volta il driver JDBC può considerare di ottimizzare la query (ma non deve farlo, quindi nessuno ti impedirà INSERTcomunque di emettere un messaggio ). Ad esempio, il driver Oracle eviterà completamente i blocchi della tabella sulle query in una transazione contrassegnata come di sola lettura, il che consente di ottenere molte prestazioni su applicazioni fortemente guidate dalla lettura.


3

Considera le transazioni nidificate .

La maggior parte degli RDBMS non supporta le transazioni annidate o cerca di emularle in modo molto limitato.

Ad esempio, in MS SQL Server, un rollback in una transazione interna (che non è una transazione reale, MS SQL Server conta solo i livelli di transazione!) Eseguirà il rollback di tutto ciò che è accaduto nell'estremo transazione più (che è la transazione reale).

Alcuni wrapper di database potrebbero considerare un rollback in una transazione interna come un segno che si è verificato un errore e ripristinare tutto nella transazione più esterna, indipendentemente dal fatto che la transazione più esterna sia stata sottoposta a commit o rollback.

Quindi un COMMIT è il modo sicuro, quando non puoi escludere che il tuo componente sia utilizzato da qualche modulo software.

Si prega di notare che questa è una risposta generale alla domanda. L'esempio di codice aggira abilmente il problema con una transazione esterna aprendo una nuova connessione al database.

Per quanto riguarda le prestazioni: a seconda del livello di isolamento, SELECT può richiedere un grado variabile di LOCK e dati temporanei (snapshot). Questo viene ripulito quando la transazione viene chiusa. Non importa se questo viene fatto tramite COMMIT o ROLLBACK. Potrebbe esserci una differenza insignificante nel tempo impiegato dalla CPU: un COMMIT è probabilmente più veloce da analizzare rispetto a un ROLLBACK (due caratteri in meno) e altre differenze minori. Ovviamente, questo è vero solo per le operazioni di sola lettura!

Totalmente non richiesto: un altro programmatore che potrebbe leggere il codice potrebbe presumere che un ROLLBACK implichi una condizione di errore.


2

Solo una nota a margine, ma puoi anche scrivere quel codice in questo modo:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

E se ri-strutturi le cose solo un po 'potresti essere in grado di spostare anche il blocco using per IDataReader in alto.


1

Se inserisci l'SQL in una procedura memorizzata e lo aggiungi sopra la query:

set transaction isolation level read uncommitted

quindi non devi saltare attraverso i cerchi nel codice C #. L'impostazione del livello di isolamento della transazione in una stored procedure non fa sì che l'impostazione venga applicata a tutti gli usi futuri di quella connessione (che è qualcosa di cui ti devi preoccupare con altre impostazioni poiché le connessioni sono in pool). Alla fine della procedura memorizzata, torna a qualunque sia la connessione con cui è stata inizializzata.


1

ROLLBACK viene utilizzato principalmente in caso di errore o circostanze eccezionali e COMMIT in caso di completamento con successo.

Dovremmo chiudere le transazioni con COMMIT (per successo) e ROLLBACK (per fallimento), anche nel caso di transazioni di sola lettura in cui non sembra avere importanza. In effetti è importante, per coerenza e protezione per il futuro.

Una transazione di sola lettura può logicamente "fallire" in molti modi, ad esempio:

  • una query non restituisce esattamente una riga come previsto
  • una stored procedure solleva un'eccezione
  • i dati recuperati risultano incoerenti
  • l'utente interrompe la transazione perché impiega troppo tempo
  • deadlock o timeout

Se COMMIT e ROLLBACK vengono utilizzati correttamente per una transazione di sola lettura, continuerà a funzionare come previsto se a un certo punto viene aggiunto codice di scrittura DB, ad esempio per la memorizzazione nella cache, il controllo o le statistiche.

Il ROLLBACK implicito deve essere utilizzato solo per situazioni di "errore irreversibile", quando l'applicazione si arresta in modo anomalo o si chiude con un errore irreversibile, errore di rete, interruzione di corrente, ecc.


0

Dato che una READ non cambia stato, non farei nulla. L'esecuzione di un commit non farà nulla, tranne sprecare un ciclo per inviare la richiesta al database. Non hai eseguito un'operazione che ha cambiato stato. Allo stesso modo per il rollback.

Tuttavia, assicurati di pulire i tuoi oggetti e chiudere le connessioni al database. Non chiudere le connessioni può causare problemi se questo codice viene chiamato ripetutamente.


3
A seconda del livello di isolamento, un selezionato CAN ottiene blocchi che bloccheranno altre transazioni.
Graeme Perrow,

La connessione verrà chiusa alla fine del blocco using, ecco a cosa serve. Ma è un buon punto che il traffico di rete è probabilmente la parte più lenta dell'equazione.
Joel Coehoorn

1
La transazione verrà salvata o annullata in un modo o nell'altro, quindi la migliore pratica sarebbe quella di emettere sempre un commit se ha avuto successo.
Neil Barnwell,

0

Se imposti AutoCommit false, allora YES.

In un esperimento con JDBC (driver Postgresql), ho scoperto che se la query di selezione si interrompe (a causa del timeout), non è possibile avviare una nuova query di selezione a meno che non venga eseguito il rollback.


-2

Nel tuo esempio di codice, dove hai

  1. // Fai qualcosa di utile

    Stai eseguendo un'istruzione SQL che modifica i dati?

In caso contrario, non esiste una transazione "Lettura" ... Solo le modifiche da un'istruzione di inserimento, aggiornamento ed eliminazione (istruzioni che possono modificare i dati) sono in una transazione ... Ciò di cui stai parlando sono i blocchi che SQL Il server inserisce i dati che stai leggendo, a causa di ALTRE transazioni che influenzano quei dati. Il livello di questi blocchi dipende dal livello di isolamento di SQL Server.

Ma non puoi eseguire il commit o il ROll Back di nulla, se la tua istruzione SQL non ha cambiato nulla.

Se si modificano i dati, è possibile modificare il livello di isolamento senza avviare esplicitamente una transizione ... Ogni singola istruzione SQL è implicitamente in una transazione. l'avvio esplicito di una transazione è necessario solo per garantire che 2 o più dichiarazioni siano all'interno della stessa transazione.

Se tutto ciò che vuoi fare è impostare il livello di isolamento della transazione, imposta CommandText di un comando su "Imposta livello di isolamento della transazione ripetibile" (o qualunque livello tu voglia), imposta CommandType su CommandType.Text ed esegui il comando. (puoi usare Command.ExecuteNonQuery ())

NOTA: se si eseguono istruzioni di lettura MULTIPLE e si desidera che tutte "vedano" lo stesso stato del database della prima, è necessario impostare il livello di isolamento superiore Lettura ripetibile o Serializzabile ...


// Fare qualcosa di utile non cambia alcun dato, basta leggere. Tutto quello che voglio fare è specificare il livello di isolamento della query.
Stefan Moser

Quindi puoi farlo senza avviare esplicitamente una transazione dal client ... Esegui semplicemente la stringa sql "Set Transaction Isolation Level ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" , o "... Serializable" "Set Isolation Level Read Committed"
Charles Bretana

3
Le transazioni contano ancora anche se stai solo leggendo. Se vuoi fare diverse operazioni di lettura, farle all'interno di una transazione garantirà la coerenza. Farli senza uno non lo farà.
MarkR

si scusa, hai ragione, almeno questo è vero se il livello di isolamento è impostato su Lettura ripetibile o superiore.
Charles Bretana

-3

Hai bisogno di impedire ad altri di leggere gli stessi dati? Perché utilizzare una transazione?

@ Joel - La mia domanda sarebbe meglio formulata come "Perché utilizzare una transazione su una query di lettura?"

@Stefan - Se intendi utilizzare AdHoc SQL e non una procedura memorizzata, aggiungi semplicemente WITH (NOLOCK) dopo le tabelle nella query. In questo modo non dovrai sostenere il sovraccarico (anche se minimo) nell'applicazione e nel database per una transazione.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Comment 3: Dato che avevi "sqlserver" nei tag della domanda, avevo supposto che MSSQLServer fosse il prodotto di destinazione. Ora che questo punto è stato chiarito, ho modificato i tag per rimuovere il riferimento specifico del prodotto.

Non sono ancora sicuro del motivo per cui desideri effettuare una transazione su un'operazione di lettura in primo luogo.


1
Al livello di isolamento impostato tutto in una volta. È possibile utilizzare la transazione per ridurre effettivamente la quantità di blocco per la query.
Joel Coehoorn

1
Sto utilizzando la transazione in modo da poter utilizzare un livello di isolamento inferiore e ridurre il blocco.
Stefan Moser

@StingyJack: questo codice può essere eseguito su un numero di database diversi, quindi NOLOCK non è un'opzione.
Stefan Moser
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.