Domanda

Stavo aiutando alcuni miei colleghi con un problema SQL. Principalmente volevano spostare tutte le righe dalla tabella A alla tabella B (entrambe le tabelle hanno le stesse colonne (nomi e tipi)). Anche se questo è stato fatto in Oracle 11g, non penso che importi davvero.

La loro ingenua implementazione iniziale era qualcosa di simile

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

La loro preoccupazione era se c'erano degli INSERTI fatti nella tabella A durante la copia da A a B e il comando "ELIMINA DA A". (o TRUNCATE per quello che valeva) provocherebbe la perdita di dati (eliminando le righe inserite più recenti in A).

Ovviamente ho consigliato di archiviare rapidamente gli ID delle righe copiate in una tabella temporanea e quindi di eliminare solo le righe in A che corrispondevano agli ID nella tabella temporanea.

Comunque per curiosità abbiamo messo un piccolo test aggiungendo un comando wait (non ricordare la sintassi PL / SQL) tra INSERT e DELETE. Quindi da una connessione diversa inseriremmo le righe DURING THE WAIT .

Abbiamo osservato che è stata una perdita di dati nel farlo. Ho riprodotto l'intero contesto in SQL Server e ho racchiuso tutto in una transazione, ma i nuovi dati sono andati persi anche in SQL Server. Questo mi ha fatto pensare che ci sia un errore / difetto sistematico nell'approccio iniziale.

Tuttavia non posso dire se il fatto che la TRANSAZIONE non sia stata (in qualche modo?) isolata dai nuovi INSERT nuovi o il fatto che gli INSERT siano arrivati ??durante il comando WAIT.

Alla fine è stato implementato utilizzando la tabella temporanea da me suggerita, ma non siamo riusciti a ottenere la risposta a "Perché la perdita di dati". Sai perché?

È stato utile?

Soluzione

A seconda del livello di isolamento, la selezione di tutte le righe da una tabella non impedisce nuovi inserimenti, bloccherà semplicemente le righe che leggi. In SQL Server, se si utilizza il livello di isolamento serializzabile, si eviteranno nuove righe se fossero state incluse nella query selezionata.

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

SERIALIZABLE Specifica quanto segue:

  • Le dichiarazioni non possono leggere i dati che sono stati modificati ma non ancora impegnati da altre transazioni.

  • Nessun'altra transazione può modificare i dati letti dalla transazione corrente fino al completamento della transazione corrente.

  • Altre transazioni non possono inserire nuove righe con valori chiave che rientrerebbero nell'intervallo di chiavi letto da qualsiasi istruzione nella transazione corrente fino al completamento della transazione corrente.

Altri suggerimenti

Non posso parlare della stabilità della transazione, ma un approccio alternativo sarebbe quello di eliminare il secondo passo dalla tabella di origine dove esiste (selezionare gli ID dalla tabella di destinazione).

Perdona la sintassi, non ho testato questo codice, ma dovresti essere in grado di ottenere l'idea:

INSERT INTO B SELECT * FROM A;

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

In questo modo si utilizza il motore relazionale per imporre che non vengano eliminati dati più recenti e non è necessario eseguire i due passaggi in una transazione.

Aggiornamento: sintassi corretta nella sottoquery

Questo può essere ottenuto in Oracle usando:

Alter session set isolation_level=serializable;

Questo può essere impostato in PL / SQL usando EXECUTE IMMEDIATE:

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

Vedi Chiedi a Tom: sui livelli di isolamento delle transazioni

È solo il modo in cui funzionano le transazioni. Devi scegliere il livello di isolamento corretto per l'attività in corso.

Stai facendo INSERT e DELETE nella stessa transazione. Non menzionate la transazione in modalità di isolamento in uso, ma probabilmente è "read commit". Ciò significa che il comando DELETE vedrà i record che sono stati impegnati nel frattempo. Per questo tipo di lavoro, è molto meglio usare il tipo di transazione "istantanea", perché sia ??INSERT che DELETE conosceranno lo stesso set di record - solo quelli e nient'altro.

Non so se questo è rilevante, ma in SQL Server la sintassi è

begin tran
....
commit

non solo 'inizia'

È necessario impostare il livello di isolamento della transazione in modo che gli inserimenti di un'altra transazione non influiscano sulla transazione. Non so come farlo in Oracle.

In Oracle, il livello di isolamento della transazione predefinito viene letto come commit. Ciò significa sostanzialmente che Oracle restituisce i risultati così come erano presenti nell'SCN (numero di modifica del sistema) all'avvio della query. L'impostazione del livello di isolamento della transazione su serializzabile significa che l'SCN viene acquisito all'inizio della transazione, quindi tutte le query nella transazione restituiscono i dati a partire da tale SCN. Ciò garantisce risultati coerenti indipendentemente da ciò che altre sessioni e transazioni stanno facendo. D'altra parte, potrebbe esserci un costo in quanto Oracle potrebbe determinare che non può serializzare la transazione a causa dell'attività che altre transazioni stanno eseguendo, quindi dovresti gestire quel tipo di errore.

Il link di Tony alla discussione di AskTom fornisce informazioni sostanzialmente più dettagliate su tutto ciò-- lo consiglio vivamente.

Sì, Milano, non ho specificato il livello di isolamento della transazione. Suppongo che sia il livello di isolamento predefinito che non so quale sia. Né in Oracle 11g né in SQL Server 2005.

Inoltre, INSERT che è stato effettuato durante il comando WAIT (sulla seconda connessione) era NOT all'interno di una transazione. Dovrebbe essere stato per prevenire questa perdita di dati?

Questo è il comportamento standard della modalità di commit di lettura predefinita, come menzionato sopra. Il comando WAIT provoca solo un ritardo nell'elaborazione, non esiste alcun collegamento alla gestione delle transazioni DB.

Per risolvere il problema puoi:

  1. imposta il livello di isolamento su serializzabile, ma poi puoi ottenere errori ORA, che devi gestire con i tentativi! Inoltre, potresti subire un grave calo delle prestazioni.
  2. usa una tabella temporanea per memorizzare prima i valori
  3. se i dati non sono troppo grandi per adattarsi alla memoria, è possibile utilizzare una clausola RETURNING per BULK COLLECT IN una tabella nidificata ed eliminare solo se la riga è presente nella tabella nidificata.

In alternativa, puoi utilizzare l'isolamento dello snapshot per rilevare gli aggiornamenti persi:

Quando l'isolamento di istantanee aiuta e quando fa male

    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 puoi provarlo inserendo nuove righe come definito da Oracle: -  Una lettura fantasma si verifica quando la transazione A recupera una serie di righe che soddisfano una determinata condizione, la transazione B inserisce o aggiorna successivamente una riga in modo tale che la riga soddisfi ora la condizione nella transazione A e la transazione A ripeta successivamente il recupero condizionale. La transazione A ora vede una riga aggiuntiva. Questa riga viene definita fantasma. Eviterà lo scenario sopra e ho usato TRANSACTION_SERIALIZABLE. Imposta il blocco più rigoroso sull'oracolo. Oracle supporta solo 2 tipi di livelli di isolamento delle transazioni: - TRANSACTION_READ_COMMITTED e TRANSACTION_SERIALIZABLE.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top