Pergunta

Eu estava ajudando alguns colegas meus com um problema de SQL. Principalmente eles queriam mover todas as linhas da tabela A com a tabela B (ambas as tabelas com as mesmas colunas (nomes e tipos)). Embora isso foi feito no Oracle 11g Eu não acho que realmente importa.

A sua implementação ingênua inicial era algo como

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

A sua preocupação era se havia inserções feitas à mesa A durante a cópia de A para B e "DELETE FROM A" (ou TRUNCATE para o que valeu a pena) iria causar perda de dados (tendo as linhas inseridas mais recentes em um excluído).

Claro que eu rapidamente recomendado armazenar as identificações das linhas copiadas em uma tabela temporária e, em seguida, excluindo apenas as linhas em uma que combinava com os IDS na tabela temporária.

No entanto, por causa da curiosidade nós colocamos um pequeno teste, adicionando um comando de espera (não me lembro o PL / SQL sintaxe) entre INSERT e DELETE. Em seguida, a partir de uma conexão diferente que iria inserir linhas Durante a espera .

Observamos que era uma perda de dados ao fazê-lo. I reproduzido todo o contexto no SQL Server e envolveu tudo em uma transação, mas ainda os novos dados frescos foi perdido também no SQL Server. Isso me fez pensar que há um erro sistemático / falha na abordagem inicial.

No entanto eu não posso dizer se era o fato de que a transação não foi (de alguma forma?) Isolada das novas pastilhas frescas ou o fato de que as inserções veio durante o comando WAIT.

No final, foi implementado usando a tabela temporária sugerida por mim, mas não conseguimos obter a resposta à pergunta "Por que a perda de dados". Você sabe por que?

Foi útil?

Solução

Dependendo do seu nível de isolamento, a seleção de todas as linhas de uma tabela não impede novas pastilhas, ele vai apenas bloquear as linhas que você lê. No SQL Server, se você usar o nível de isolamento Serializable, então ele irá prevenir novas linhas, se eles teriam sido inclusive em sua consulta de selecção.

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

SERIALIZABLE Especifica o seguinte:

  • As declarações podem não ler os dados que foram modificados, mas ainda não cometidos por outras transações.

  • Não há outras transações pode modificar os dados que foram lidos pela transação atual até que os concluída atual transação.

  • Outras transações não pode inserir novas linhas com valores de chave que se enquadram na faixa de chaves lido por qualquer declaração na transação atual até que os concluída atual transação.

Outras dicas

Eu não posso falar para a estabilidade transação, mas uma abordagem alternativa seria a de ter o segundo passo excluir da tabela de origem, onde existe (selecione ids de tabela de destino).

Perdoe a sintaxe, eu não testei este código, mas você deve ser capaz de obter a idéia:

INSERT INTO B SELECT * FROM A;

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

Dessa forma, você está usando o mecanismo relacional para impor que há dados mais recentes será excluído, e você não precisa fazer as duas etapas em uma transação.

Update: corrigido sintaxe na subconsulta

Isto pode ser conseguido no Oracle usando:

Alter session set isolation_level=serializable;

Isto pode ser definido em PL / SQL usando EXECUTE IMMEDIATE:

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

Pergunte Tom: Na transação de isolamento níveis

É apenas o trabalho transações maneira. Você tem que escolher o nível de isolamento correto para a tarefa em mãos.

Você está fazendo a inserção e eliminação na mesma transação. Você não menciona o modo de isolamento da transação está usando, mas provavelmente é 'read committed'. Isto significa que o comando DELETE verá os registros que foram cometidos no mesmo período. Para este tipo de trabalho, é muito melhor para uso 'instantâneo' tipo de transação, porque então ambos inserir e excluir saberia sobre o mesmo conjunto de registros -. Apenas aqueles e nada mais

Eu não sei se isso é relevante, mas no SQL Server a sintaxe é

begin tran
....
commit

não apenas 'começar'

Você precisa definir o nível de isolamento de transação para que as inserções de outra transação não afetam sua transação. Eu não sei como fazer isso no Oracle.

Em Oracle, o nível de isolamento de transação padrão é lido comprometido. Isso significa, basicamente, que a Oracle retorna os resultados como eles existiam no SCN (número de alteração do sistema) quando sua consulta começou. Definir o nível de isolamento de transação para meios serializáveis ??que o SCN é capturado no início da transação para todas as consultas em seus dados de retorno de transação como de que SCN. Que garante resultados consistentes, independentemente do que outras sessões e transações estão fazendo. Por outro lado, pode haver um custo em que a Oracle pode determinar que ele não pode serializar sua transação por causa da atividade que outras operações estão realizando, assim você teria que lidar com esse tipo de erro.

link de Tony para a discussão AskTom vai para substancialmente mais detalhes sobre todos isto-- Eu recomendo-lo.

Sim Milan, eu não especificou o nível de isolamento da transação. Acho que é o nível de isolamento padrão, que eu não sei qual é. Nem no Oracle 11g ou no SQL Server 2005.

Além disso, o INSERT que foi feito durante o comando de espera (no 2º conexão) foi não dentro de uma transação. Deveria tê-lo sido para evitar essa perda de dados?

Este é o comportamento padrão do padrão de leitura comprometida modo, como mencionado acima. O comando WAIT apenas provoca um atraso no processamento, não há nenhuma ligação a qualquer manipulação de transações DB.

Para corrigir o problema, você pode:

  1. definir o nível de isolamento para ser serializado, mas então você pode obter erros ora-, o que você precisa para lidar com novas tentativas! Além disso, você pode obter um sério impacto na performance.
  2. usar uma tabela temporária para armazenar os valores first
  3. Se os dados não é muito grande para caber na memória, você pode usar uma cláusula retornando ao BULK COLLECT INTO uma tabela aninhada e excluir somente se a linha está presente na tabela aninhada.

Como alternativa, você pode usar o isolamento de instantâneo para detectar atualizações perdidas:

Quando Snapshot Isolation ajuda e quando fere

    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 pode testá-lo através da inserção de novas linhas, como definido pela Oracle: - Uma leitura fantasma ocorre quando a transação A recupera um conjunto de linhas satisfazer uma determinada condição, a transação B, posteriormente, inserções ou atualiza uma linha de tal forma que a linha agora satisfaz a condição da transação A, e a transação A repete mais tarde a recuperação condicional. Transação A vê agora uma linha adicional. Esta linha é referido como um fantasma. Ele vai evitar o cenário acima, bem como tenho TRANSACTION_SERIALIZABLE usado. Ele irá definir o bloqueio mais rigorosa sobre a Oracle. A Oracle suporta apenas 2 tipos de níveis de isolamento de transação: - TRANSACTION_READ_COMMITTED e TRANSACTION_SERIALIZABLE.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top