2008-09-29 2 views
7

Я помогал своим коллегам с проблемой SQL. В основном они хотели переместить все строки из таблицы A в таблицу B (обе таблицы имеют одинаковые столбцы (имена и типы)). Хотя это было сделано в Oracle 11g, я не думаю, что это действительно имеет значение.проблема изоляции транзакций или неправильный подход?

Их первая наивная реализация была что-то вроде

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

их озабоченность, если были Вставки в таблицу А во время копирования от А до В и «DELETE FROM A» (или TRUNCATE для того, что стоит) приведет к потере данных (с добавлением новых вставленных строк в A).

Конечно, я быстро рекомендовал хранить идентификаторы скопированных строк во временной таблице, а затем удалять только строки из A, которые соответствуют IDS во временной таблице.

Однако, ради любопытства, мы немного подходим к тестированию, добавляя команду ожидания (не помните синтаксис PL/SQL) между INSERT и DELETE. Тогда из другого соединения мы вставляем строки ВО ВРЕМЯ ЖДЕТ.

Мы заметили, что это было потерей данных. Я воспроизвел весь контекст в SQL Server и завернул все это в транзакцию, но все же свежие новые данные были потеряны и в SQL Server. Это заставило меня думать, что в первоначальном подходе происходит систематическая ошибка/недостаток.

Однако я не могу сказать, был ли тот факт, что TRANSACTION не был (каким-то образом) изолирован от новых новых INSERT или фактом, что INSERT пришли во время команды WAIT.

В конце концов, это было реализовано с использованием временной таблицы, предложенной мной, но мы не смогли получить ответ на вопрос «Почему потеря данных». Ты знаешь почему?

ответ

8

В зависимости от уровня изоляции, выбор всех строк из таблицы не предотвращает появление новых вставок, он просто блокирует прочитанные строки. В SQL Server, если вы используете уровень изоляции Serializable, это предотвратит новые строки, если они будут включать в ваш запрос выбора.

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

SERIALIZABLE Указывает следующее:

  • отчетность не может читать данные, которые были изменены, но еще не совершенные другими транзакциями.

  • Никакие другие транзакции не могут изменять данные, которые были прочитаны текущей транзакцией, до завершения текущей транзакции.

  • Другие транзакции не могут вставлять новые строки с ключевыми значениями, которые попадают в диапазон ключей, считываемых любыми операторами в текущей транзакции, до завершения текущей транзакции.

1

я не знаю, если это уместно, но в SQL Server синтаксис

begin tran 
.... 
commit 

не только «начать»

1

Вы должны установить уровень изоляции транзакций, так что вставки из другая транзакция не влияет на вашу транзакцию. Я не знаю, как это сделать в Oracle.

7

Я не могу говорить о стабильности транзакции, но альтернативный подход состоял бы в том, чтобы удалить второй этап из исходной таблицы, где существует (выберите идентификаторы из целевой таблицы).

Простите синтаксис, я не проверял этот код, но вы должны быть в состоянии получить идею:

INSERT INTO B SELECT * FROM A; 

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

Таким образом, вы используете реляционный двигатель для обеспечения, что никаких новых данные не будут удалены, и вам не нужно делать два шага в транзакции.

Обновление: исправлена ​​синтаксис подзапросов

+0

Я иду с @Guy. Гораздо лучше сделать все явным и очевидным. Выполнение этого с использованием SQL там, где это возможно, менее загадочно, чем использование уровней изоляции (если вы можете это сделать, конечно). – 2008-09-30 09:35:27

+0

Синтаксис неверен: «... где существует (выберите B. из B, где B. = A. ) – 2009-07-05 11:29:06

5

Это может быть достигнуто в Oracle с помощью:

Alter session set isolation_level=serializable; 

Это может быть установлено в PL/SQL с использованием EXECUTE IMMEDIATE:

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

См Ask Tom: On Transaction Isolation Levels

2

Это как раз то, как работают транзакции. Вы должны выбрать правильный уровень изоляции для задачи.

Вы делаете INSERT и DELETE в той же транзакции. Вы не говорите, что транзакция режима изоляции используется, но это, вероятно, «прочитано». Это означает, что команда DELETE увидит записи, которые были зафиксированы в то же время. Для такой работы гораздо лучше использовать транзакцию типа «моментальный снимок», потому что тогда как INSERT, так и DELETE будут знать о том же наборе записей - только те и ничего.

0

Да, Милан, я не указал уровень изоляции транзакции. Я полагаю, что это уровень изоляции по умолчанию, который я не знаю, что это такое. Ни в Oracle 11g, ни в SQL Server 2005.

Кроме того, INSERT, который был выполнен во время команды WAIT (по второму соединению), состоял из NOT внутри транзакции. Должно было быть, чтобы предотвратить эту потерю данных?

1

В Oracle уровень изоляции транзакций по умолчанию считается прочитанным. Это в основном означает, что Oracle возвращает результаты, как они существовали в SCN (номер изменения системы), когда ваш запрос запущен. Установка уровня изоляции транзакций на сериализуемое означает, что SCN фиксируется в начале транзакции, поэтому все запросы в вашей транзакции возвращают данные с этого SCN. Это обеспечивает согласованные результаты независимо от того, что делают другие сеансы и транзакции. С другой стороны, может быть и стоимость того, что Oracle может определить, что он не может сериализовать транзакцию из-за активности, которую выполняют другие транзакции, поэтому вам придется обрабатывать такую ​​ошибку.

Ссылка Тони на обсуждение AskTom содержит более подробную информацию обо всем этом - я очень рекомендую его.

0

Это стандартное поведение режима чтения по умолчанию, как указано выше. Команда WAIT просто задерживает обработку, нет никакой связи с обработкой транзакций БД.

Чтобы устранить эту проблему, вы можете:

  1. установить уровень изоляции сериализации, но тогда вы можете получить ORA- ошибки, которые нужно обрабатывать с повторами! Кроме того, вы можете получить серьезный удар производительности.
  2. используйте временную таблицу для хранения значений сначала
  3. Если данные не слишком велики, чтобы вписаться в память, вы можете использовать предложение RETURNING для BULK COLLECT IN Вложенную таблицу и удалять, только если строка присутствует в вложенную таблицу.
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 может проверить его, вставив новые строки, как это определенно оракулом: - Фантом чтения возникает, когда транзакция A извлекает набор строк, удовлетворяющих заданному условию, транзакция B впоследствии вставляет или обновляет строку, так что строка теперь удовлетворяет условию транзакции A, а транзакция A позже повторяет условное извлечение. Транзакция A теперь видит дополнительную строку. Эта строка называется фантомом. Это позволит избежать описанного выше сценария, а также я использовал TRANSACTION_SERIALIZABLE. Он установит самый строгий замок на Oracle. Oracle поддерживает только 2 типа уровней изоляции транзакций: - TRANSACTION_READ_COMMITTED и TRANSACTION_SERIALIZABLE.

Смежные вопросы