문제

나는 SQL 문제로 내 동료들을 돕고있었습니다. 주로 그들은 모든 행을 표 A에서 표 B로 옮기고 싶었습니다 (두 테이블은 동일한 열 (이름과 유형)을 갖습니다). 이것은 Oracle 11G에서 이루어졌지만 실제로는 중요하지 않다고 생각합니다.

그들의 초기 순진한 구현은 비슷했습니다

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

그들의 우려는 A에서 B로 복사하는 동안 표 A에 삽입 된 인서트가 있고 "삭제"(또는 가치가있는 것에 대한 자르기)가 데이터 손실 (최신 삽입 된 행을 삭제 된 경우)을 유발할 것입니다.

물론 나는 복사 된 행의 ID를 임시 테이블에 저장 한 다음 임시 테이블의 ID와 일치하는 행에 행만 삭제하는 것을 신속하게 추천했습니다.

그러나 호기심을 위해 삽입과 삭제 사이에 대기 명령 (PL/SQL 구문을 기억하지 않음)을 추가하여 약간의 테스트를 시작했습니다. 그런 다음 다른 연결에서 행을 삽입합니다 기다리는 동안.

우리는 그것이 그렇게함으로써 데이터 손실이라는 것을 관찰했습니다. SQL Server의 전체 컨텍스트를 재현하고 트랜잭션에서 모든 것을 마무리했지만 SQL Server에서도 새로운 새로운 데이터도 손실되었습니다. 이로 인해 초기 접근 방식에 체계적인 오류/결함이 있다고 생각했습니다.

그러나 트랜잭션이 신선한 새로운 삽입물에서 분리되어 있지 않거나 대기 명령 중에 인서트가 나왔다는 사실이 사실인지 알 수 없습니다.

결국 그것은 나에게 제안한 임시 테이블을 사용하여 구현되었지만 "데이터 손실"에 대한 답을 얻을 수 없었습니다. 왜 그런지 아십니까?

도움이 되었습니까?

해결책

격리 수준에 따라 테이블에서 모든 행을 선택해도 새 인서트를 방지하지는 않지만 읽은 행을 잠그게합니다. SQL Server에서 직렬화 가능한 격리 레벨을 사용하면 선택한 쿼리에 포함 된 경우 새 행이 방지됩니다.

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

직렬화 가능한 다음을 지정합니다.

  • 명세서는 수정되었지만 아직 다른 거래에 의해 커밋되지 않은 데이터를 읽을 수 없습니다.

  • 현재 트랜잭션이 완료 될 때까지 현재 트랜잭션에서 읽은 데이터를 수정할 수 없습니다.

  • 다른 트랜잭션은 현재 트랜잭션이 완료 될 때까지 현재 트랜잭션의 명령문에 의해 읽는 키 범위에 속하는 키 값으로 새 행을 삽입 할 수 없습니다.

다른 팁

트랜잭션 안정성에 대해서는 말할 수 없지만 대체 접근 방식은 존재하는 소스 테이블에서 두 번째 단계를 삭제하는 것입니다 (대상 테이블에서 ID를 선택).

구문을 용서하면이 코드를 테스트하지는 않았지만 아이디어를 얻을 수 있어야합니다.

INSERT INTO B SELECT * FROM A;

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

이렇게하면 관계형 엔진을 사용하여 최신 데이터가 삭제되지 않으며 트랜잭션에서 두 단계를 수행 할 필요가 없습니다.

업데이트 : 하위 쿼리의 구문 수정

이것은 다음을 사용하여 Oracle에서 달성 할 수 있습니다.

Alter session set isolation_level=serializable;

Execute ormeriate를 사용하여 PL/SQL로 설정할 수 있습니다.

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

보다 Tom : 거래 격리 수준에서 문의하십시오

거래가 작동하는 방식 일뿐입니다. 당면한 작업에 대한 올바른 격리 수준을 선택해야합니다.

동일한 거래에서 삽입 및 삭제를하고 있습니다. 격리 모드 트랜잭션이 사용중인 언급은 없지만 아마도 '저 커밋 된'일 것입니다. 이는 삭제 명령이 그 동안 커밋 된 레코드를 볼 수 있음을 의미합니다. 이런 종류의 작업의 경우 '스냅 샷'유형의 트랜잭션을 사용하는 것이 훨씬 좋습니다. 삽입 및 삭제는 동일한 레코드 세트에 대해 알 수 있기 때문입니다.

이것이 관련이 있는지 모르겠지만 SQL Server에서 구문은

begin tran
....
commit

'시작'뿐만 아니라

다른 트랜잭션의 인서트가 거래에 영향을 미치지 않도록 트랜잭션 격리 수준을 설정해야합니다. 오라클에서 어떻게하는지 모르겠습니다.

Oracle에서는 기본 트랜잭션 격리 수준이 커밋됩니다. 이는 기본적으로 Oracle이 쿼리를 시작했을 때 SCN (시스템 변경 번호)에 존재하는 결과를 반환한다는 것을 의미합니다. 트랜잭션 격리 수준을 직렬화 가능한 것으로 설정하면 트랜잭션 시작시 SCN이 캡처되어 해당 트랜잭션의 모든 쿼리가 해당 SCN에 따라 데이터를 반환합니다. 다른 세션 및 거래가 수행하는 일에 관계없이 일관된 결과를 보장합니다. 다른 한편으로, Oracle은 다른 거래가 수행하는 활동으로 인해 거래를 일련화 할 수 없다고 판단 할 수 있으므로 이러한 종류의 오류를 처리해야합니다.

Tony의 AskTom 토론에 대한 링크는이 모든 것에 대해 더 자세히 설명합니다. 나는 그것을 강력히 추천합니다.

예 밀라노, 거래 격리 수준을 지정하지 않았습니다. 나는 그것이 내가 무엇인지 모르는 기본 격리 수준이라고 생각합니다. Oracle 11G 나 SQL Server 2005에도 없습니다.

또한 대기 명령 (2 차 연결) 중에 만들어진 삽입 아니다 거래 내부. 이 데이터 손실을 방지해야했을까요?

이것은 위에서 언급 한 것처럼 기본 판독 모드의 표준 동작입니다. 대기 명령은 처리가 지연되며 DB 트랜잭션 처리에 대한 링크가 없습니다.

문제를 해결하려면 다음 중 하나도 할 수 있습니다.

  1. 분리 레벨을 직렬화 가능하게 설정하면 오류를 얻을 수 있습니다. 또한 심각한 성능을 얻을 수 있습니다.
  2. 임시 테이블을 사용하여 값을 먼저 저장하십시오
  3. 데이터가 메모리에 맞지 않는 경우 리턴 조항을 사용하여 중첩 테이블에 대량으로 수집하고 중첩 테이블에있는 경우에만 삭제할 수 있습니다.

또는 스냅 샷 격리를 사용하여 분실 된 업데이트를 감지 할 수 있습니다.

스냅 샷 격리가 도움이 될 때 그리고 아프면

    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는 Oracle에 의해 정의 된대로 새 행을 삽입하여 테스트 할 수 있습니다 .- 팬텀 읽기는 트랜잭션 A가 주어진 조건을 만족시키는 행을 검색 할 때 발생합니다. 트랜잭션 B는 이제 행이 트랜잭션 A의 조건을 충족하도록 행을 삽입하거나 업데이트합니다. , 그리고 거래 a 나중에 조건부 검색을 반복합니다. 거래 a는 이제 추가 행이 나타납니다. 이 행을 팬텀이라고합니다. Transaction_Serializable을 사용했을뿐만 아니라 위의 시나리오를 피할 수 있습니다. 오라클에서 가장 엄격한 잠금 장치를 설정합니다. Oracle은 2 가지 유형의 트랜잭션 격리 수준 만 지원합니다.-Transaction_Read_Committed 및 Transaction_Serializable.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top