2009-06-02 6 views
1

Ich habe die folgende Situation:TransactionScope mit MySQL und Lesesperre verwenden

Wenn eine MySQL-Datenbank mit einer InnoDB-Tabelle, die ich verwenden, um eindeutige Zahlen zu speichern. Ich starte eine Transaktion, lese den Wert (zB 1000471), speichere diesen Wert in einer anderen Tabelle und aktualisiere den inkrementierten Wert (100472). Jetzt möchte ich vermeiden, dass jemand anderes den Wert liest, während meine Transaktion läuft.

Wenn ich Ebene MySQL verwenden würde ich so etwas tun würde:

Exceute ("LOCK Tbl1 READ");
Execute ("SELECT ... from tbl1");
Ausführen ("INSERT in tbl2");
Ausführen ("UNLOCK TABLES");

Da ich aber SubSonic als DAL benutze und der Code unabhängig von mysql sein soll, muss ich TransactionScope verwenden.

Mein Code:

 TransactionOptions TransOpt = new TransactionOptions(); 
     TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; 
     TransOpt.Timeout = new TimeSpan(0, 2, 0); 

     using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TransOpt)) 
     { 

      // Select Row from tbl1 

      // Do something 

      ts.Complete(); 
     } 

Nach der Hilfe von TransactionOptions

system.transactions.isolationlevel

Der Effekt, den ich erreichen will, mit IsolationLevel.ReadCommitted umgesetzt werden könnte, aber ich kann immer noch die Zeile lesen von außerhalb der Transaktion (Wenn ich versuche, es zu ändern, bekomme ich eine Sperre, so dass die Transaktion funktioniert)

Tut anyb ody hat einen Vorschlag? Ist eine Lesesperre sogar möglich mit TransactionScope

+0

Beachten Sie, dass Sie bei Verwendung von TransactionScope mit einem Remote-MySQL-Server einen Distributed Transaction Coordinator auf dem lokalen und dem Remotecomputer benötigen. –

+0

Das ist nicht wahr, DTC wird benötigt a) wenn die Implementierung es benötigt b) immer wenn Sie mehr als eine Transaktion i.g. (TransactionScope ts = neue TransactionScope()) {- löschen Sie eine Datei (auf Vista-Dateioperationen Unterstützung TransactionScope) - Wählen Sie * aus Tabelle auf Server 1 - In Tabelle auf Server 2 einfügen - Löschen aus Tabelle auf Server 1} –

Antwort

0

Ok, habe ich beschlossen, die „Hintertür“ zu verwenden und jetzt eine Inline-Abfrage verwenden:

 // start transaction 
     using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     { 
      using (SharedDbConnectionScope scope = new SharedDbConnectionScope(DB._provider)) 
      { 

       try 
       { 

        Record r = new InlineQuery().ExecuteAsCollection<RecordCollection>(
         String.Format("SELECT * FROM {0} WHERE {1} = ?param FOR UPDATE", 
             Record.Schema.TableName, 
             Record.Columns.Column1), "VALUE")[0]; 

        // do something 

        r.Save(); 
       } 
      } 
     } 

Ich habe 10 Threads simultan gestartet und es funktioniert wie erwartet. Danke an rudolfson für den Hinweis "SELECT FOR UPDATE".

+0

Ich habe vergessen zu erwähnen, dass dieser Codecode diesen Patch benötigt: http://code.google.com/p/subsonicproject/issues/detail?id=96 (oder ändern Sie ihn, um den Parameter nicht zu verwenden) –

1

Meine erste Schätzung war zu verwenden SELECT FOR UPDATE und nach einer schnellen Suche fand ich eine Seite über locking reads in der MySQL 5 Referenz.

Wenn ich richtig verstehe, ist dies unabhängig von der Isolationsstufe verwendet. Und Vorsicht - die Isolationsstufe sagt nur aus, wie die aktuelle Transaktion durch Änderungen in anderen Transaktionen beeinflusst wird. Es zeigt nicht an, was andere Transaktionen tun können. Für höhere Isolationsstufen sind jedoch stärker einschränkende Sperren erforderlich.

+0

Wie ich bereits erwähnt habe, benutze ich SubSonic für Ausdauer.

Ich könnte Plain SQL gegen meine MySQL DB ausführen, aber das würde das Multi-Datenbank-Konzept von Subsonic durchbrechen. Thx für den Link, aber ich habe immer noch keine Lösung zum Sperren einer Zeile zum Lesen mit mysql gefunden Dies ist auch lesenswert: http://dev.mysql.com/doc/refman/5.1/ en/innodb-lock-modes.html Ich entdeckte, wenn ich nicht die IsolationLevel gesetzt bekomme ich eine "Deadlock gefunden, wenn Sie versuchen, Sperre zu bekommen; versuchen, Transaktion neu zu starten" - MySqlException, die eine gute Sache scheint (siehe meine Antwort) –

+0

Ich habe den Link gelesen, den Sie angegeben haben, und meiner Meinung nach ist SELECT FOR UPDATE der richtige Weg, um eine Zeile explizit zu sperren. Ich kenne SubSonic nicht (denke, es ist ein ORM-Tool/Persistenz-Framework) - haben Sie in der Dokumentation nachgesehen, ob es die Angabe eines Sperrmodus beim Lesen von Entities unterstützt? Ich kenne diese Möglichkeit von anderen ORM-Werkzeugen, z.B. Ruhezustand (Java). Wenn ich Hibernate anrichte, z. LockMode.UPGRADE, würde dies zu einem SELECT für UPDATE führen. – rudolfson

+0

Sie haben Recht. SELECT FOR UPDATE ist die richtige Syntax in SQL. Das Problem scheint zu sein, dass TransactionOption IsolationLevel mit dem TransactionScope - Ansatz nur steuern kann, welche Daten von der aktuellen Transaktion gelesen werden können (alt, nicht commited, neu) und was mit Schreibvorgängen passiert, wenn sich Daten geändert haben Post), kann aber keine UPDATE-Sperre aufrufen.Schau mal, was ich gefunden habe: http://stackoverflow.com/questions/190666/linq-to-sql-and-concurrency-issues/190690 –

1

Da ich nicht einen Weg finden, eine Zeile für das Lesen mit InnoDB und Transaction zu sperren (Ich kann mich irren) dies sollte funktionieren:

Wenn ich zwei Transaktionen simultan ausgeführt werden (ohne TransactionOptions) und ein Ende, der andere kann wegen einer "Deadlock" -Ausnahme nicht abgeschlossen werden.

Anstatt diese Ausnahme zu vermeiden, scheint es nach dem MySQL-Standard documentation die beste Vorgehensweise zu sein, einen Deadlock zu erwarten und die Transaktion neu zu starten.

, wenn Sie festgelegt:

TransactionOptions TransOpt = new TransactionOptions(); 
    TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; 

für Ihre Transaktion, die Sie nicht aus der Sackgasse Ausnahme, aber in meinem Fall, dies in einer dublicate eindeutigen Nummer zur Folge haben würde, was schlimmer ist.

2

Wenn jemand interessiert ist, das ist, wie TransactionOptions beeinflussen MySql:

sagen Lets Ich habe zwei Methoden.

Methode1 startet eine Transaktion, wählt eine Zeile aus meiner Tabelle, erhöht den Wert und aktualisiert die Tabelle.

Methode2 ist das gleiche, aber zwischen Auswahl und Update habe ich einen Schlaf von 1000ms hinzugefügt.

Nun stelle ich mir den folgenden Code haben:

Private Sub Button1_Click(sender as Object, e as System.EventArgs) Handles Button1.Click 

     Dim thread1 As New Threading.Thread(AddressOf Method1) 
     Dim thread2 As New Threading.Thread(AddressOf Method2) 

     thread2.Start() // I start thread 2 first, because this one sleeps 
     thread1.Start() 

    End Sub 

Ohne Transaktionen dies geschehen würde:
Thread2 startet, liest den Wert 5, schläft dann,
Thread1 startet, liest den Wert 5, aktualisiert die Wert auf 6,
thread2 aktualisiert den Wert ebenfalls auf 6.

Wirkung: Ich habe die eindeutige Nummer zweimal.

Was ich will:
Thread2 startet, liest den Wert 5, dann schläft,
Thread1 beginnt, Trys, um den Wert liest, aber eine Sperre erhalten und schläft,
Thread2 den Wert auf 6 aktualisiert,
thread1 weiter, liest den Wert 6, aktualisiert den Wert auf 7

das ist, wie Transaktion mit der Transaction zu starten:

 TransactionOptions Opts = new TransactionOptions(); 
     Opts.IsolationLevel = IsolationLevel.ReadUncommitted; 

     // start Transaction 
     using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, Opts)) 
     { 
      // Do your work and call complete 
      ts.Complete(); 
     } 

Das kann sogar verteilte Transaktionen verwalten. Wenn eine Ausnahme ausgelöst wird, wird ts.Complete nie aufgerufen, und der Dispose() - Teil des Gültigkeitsbereichs setzt die Transaktion zurück.

Hier ist eine Übersicht, wie die verschiedenen IsolationLevels die Transaktion beeinflussen:

  • IsolationLevel.Chaos
    eine NotSupportedException Wirft - Chaos Isolationsstufe wird nicht unterstützt

  • IsolationLevel.ReadCommited
    Die Transaktionen stören sich nicht (zwei identische Lesevorgänge, schlechte)

  • IsolationLevel.ReadUncommitted
    Die Transaktionen stören einander nicht (zwei identische liest, schlecht)

  • IsolationLevel.RepeatableRead
    Die Transaktionen stören einander nicht (zwei identische liest, schlecht
    )
  • IsolationLevel.Serializable
    wirft einen MySqlE xception - Deadlock gefunden, wenn versucht wird, Sperre zu bekommen; versuchen, die Transaktion während des Updates neu zu starten

  • Isolationsstufe.Snapshot
    löst eine MySqlException aus - Sie haben einen Fehler in Ihrer SQL-Syntax; Sie in die Bedienungsanleitung zu Ihrer MySQL-Server-Version für die richtige Syntax entspricht verwendet in der Nähe von ‚‘ in Zeile 1 während Connection.Open()

  • IsolationLevel.Unspecified
    eine MySqlException Wirft - Deadlock gefunden, wenn sie versuchen zu erhalten sperren; versuchen, während der Update-

  • TransactionOptions nicht gesetzt
    Wirft eine MySqlException Neustart Transaktion - Deadlock gefunden, wenn sie versuchen Sperre zu erhalten; versuchen einen Neustart Transaktion während Aktualisierung

0

Da SubSonic scheint nicht SELECT ... FOR UPDATE und mit Isolationsstufen zu unterstützen ein Missbrauch meiner Meinung nach wäre - was ist ein user defined function mit, die eine neue id zurückgibt? Diese Funktion würde den aktuellen Wert von tbl1 mit einer SELECT ... FOR UPDATE lesen, die Zeile udpate und den Wert zurückgeben.

In Ihrem Anwendungscode in dem Sie einen neuen Wert einfügen würden Sie nur verwenden:

insert into tbl2 (id, ....) values (next_id(), ...) 
+0

Wäre eine mögliche Lösung, aber in meinem Fall ist die Nummer nicht nur eine Zahl, aber ein String-Wert, den ich programmatisch mit einem Masken-String kombiniere, um den neuen Wert zu bestimmen (zB die aktuelle Zahl 200905123 und die Maske YYYYMM ### würde heute eine neue Nummer 200906001 ergeben) und ich möchte nicht ziehen diese Logik aus meiner App. –

Verwandte Themen