Soll ich eine Lesetransaktion festschreiben oder zurücksetzen?

95

Ich habe eine Leseabfrage, die ich innerhalb einer Transaktion ausführe, damit ich die Isolationsstufe angeben kann. Was soll ich tun, wenn die Abfrage abgeschlossen ist?

  • Übernehmen Sie die Transaktion
  • Setzen Sie die Transaktion zurück
  • Tun Sie nichts (was dazu führt, dass die Transaktion am Ende des using-Blocks zurückgesetzt wird)

Was bedeutet es, dies zu tun?

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?
    }
}

BEARBEITEN: Die Frage ist nicht, ob eine Transaktion verwendet werden soll oder ob es andere Möglichkeiten gibt, die Transaktionsebene festzulegen. Die Frage ist, ob es einen Unterschied macht, ob eine Transaktion, die nichts ändert, festgeschrieben oder zurückgesetzt wird. Gibt es einen Leistungsunterschied? Betrifft es andere Verbindungen? Irgendwelche anderen Unterschiede?

Stefan Moser
quelle
1
Sie wissen wahrscheinlich bereits davon, aber angesichts des Beispiels, das Sie angegeben haben, können Sie gleichwertige Ergebnisse erzielen, indem Sie einfach die Abfrage ausführen: SELECT * FROM SomeTable mit NOLOCK
JasonTrue
@Stefan, es scheint, dass sich die meisten von uns fragen, warum Sie sich die Mühe machen, eine schreibgeschützte Operation durchzuführen. Können Sie uns mitteilen, ob Sie über NOLOCK Bescheid wissen und warum Sie diesen Weg nicht gegangen sind?
StingyJack
Ich kenne NOLOCK, aber dieses System funktioniert sowohl mit verschiedenen Datenbanken als auch mit SQL Server. Daher versuche ich, SQL Server-spezifische Sperrhinweise zu vermeiden. Dies ist eine Frage mehr aus Neugier als aus irgendetwas anderem, da die Anwendung mit dem obigen Code einwandfrei funktioniert.
Stefan Moser
Ah, in diesem Fall entferne ich das sqlserver-Tag, da dies MSSqlServer als Zielprodukt kennzeichnet.
StingyJack
@StingyJack - Du hast recht, ich hätte das sqlserver-Tag nicht verwenden sollen.
Stefan Moser

Antworten:

51

Sie verpflichten sich. Zeitraum. Es gibt keine andere sinnvolle Alternative. Wenn Sie eine Transaktion gestartet haben, sollten Sie sie schließen. Das Festschreiben gibt alle Sperren frei, die Sie möglicherweise hatten, und ist bei ReadUncommitted- oder Serializable-Isolationsstufen gleichermaßen sinnvoll. Sich auf implizites Rollback zu verlassen - obwohl es vielleicht technisch gleichwertig ist - ist nur eine schlechte Form.

Wenn Sie das nicht überzeugt hat, stellen Sie sich den nächsten vor, der eine Update-Anweisung in die Mitte Ihres Codes einfügt und den impliziten Rollback aufspüren und seine Daten entfernen muss.

Mark Brackett
quelle
44
Es gibt eine sinnvolle Alternative - Rollback. Explizites Rollback also. Wenn Sie nichts ändern wollten, stellt das Rollback sicher, dass alles rückgängig gemacht wird. Natürlich hätte es keine Änderungen geben dürfen; Rollback garantiert das.
Jonathan Leffler
2
Unterschiedliche DBMS können unterschiedliche Semantiken für den impliziten Abschluss von Transaktionen haben. IBM Informix (und ich glaube DB2) führen ein implizites Rollback durch. Gerüchten zufolge führt Oracle ein implizites Commit durch. Ich bevorzuge implizites Rollback.
Jonathan Leffler
8
Angenommen, ich erstelle eine temporäre Tabelle, fülle sie mit IDs, verbinde sie mit einer Datentabelle, um die Daten auszuwählen, die zu den IDs gehören, und lösche dann die temporäre Tabelle. Ich lese wirklich nur Daten und es ist mir egal, was mit der temporären Tabelle passiert, da sie nur vorübergehend ist. Aber wäre es aus Sicht der Leistung teurer, die Transaktion zurückzusetzen oder festzuschreiben? Was bewirkt ein Commit / Rollback, wenn nur temporäre Tabellen und Lesevorgänge beteiligt sind?
Triynko
4
@Triynko - Intuitiv würde ich vermuten, dass ROLLBACK teurer ist. COMMIT ist der normale Anwendungsfall und ROLLBACK der Ausnahmefall. Aber wen interessiert es, außer akademisch? Ich bin sicher, es gibt 1000 bessere Optimierungspunkte für Ihre App. Wenn Sie wirklich neugierig sind, finden Sie den mySQL-Transaktionsbehandlungscode unter bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett
3
@Triynko - Die einzige Möglichkeit zur Optimierung ist das Profil. Es ist eine so einfache Codeänderung, dass es keinen Grund gibt, beide Methoden nicht zu profilieren, wenn Sie sie wirklich optimieren möchten. Aktualisieren Sie uns unbedingt mit den Ergebnissen!
Mark Brackett
28

Wenn Sie nichts geändert haben, können Sie entweder ein COMMIT oder ein ROLLBACK verwenden. In beiden Fällen werden alle von Ihnen erworbenen Lesesperren freigegeben, und da Sie keine weiteren Änderungen vorgenommen haben, sind diese gleichwertig.

Graeme Perrow
quelle
2
Vielen Dank, dass Sie mich wissen lassen, dass sie gleichwertig sind. Meiner Meinung nach beantwortet dies am besten die eigentliche Frage.
Chowey
Es würde dazu führen, dass die Transaktion inaktiv ist, wenn wir Commit ohne tatsächliche Aktualisierungen verwenden. Ich habe es gerade auf meiner Live-Seite gesehen
Muhammad Omer Aslam
6

Wenn Sie eine Transaktion beginnen, empfiehlt es sich immer, sie festzuschreiben. Wenn eine Ausnahme in Ihrem Verwendungsblock (Transaktionsblock) ausgelöst wird, wird die Transaktion automatisch zurückgesetzt.

Neil Barnwell
quelle
3

IMHO kann es sinnvoll sein, schreibgeschützte Abfragen in Transaktionen einzuschließen, da Sie (insbesondere in Java) festlegen können, dass die Transaktion "schreibgeschützt" ist, was wiederum der JDBC-Treiber in Betracht ziehen kann, die Abfrage zu optimieren (muss dies aber nicht, also niemand wird Sie INSERTtrotzdem daran hindern, eine auszustellen ). Beispielsweise vermeidet der Oracle-Treiber Tabellensperren bei Abfragen in einer als schreibgeschützt gekennzeichneten Transaktion vollständig, wodurch bei stark lesegesteuerten Anwendungen eine hohe Leistung erzielt wird.

Oliver Drotbohm
quelle
3

Betrachten Sie verschachtelte Transaktionen .

Die meisten RDBMS unterstützen keine verschachtelten Transaktionen oder versuchen, diese auf sehr begrenzte Weise zu emulieren.

In MS SQL Server wird beispielsweise durch ein Rollback in einer inneren Transaktion (bei der es sich nicht um eine echte Transaktion handelt, MS SQL Server zählt nur die Transaktionsebenen!) Alles zurückgesetzt, was in der äußersten Transaktion (der realen Transaktion) geschehen ist.

Einige Datenbank-Wrapper betrachten ein Rollback in einer inneren Transaktion möglicherweise als Zeichen dafür, dass ein Fehler aufgetreten ist, und setzen alles in der äußersten Transaktion zurück, unabhängig davon, ob die äußerste Transaktion festgeschrieben oder zurückgesetzt wurde.

Ein COMMIT ist also der sichere Weg, wenn Sie nicht ausschließen können, dass Ihre Komponente von einem Softwaremodul verwendet wird.

Bitte beachten Sie, dass dies eine allgemeine Antwort auf die Frage ist. Das Codebeispiel umgeht das Problem mit einer äußeren Transaktion geschickt, indem eine neue Datenbankverbindung geöffnet wird.

In Bezug auf die Leistung: Abhängig von der Isolationsstufe erfordern SELECTs möglicherweise einen unterschiedlichen Grad an LOCKs und temporären Daten (Snapshots). Dies wird bereinigt, wenn die Transaktion geschlossen wird. Es spielt keine Rolle, ob dies über COMMIT oder ROLLBACK erfolgt. Möglicherweise gibt es einen unbedeutenden Unterschied in der CPU-Zeit - ein COMMIT lässt sich wahrscheinlich schneller analysieren als ein ROLLBACK (zwei Zeichen weniger) und andere geringfügige Unterschiede. Dies gilt natürlich nur für schreibgeschützte Operationen!

Völlig nicht gefragt: Ein anderer Programmierer, der den Code lesen kann, könnte annehmen, dass ein ROLLBACK eine Fehlerbedingung impliziert.

Klaws
quelle
2

Nur eine Randnotiz, aber Sie können diesen Code auch so schreiben:

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?
}

Und wenn Sie die Dinge nur ein wenig neu strukturieren, können Sie möglicherweise auch den using-Block für den IDataReader nach oben verschieben.

Joel Coehoorn
quelle
1

Wenn Sie die SQL in eine gespeicherte Prozedur einfügen und diese über der Abfrage hinzufügen:

set transaction isolation level read uncommitted

dann müssen Sie nicht durch irgendwelche Reifen im C # -Code springen. Das Festlegen der Transaktionsisolationsstufe in einer gespeicherten Prozedur führt nicht dazu, dass die Einstellung für alle zukünftigen Verwendungen dieser Verbindung gilt (worüber Sie sich bei anderen Einstellungen Sorgen machen müssen, da die Verbindungen zusammengefasst werden). Am Ende der gespeicherten Prozedur wird nur auf das zurückgegriffen, mit dem die Verbindung initialisiert wurde.

Eric Z Bart
quelle
1

ROLLBACK wird meistens bei Fehlern oder außergewöhnlichen Umständen und COMMIT bei erfolgreichem Abschluss verwendet.

Wir sollten Transaktionen mit COMMIT (für den Erfolg) und ROLLBACK (für den Misserfolg) abschließen, selbst bei schreibgeschützten Transaktionen, bei denen dies keine Rolle zu spielen scheint. Tatsächlich ist es wichtig für Konsistenz und Zukunftssicherheit.

Eine schreibgeschützte Transaktion kann auf viele Arten logisch "fehlschlagen", zum Beispiel:

  • Eine Abfrage gibt nicht genau wie erwartet eine Zeile zurück
  • Eine gespeicherte Prozedur löst eine Ausnahme aus
  • Die abgerufenen Daten sind inkonsistent
  • Der Benutzer bricht die Transaktion ab, da sie zu lange dauert
  • Deadlock oder Timeout

Wenn COMMIT und ROLLBACK für eine schreibgeschützte Transaktion ordnungsgemäß verwendet werden, funktioniert es weiterhin wie erwartet, wenn irgendwann DB-Schreibcode hinzugefügt wird, z. B. für Caching, Überwachung oder Statistik.

Implizites ROLLBACK sollte nur in Situationen mit "schwerwiegenden Fehlern" verwendet werden, in denen die Anwendung mit einem nicht behebbaren Fehler, einem Netzwerkfehler, einem Stromausfall usw. abstürzt oder beendet wird.

Sam Watkins
quelle
0

Da ein READ den Status nicht ändert, würde ich nichts tun. Das Ausführen eines Commits führt nur dazu, dass ein Zyklus zum Senden der Anforderung an die Datenbank verschwendet wird. Sie haben keine Operation ausgeführt, deren Status geändert wurde. Ebenso für den Rollback.

Sie sollten jedoch sicherstellen, dass Sie Ihre Objekte bereinigen und Ihre Verbindungen zur Datenbank schließen. Wenn Sie Ihre Verbindungen nicht schließen, kann dies zu Problemen führen, wenn dieser Code wiederholt aufgerufen wird.

Brett McCann
quelle
3
Abhängig von der Isolationsstufe kann ein ausgewählter CAN Sperren erhalten, die andere Transaktionen blockieren.
Graeme Perrow
Die Verbindung wird am Ende des using-Blocks geschlossen - dafür ist sie da. Aber ein guter Punkt, dass der Netzwerkverkehr wahrscheinlich der langsamste Teil der Gleichung ist.
Joel Coehoorn
1
Die Transaktion wird auf die eine oder andere Weise festgeschrieben oder zurückgesetzt. Daher wird empfohlen, immer eine Festschreibung durchzuführen, wenn dies erfolgreich war.
Neil Barnwell
0

Wenn Sie AutoCommit auf false setzen, dann JA.

In einem Experiment mit JDBC (Postgresql-Treiber) stellte ich fest, dass Sie bei Unterbrechungen der Auswahlabfrage (aufgrund eines Zeitlimits) keine neue Auswahlabfrage initiieren können, es sei denn, Sie führen ein Rollback durch.

Shiv Krishna Jaiswal
quelle
-2

In Ihrem Codebeispiel, wo Sie haben

  1. // Mach etwas Nützliches

    Führen Sie eine SQL-Anweisung aus, die Daten ändert?

Wenn nicht, gibt es keine "Lese" -Transaktion ... Nur Änderungen von Anweisungen zum Einfügen, Aktualisieren und Löschen (Anweisungen, die Daten ändern können) befinden sich in einer Transaktion ... Es handelt sich um die Sperren von SQL Der Server legt die Daten, die Sie lesen, aufgrund ANDERER Transaktionen an, die diese Daten betreffen. Die Stufe dieser Sperren hängt von der SQL Server-Isolationsstufe ab.

Sie können jedoch nichts festschreiben oder zurückgeben, wenn Ihre SQL-Anweisung nichts geändert hat.

Wenn Sie Daten ändern, können Sie die Isolationsstufe ändern, ohne explizit eine Transaktion zu starten ... Jede einzelne SQL-Anweisung ist implizit in einer Transaktion enthalten. Das explizite Starten einer Transaktion ist nur erforderlich, um sicherzustellen, dass sich zwei oder mehr Anweisungen innerhalb derselben Transaktion befinden.

Wenn Sie nur die Transaktionsisolationsstufe festlegen möchten, setzen Sie den CommandText eines Befehls einfach auf "Set Transaction Isolation Level Repeatable Read" (oder eine beliebige gewünschte Stufe), setzen Sie den CommandType auf CommandType.Text und führen Sie den Befehl aus. (Sie können Command.ExecuteNonQuery () verwenden.)

HINWEIS: Wenn Sie MULTIPLE-Leseanweisungen ausführen und möchten, dass alle den gleichen Status der Datenbank wie die erste "sehen", müssen Sie die Isolationsstufe auf "Wiederholbares Lesen" oder "Serialisierbar" setzen.

Charles Bretana
quelle
// Etwas Nützliches tun ändert keine Daten, nur lesen. Ich möchte lediglich die Isolationsstufe der Abfrage angeben.
Stefan Moser
Dann können Sie dies tun, ohne explizit eine Transaktion vom Client aus zu starten ... Führen Sie einfach die SQL-Zeichenfolge "Set Transaction Isolation Level ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" aus. , oder "... Serializable" "Set Isolation Level Read Committed"
Charles Bretana
3
Transaktionen sind immer noch wichtig, auch wenn Sie nur lesen. Wenn Sie mehrere Lesevorgänge ausführen möchten, wird durch Konsistenz innerhalb einer Transaktion die Konsistenz sichergestellt. Wenn man sie ohne macht, wird man es nicht tun.
MarkR
Ja, Entschuldigung, Sie haben Recht. Zumindest ist dies der Fall, wenn die Isolationsstufe auf Wiederholbares Lesen oder höher eingestellt ist.
Charles Bretana
-3

Müssen Sie andere daran hindern, dieselben Daten zu lesen? Warum eine Transaktion verwenden?

@Joel - Meine Frage wäre besser formuliert als "Warum eine Transaktion für eine Leseabfrage verwenden?"

@Stefan - Wenn Sie AdHoc SQL und keinen gespeicherten Prozess verwenden möchten, fügen Sie einfach das WITH (NOLOCK) nach den Tabellen in der Abfrage hinzu. Auf diese Weise entsteht kein Aufwand (wenn auch nur minimal) in der Anwendung und der Datenbank für eine Transaktion.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Kommentar 3: Da Sie "sqlserver" in den Frage-Tags hatten, hatte ich angenommen, dass MSSQLServer das Zielprodukt war. Nachdem dieser Punkt geklärt wurde, habe ich die Tags bearbeitet, um die spezifische Produktreferenz zu entfernen.

Ich bin mir immer noch nicht sicher, warum Sie überhaupt eine Transaktion für eine Leseoperation durchführen möchten.

StingyJack
quelle
1
Auf einmal auf die eingestellte Isolationsstufe. Sie können die Transaktion verwenden, um den Sperrbetrag für die Abfrage tatsächlich zu reduzieren .
Joel Coehoorn
1
Ich verwende die Transaktion, damit ich eine niedrigere Isolationsstufe verwenden und die Sperrung reduzieren kann.
Stefan Moser
@StingyJack - Dieser Code kann für eine Reihe verschiedener Datenbanken ausgeführt werden, daher ist NOLOCK keine Option.
Stefan Moser