2008-09-29 15 views
7

estaba ayudando a algunos de mis colegas con un problema de SQL. Principalmente querían mover todas las filas de la tabla A a la tabla B (ambas tablas tenían las mismas columnas (nombres y tipos)). Aunque esto se hizo en Oracle 11g, no creo que realmente importe.problema de aislamiento de transacción o enfoque incorrecto?

Su aplicación ingenua inicial fue algo así como

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

Su preocupación era si había inserciones hechas al cuadro A durante la copia de A a B y el "borrar de una" (o TRUNCATE para lo que vale la pena) Causaría pérdida de datos (eliminando las filas insertadas más nuevas en A).

Por supuesto, recomendé rápidamente almacenar los ID de las filas copiadas en una tabla temporal y luego eliminar solo las filas en A que coinciden con los IDS en la tabla temporal.

Sin embargo por curiosidad ponemos un poco de ensayo mediante la adición de un comando de espera (no recuerdo la sintaxis de PL/SQL) entre INSERT y DELETE. Luego de una conexión diferente tendríamos insertar filas durante la espera.

se observó que era una pérdida de datos por hacerlo. Reproduje todo el contexto en SQL Server y lo envolví todo en una transacción, pero aún los nuevos datos nuevos también se perdieron en SQL Server. Esto me hizo pensar que hay un error/error sistemático en el enfoque inicial.

Sin embargo, no puedo decir si fue el hecho de que la TRANSACCIÓN no estaba (de alguna manera?) Aislada de los nuevos INSERT o el hecho de que los INSERT aparecieron durante el comando WAIT.

Al final se llevó a cabo utilizando la tabla temporal sugerido por mí, pero no pudimos conseguir la respuesta a "¿Por qué la pérdida de datos". ¿Sabes por qué?

Respuesta

8

Dependiendo de su nivel de aislamiento, seleccionar todas las filas de una tabla no impide nuevas inserciones, simplemente bloqueará las filas que lee. En SQL Server, si usa el nivel de aislamiento Serializable, evitará filas nuevas si se hubieran incluido en su consulta de selección.

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

SERIALIZABLE especifica lo siguiente:

  • Las declaraciones no pueden leer datos que han sido modificados pero aún no cometidos por otras transacciones.

  • Ninguna otra transacción puede modificar los datos que ha leído la transacción actual hasta que se complete la transacción actual.

  • Otras transacciones no pueden insertar nuevas filas con valores clave que corresponderían al rango de claves leídas por cualquier instrucción en la transacción actual hasta que se complete la transacción actual.

1

no sé si esto es relevante, pero en SQL Server La sintaxis es

begin tran 
.... 
commit 

no sólo 'comenzar'

1

Es necesario establecer el nivel de aislamiento de transacción para que los insertos de otra transacción no afecta su transacción. No sé cómo hacer eso en Oracle.

7

No puedo hablar a la estabilidad transacción, pero un enfoque alternativo sería tener la segunda etapa de borrar de la tabla de origen donde existe (seleccione ids de tabla de destino).

perdonar a la sintaxis, no he probado este código, pero debe ser capaz de obtener la idea:

INSERT INTO B SELECT * FROM A; 

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

De esa manera usted está utilizando el motor relacional de hacer cumplir que no se eliminarán los datos más recientes, y no necesita hacer los dos pasos en una transacción.

Actualización: sintaxis corregido en subconsulta

+0

Voy con @Guy. Es mucho mejor hacer que todo sea explícito y obvio. Hacer esto usando SQL donde sea posible es menos arcano que usar niveles de aislamiento (si puedes hacerlo, por supuesto). –

+0

La sintaxis es incorrecta: "... donde existe (Seleccione B. desde B donde B. = A. ) –

5

Esto se puede lograr en Oracle usando:

Alter session set isolation_level=serializable; 

Esto se puede configurar en PL/SQL mediante EXECUTE IMMEDIATE:

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

Ver Ask Tom: On Transaction Isolation Levels

2

Es así como funcionan las transacciones. Debe elegir el nivel de aislamiento correcto para la tarea en cuestión.

Estás haciendo INSERTAR y ELIMINAR en la misma transacción. No mencionas el modo de aislamiento que está usando la transacción, pero probablemente sea 'read committed'. Esto significa que el comando DELETE verá los registros que fueron cometidos mientras tanto. Para este tipo de trabajo, es mucho mejor utilizar el tipo de transacción de "instantánea", ya que tanto INSERT como DELETE conocerían el mismo conjunto de registros, solo esos y nada más.

0

Sí Milan, no he especificado el nivel de aislamiento de la transacción. Supongo que es el nivel de aislamiento predeterminado que no sé cuál es. Ni en Oracle 11g, ni en SQL Server 2005.

Además, el INSERT que se hizo durante el comando WAIT (en la segunda conexión) fue NO dentro de una transacción. ¿Debería haber sido para evitar esta pérdida de datos?

+0

AFAIK, no se puede hacer nada sin transacción. Aunque no inició uno explícitamente, el servidor comenzó una implícita para ti. –

1

En Oracle, el nivel de aislamiento de transacción predeterminado se lee confirmado. Eso básicamente significa que Oracle devuelve los resultados tal como existían en el SCN (número de cambio del sistema) cuando comenzó su consulta. Establecer el nivel de aislamiento de la transacción en serializable significa que la SCN se captura al inicio de la transacción, por lo que todas las consultas en la transacción devuelven datos a partir de esa SCN. Eso garantiza resultados consistentes independientemente de lo que otras sesiones y transacciones estén haciendo. Por otro lado, puede haber un costo en que Oracle puede determinar que no puede serializar su transacción debido a la actividad que otras transacciones están realizando, por lo que tendría que manejar ese tipo de error.

El enlace de Tony a la discusión de AskTom entra en más detalles sobre todo esto-- Lo recomiendo encarecidamente.

0

Este es el comportamiento estándar del modo predeterminado de confirmación de lectura, como se mencionó anteriormente. El comando WAIT simplemente causa un retraso en el procesamiento, no hay ningún enlace para el manejo de transacciones DB.

Para solucionar el problema, puede:

  1. establecer el nivel de aislamiento en serializable, pero entonces se puede obtener errores ORA, que es necesario para manejar con reintentos! Además, puede obtener un golpe de rendimiento serio.
  2. utilizar una tabla temporal para almacenar los valores de primer
  3. si los datos no es demasiado grande para caber en la memoria, se puede utilizar una cláusula de regresar a granel Tomar en una tabla anidada y borrar sólo si la fila está presente en la tabla anidada
0
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 puede probar mediante la inserción de nuevas filas como se define por Oracle: - Una lectura fantasma se produce cuando la transacción A recupera un conjunto de filas que satisface una condición dada, la transacción B posteriormente inserta o actualiza una fila de modo que la fila ahora cumple la condición en la transacción A, y la transacción A repite más adelante la recuperación condicional. La transacción A ahora ve una fila adicional. Esta fila se conoce como un fantasma. Evitará el escenario anterior y también he usado TRANSACTION_SERIALIZABLE. Fijará el bloqueo más estricto en el Oracle. Oracle solo admite 2 tipos de niveles de aislamiento de transacción: TRANSACTION_READ_COMMITTED y TRANSACTION_SERIALIZABLE.

Cuestiones relacionadas