Frage

Ich habe einige Kollegen von mir mit einem SQL-Problem helfen. Vor allem wollten sie alle Zeilen aus der Tabelle A Tabelle B bewegen (beide Tabellen die gleichen Spalten (Namen und Typen) mit). Obwohl dies in Oracle 11g getan wurde ich glaube nicht, es wirklich wichtig ist.

Ihre anfängliche naive Implementierung war so etwas wie

BEGIN
  INSERT INTO B SELECT * FROM A
  DELETE FROM A
  COMMIT;
END

Ihre Sorge war, ob es INSERTs Tabelle A von A gemacht wurden nach B während des Kopierens und der „FROM A DELETE“ (oder TRUNCATE für das, was wert war) würde zu Datenverlust führen (die neueren eingefügten Zeilen mit in A gelöscht).

Ofcourse schnell empfahl ich die IDs der kopierten Zeilen in einer temporären Tabelle zu speichern und dann nur die Zeilen in A zu löschen, die die IDS in der temporären Tabelle angepasst.

Doch für Neugier willen setzen wir durch das Hinzufügen einer Wartebefehl einen kleinen Test auf (kann mich nicht erinnern die PL / SQL-Syntax) zwischen INSERT und DELETE. Dann aus einer anderen Verbindung würden wir Einfügen von Zeilen während des Wartens .

Wir haben beobachtet, dass ein Datenverlust durch dabei war. Ich reproduzierte den gesamten Kontext in SQL Server und das alles in einer Transaktion eingewickelt, aber immer noch die frischen neuen Daten wurden auch in SQL Server verloren. Das machte ich denke, es ist ein systematischer Fehler / Fehler im ersten Ansatz.

Allerdings kann ich nicht sagen, ob es die Tatsache, dass die Transaktion ist nicht (irgendwie?), Isoliert aus den frischen neuen Einfügungen oder der Tatsache, dass die Einsätze während des WAIT-Befehls kamen.

Am Ende wurde es mit der temporären Tabelle von mir vorgeschlagen umgesetzt, aber wir konnten nicht die Antwort auf „Warum der Datenverlust“ bekommen. Weißt du, warum?

War es hilfreich?

Lösung

auf der Isolationsstufe Je nach Auswahl aller Zeilen aus einer Tabelle nicht daran hindert, neue Einsätze, wird es sperrt nur die Zeilen, die Sie lesen. In SQL Server, wenn Sie die Serializable Isolationsstufe verwenden, dann wird es neue Zeilen verhindern, wenn sie in Ihrer Auswahlabfrage wurden einschließlich würden.

http://msdn.microsoft.com/en-us/library /ms173763.aspx -

SERIALIZABLE Gibt folgende Möglichkeiten:

  • Statements können keine Daten lesen, die von anderen Transaktionen, aber noch nicht begangen wurde geändert.

  • Es wird keine andere Transaktionen können Daten ändern, die von der aktuellen Transaktion gelesen wurde, bis die aktuelle Transaktion abgeschlossen ist.

  • Andere Transaktionen können nicht neue Zeilen mit Schlüsselwerten einzufügen, die im Bereich der Schlüssel gelesen von Erklärungen, die in der aktuellen Transaktion, bis die aktuelle Transaktion abgeschlossen fallen würden.

Andere Tipps

Ich kann nicht auf die Transaktion Stabilität sprechen, aber ein alternativer Ansatz zu haben, wäre der zweite Schritt aus der Quelltabelle löschen, wo (wählen ids aus Zieltabelle) vorhanden ist.

Verzeihen Sie die Syntax, ich habe diesen Code nicht getestet, aber Sie sollten die Idee zu erhalten fähig sein:

INSERT INTO B SELECT * FROM A;

DELETE FROM A WHERE EXISTS (SELECT B.<primarykey> FROM B WHERE B.<primarykey> = A.<primarykey>);

Auf diese Weise kann die relationale Engine zu erzwingen verwenden, dass keine neueren Daten werden gelöscht, und Sie brauchen nicht die beiden Schritte in einer Transaktion zu tun.

Update: korrigiert Syntax in Unterabfrage

Dies kann in Oracle erreicht werden:

Alter session set isolation_level=serializable;

Dies kann in PL / SQL eingestellt werden SOFORT mit EXECUTE:

BEGIN
    EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
    ...
END;

Siehe Tom Frage: Auf Transaktionsisolationsstufen

Es ist nur die Art und Weise Transaktionen arbeiten. Sie haben die richtige Isolationsstufe für die Aufgabe wählen.

Sie tun INSERT und DELETE in der gleichen Transaktion. Sie erwähnen nicht den Isolationsmodus Transaktion verwendet, aber es ist wahrscheinlich ‚read committed‘. Dies bedeutet, dass die DELETE-Befehl werden die Datensätze angezeigt, die in der Zwischenzeit begangen wurden. Für diese Art von Job, ist es viel besser ‚Momentaufnahme‘ Art der Transaktion zu verwenden, da dann beide INSERT und DELETE würde etwa die gleiche Menge von Datensätzen kennen -. Nur diejenigen, und nichts anderes

Ich weiß nicht, ob dies relevant ist, aber in SQL Server die Syntax

begin tran
....
commit

nicht nur 'beginnen'

Sie benötigen eine Transaktionsisolationsstufe zu setzen, so dass die Einsätze aus einer anderen Transaktion wirken sich nicht auf Ihre Transaktion. Ich weiß nicht, wie das in Oracle zu tun.

In Oracle wird die Standardtransaktionsisolationsstufe verpflichtet. Das bedeutet im Grunde, dass Oracle die Ergebnisse zurückgibt, wie sie bei der SCN (System Change Number) bestanden, wenn Ihre Abfrage gestartet. Einstellen der Transaktionsisolationsstufe zu serializable bedeutet, dass der SCN zu Beginn der Transaktion erfaßt wird, so dass alle Abfragen in Ihren Transaktion Rückgabedaten ab diesem SCN. Das sorgt für konsistente Ergebnisse unabhängig davon, welche anderen Sitzungen und Transaktionen tun. Auf der anderen Seite kann es ein Kosten in diesem Oracle kann bestimmen, dass es nicht Ihre Transaktion wegen der Aktivität serialisiert kann, die anderen Transaktionen durchführen, so dass Sie diese Art von Fehlern behandeln müßten.

Tonys Link auf die AskTom Diskussion geht im wesentlichen auf mehr Details über this-- zu alles, was ich kann es sehr empfehlen.

Ja Milan, ich habe nicht die Transaktionsisolationsstufe festgelegt. Ich nehme an, es ist die Standardisolationsstufe, die ich weiß nicht, was es ist. Weder in Oracle 11g noch in SQL Server 2005.

Darüber hinaus die INSERT, die während des WAIT-Befehl (auf der zweiten Verbindung) war nicht in einer Transaktion durchgeführt wurde. Sollte es gewesen diesen Datenverlust zu verhindern?

Dies ist das Standardverhalten des Standardlese begangen Modus, wie oben erwähnt. Der WAIT-Befehl bewirkt, dass nur eine Verzögerung bei der Verarbeitung gibt es keine Verbindung zu einer beliebigen DB-Transaktionsabwicklung.

Um das Problem zu beheben, können Sie entweder:

  1. stellen Sie die Isolationsstufe auf serializable, aber dann kann man ORA- Fehlermeldungen erhalten, die Sie mit Wiederholungen behandeln müssen! Auch Sie können eine schwere Leistungseinbußen kommen.
  2. verwenden, um eine temporäre Tabelle zum Speichern der Werte zuerst
  3. , wenn die Daten nicht zu groß sind, in den Speicher zu passen, können Sie eine RETURNING-Klausel BULK verwenden, um in einer verschachtelten Tabelle COLLECT und nur löschen, wenn die Zeile in der verschachtelten Tabelle vorhanden ist.

Alternativ können Sie die Snapshot-Isolation zu erkennen verlorenes Updates verwenden:

Wenn Snapshot Isolation hilft und wenn es verletzt

    I have written a sample code:-

    First run this on Oracle DB:-


     Create table AccountBalance
        (
              id integer Primary Key,
              acctName varchar2(255) not null,
              acctBalance integer not null,
              bankName varchar2(255) not null
        );

        insert into AccountBalance values (1,'Test',50000,'Bank-a');

    Now run the below code 





 package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;

        public class DirtyReadExample {

         /**
          * @param args
         * @throws ClassNotFoundException 
          * @throws SQLException 
          * @throws InterruptedException 
          */
         public static void main(String[] args) throws ClassNotFoundException, SQLException, InterruptedException {

             Class.forName("oracle.jdbc.driver.OracleDriver");
             Connection connectionPayment = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");
             Connection connectionReader = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");

          try {
              connectionPayment.setAutoCommit(false);
              connectionPayment.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);


          } catch (SQLException e) {
           e.printStackTrace();
          }


          Thread pymtThread=new Thread(new PaymentRunImpl(connectionPayment));
          Thread readerThread=new Thread(new ReaderRunImpl(connectionReader));

          pymtThread.start();
          Thread.sleep(2000);
          readerThread.start();

         }

        }



        package com.java.transaction.dirtyread;

        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.ResultSet;
        import java.sql.SQLException;

        public class ReaderRunImpl  implements Runnable{

         private Connection conn;

         private static final String QUERY="Select acctBalance from AccountBalance where id=1";

         public ReaderRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt =null; 
          ResultSet rs =null;

          try {
           stmt = conn.prepareStatement(QUERY);
           System.out.println("In Reader thread --->Statement Prepared");
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           System.out.println("In Reader thread --->Statement Prepared");
           Thread.sleep(5000);
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
          } catch (SQLException | InterruptedException e) {
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
            rs.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }   
          }
         }

        }

        package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.SQLException;

        public class PaymentRunImpl implements Runnable{

         private Connection conn;

         private static final String QUERY1="Update AccountBalance set acctBalance=40000 where id=1";
         private static final String QUERY2="Update AccountBalance set acctBalance=30000 where id=1";
         private static final String QUERY3="Update AccountBalance set acctBalance=20000 where id=1";
         private static final String QUERY4="Update AccountBalance set acctBalance=10000 where id=1";

         public PaymentRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt = null;

          try {   
           stmt = conn.prepareStatement(QUERY1);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY2);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY3);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           stmt = conn.prepareStatement(QUERY4);
           stmt.execute();
           System.out.println("In Payment thread --> executed");

           Thread.sleep(5000);
            //case 1
           conn.rollback();
           System.out.println("In Payment thread --> rollback");
          //case 2
           //conn.commit();
          // System.out.println("In Payment thread --> commit");
          } catch (SQLException e) {
           e.printStackTrace();
          } catch (InterruptedException e) {    
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }
          }
         }

        }

    Output:-
    In Payment thread --> executed
    In Reader thread --->Statement Prepared
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->Statement Prepared
    In Payment thread --> executed
    In Payment thread --> executed
    In Payment thread --> executed
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->executing
    Balance is:50000.0
    In Payment thread --> rollback

U kann es testen, indem neue Zeilen eingefügt, wie durch Oracle definiert: -  Eine Phantom-Leseoperation tritt auf, wenn A Transaktion eine Reihe von Zeilen eine gegebene Bedingung erfüllen, ruft die Transaktion B anschließend Einsätze oder aktualisiert eine Reihe, so dass die Reihe nun den Zustand, in A-Transaktion entspricht, und A-Transaktion später wiederholt den bedingten Abruf. Transaktion A sieht nun eine zusätzliche Zeile. Diese Zeile wird als Phantom bezeichnet. Es wird das oben beschriebene Szenario vermeiden, so gut ich TRANSACTION_SERIALIZABLE verwendet haben. Es wird die strengste Sperre auf der Oracle gesetzt. Oracle unterstützt nur 2 Art der Transaktionsisolationsstufen: - TRANSACTION_READ_COMMITTED und TRANSACTION_SERIALIZABLE.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top