source

서로 다른 Oracle DB 연결 간의 공유 트랜잭션

manysource 2023. 6. 19. 21:43

서로 다른 Oracle DB 연결 간의 공유 트랜잭션

그 문제에 대해 조사하기 위해 며칠이 지난 후, 저는 지금 일어나고 있는 일에 의미가 없기 때문에 이 질문을 제출하기로 결정했습니다.

더 케이스

내 컴퓨터가 로컬 Oracle Express 데이터베이스로 구성되어 있습니다.저는 부모 클래스를 확장하는 몇 가지 Junit Test가 있는 JAVA 프로젝트가 있습니다("모범 사례"가 아닌 것으로 알고 있습니다). 이 프로젝트는 @Before 메서드에서 OJDBC 연결(10개 연결의 정적 Hikari 연결 풀 사용)을 열고 @After에서 롤백합니다.

public class BaseLocalRollbackableConnectorTest {
private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
protected Connection connection;

@Before
public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
}

@After
public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback();
    logger.debug("Close connection");
    connection.close();
}

정적 연결 풀

public class StaticConnectionPool {

private static HikariDataSource ds;

private static final Logger log = LoggerFactory.getLogger(StaticConnectionPool.class);

public static Connection getPooledConnection() throws SQLException {

    if (ds == null) {
        log.debug("Initializing ConnectionPool");
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(10);
        config.setDataSourceClassName("oracle.jdbc.pool.OracleDataSource");
        config.addDataSourceProperty("url", "jdbc:oracle:thin:@localhost:1521:XE");
        config.addDataSourceProperty("user", "MyUser");
        config.addDataSourceProperty("password", "MyPsw");
        config.setAutoCommit(false);
        ds = new HikariDataSource(config);

    }
    return ds.getConnection();

}

}

이 프로젝트에는 이 연결(로컬 호스트)을 사용하여 Sql2o를 사용하여 쿼리(삽입/업데이트 및 선택)를 실행하는 수백 개의 테스트(병렬되지 않음)가 있지만, 트랜잭션 및 연결 클로저는 외부에서만 관리됩니다(위 테스트에 의해).데이터베이스가 완전히 비어 있어 ACID 테스트를 수행할 수 없습니다.

따라서 예상되는 결과는 DB에 무언가를 삽입하고, 주장을 한 다음 롤백하는 것입니다.이러한 방식으로 두 번째 테스트는 분리 레벨을 유지하기 위해 이전 테스트에 의해 추가된 데이터를 찾지 않습니다.

문제 모든 테스트를 함께(순차적으로) 실행하는 경우 90%의 테스트가 제대로 작동합니다.이전 검정에 의해 데이터베이스에 더티 데이터(예: 고유하게 복제됨)가 있기 때문에 랜덤하게 10% 하나 또는 두 개의 검정이 실패합니다.로그를 보면 이전 테스트의 롤백이 제대로 수행되었습니다.실제로 데이터베이스를 확인하면 비어 있습니다.) 성능은 더 높지만 동일한 JDK, 동일한 Oracle DB XE를 가진 서버에서 이 테스트를 실행하면 이 실패율이 50%로 증가합니다.

테스트마다 연결이 다르고 롤백이 매번 호출되기 때문에 매우 이상하고 잘 모르겠습니다.JDBC Isolation 수준은 READ COMMITED이므로 동일한 연결을 사용하더라도 문제가 발생하지 않습니다.그래서 제 질문은:왜 그런 일이 일어날까요?당신은 알기라도 하나요?JDBC 롤백은 제가 알기로는 동기식인가요, 아니면 완전히 완료되지 않았는데도 진행될 수 있는 경우도 있을 수 있나요?

이것들은 나의 주요 DB 매개변수입니다: 프로세스 100 세션 172 트랜잭션 189

저는 2-3년 전에 같은 문제에 부딪힌 적이 있습니다(이 문제를 해결하기 위해 많은 시간을 들였습니다).문제는 @Before와 @After가 항상 실제로 순차적인 것은 아니라는 것입니다.[디버깅에서 프로세스를 시작하고 주석이 달린 메서드에 중단점을 몇 개 배치하여 이를 시도할 수 있습니다.

편집: 저는 토니오가 지적한 것처럼 충분히 명확하지 않았습니다.@Before와 @After의 순서는 테스트 전후의 실행 측면에서 보장됩니다.문제는 가끔 @Before와 @After가 엉망이 된 제 경우였습니다.

예상:

@전 -> test1() -> @After -> @Before -> @test2() -> @After

하지만 가끔 저는 다음과 같은 순서를 경험했습니다.

@전 -> test1() -> @Before -> @After -> @test2() -> @After

나는 그것이 버그인지 아닌지 확신할 수 없습니다.그 당시 저는 그 깊이를 파고들었고 그것은 일종의 (프로세서?) 스케줄링 관련 마법처럼 보였습니다.이 문제에 대한 해결책은 단일 스레드에서 테스트를 실행하고 초기화 및 정리 프로세스를 수동으로 호출하는 것이었습니다.이와 같은 것:

public class BaseLocalRollbackableConnectorTest {
    private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
    protected Connection connection;

    public void setup() throws SQLException{
        logger.debug("Getting connection and setting autocommit to FALSE");
        connection = StaticConnectionPool.getPooledConnection();
    }

    public void teardown() throws SQLException{ 
        logger.debug("Rollback connection");
        connection.rollback();
        logger.debug("Close connection");
        connection.close();
    }

    @Test
    public void test() throws Exception{
        try{
            setup();
            //test
        }catch(Exception e){ //making sure that the teardown will run even if the test is failing 
            teardown();
            throw e;
        }
        teardown();
    }
}

나는 그것을 테스트하지 않았지만 훨씬 더 우아한 해결책은 동일한 객체에서 @Before 및 @After 메서드를 동기화하는 것일 수 있습니다.시도해 볼 수 있는 기회가 있다면 업데이트 부탁드립니다.:)

저는 그것이 당신의 문제도 해결되기를 바랍니다.

테스트를 순서대로 완료하기 위해 성능에 관계없이 문제를 "해결"(예: "베스트 프랙티스"가 아님)해야 하는 경우:

config.setMaximumPoolSize(1);

테스트 대기열의 테스트가 차례를 기다리고 시간이 초과될 수 있으므로 시간 초과를 더 높게 설정해야 할 수 있습니다.일반적으로 이러한 솔루션을 제안하지는 않지만 설정이 차선이기 때문에 경쟁 상태와 데이터 손실로 이어질 수 있습니다.하지만, 시험에 행운을 빌어요.

Oracle의 모든 문에 대해 감사를 구성해 보십시오.그런 다음 동시에 작동하는 세션을 찾습니다.입니다.JDBC 롤백은 동기식입니다.커밋은 다음과 같이 구성할 수 있습니다.commit nowait하지만 당신은 시험에서 특별하게 하지 않는 것 같아요.

병렬 dml에도 주의하십시오.동일한 트랜잭션의 한 테이블에서 Ora-12838을 얻기 때문에 커밋 없이 병렬 dml + 다른 dml을 수행할 수 없습니다.

자율적인 거래가 있습니까?테스트의 비즈니스 로직은 수동으로 롤백할 수 있으며 테스트 중에 자율 트랜잭션은 다른 세션과 같으며 상위 세션의 커밋을 볼 수 없습니다.

이 방법으로 문제가 해결될지는 확실하지 않지만 다음을 시도해 볼 수 있습니다.

public class BaseLocalRollbackableConnectorTest {
  private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class);
  protected Connection connection;
  private Savepoint savepoint;

  @Before
  public void setup() throws SQLException{
    logger.debug("Getting connection and setting autocommit to FALSE");
    connection = StaticConnectionPool.getPooledConnection();
    savepoint = connection.setSavepoint();
  }

  @After
  public void teardown() throws SQLException{ 
    logger.debug("Rollback connection");
    connection.rollback(savepoint);
    logger.debug("Close connection");
    connection.close();
    while (!connection.isClosed()) {
      try { Thread.sleep(500); } catch (InterruptedException ie) {}
    }
}

실제로 수영장으로 돌아가기 전에 연결이 닫혀 있는지 확인하기 위해 닫기 후 루프하는 두 가지 '수정'이 있습니다.둘째, 테스트 전에 저장 지점을 생성하고 이후에 복원합니다.

다른 모든 답변이 지적했듯이 제공된 정보에 무엇이 문제가 있는지 말하기 어렵습니다.또한 감사를 통해 현재 문제를 찾았다고 해도 테스트에 데이터 오류가 없는 것은 아닙니다.

그러나 이미 빈 데이터베이스 스키마가 있으므로 SQL 파일로 내보낼 수 있습니다.각 테스트 전에:

  1. 스키마 삭제
  2. 스키마 다시 만들기
  3. 샘플 데이터 공급(필요한 경우)

디버깅 시간을 많이 절약할 수 있습니다. 테스트를 실행할 때마다 데이터베이스가 원래 상태인지 확인하십시오.이 모든 작업을 스크립트로 수행할 수 있습니다.

참고: Oracle Enterprise에는 사용자의 작업을 지원하는 플래시백 기능이 있습니다.또한 Hibernate 등을 사용할 수 있다면 테스트 속도를 높이고 데이터 세트의 일관성을 유지하는 데 사용할 수 있는 다른 메모리 내 데이터베이스(HSQLDB 등)도 있습니다.

편집: 불가능해 보이지만, 만약을 위해:connection.rollback()호출하지 않는 경우에만 적용됩니다.commit그 앞에 ().

당신의 답변에서 제가 유닛 테스트에서의 롤백과 거래 행동에 화가 나지 않았다는 것을 확인한 후, 저는 모든 쿼리와 가능한 모든 원인을 깊이 확인했고 다행히도 그렇습니다..부끄러워도 마음을 자유롭게 합니다) 모두 예상대로 작동합니다(트랜잭션, Before, After 등).

단일 행 정보를 식별하기 위해 일부 복잡한 뷰(DAO 계층에 근본적으로 심층적으로 구성됨)의 결과를 얻는 몇 가지 쿼리가 있습니다.이 보기는 다음을 기반으로 합니다.MAX of a TIMESTAMP특정 이벤트의 최신(실생활에서 몇 달 후에 발생하는 이벤트)을 식별하기 위해.

단위 테스트를 진행하기 위해 데이터베이스를 준비하면 각 테스트에 의해 이러한 이벤트가 순차적으로 추가됩니다.경우에 따라 동일한 트랜잭션에서 이러한 삽입 쿼리가 특히 빠른 경우 동일한 개체와 관련된 이벤트가 동일한 밀리초 내에 추가되고(TIMESTAMP는 JODA DateTime을 사용하여 수동으로 추가됨), 날짜의 MAX는 두 개 이상의 값을 반환합니다.이러한 이유로 성능이 우수한 컴퓨터/서버에서는 느린 컴퓨터/서버보다 이러한 현상이 더 자주 발생한다는 사실이 설명됩니다.이 보기는 더 많은 테스트에서 사용되며 테스트에 따라 오류가 다르고 임의입니다(NULL 값이 기본 키로 추가됨, 중복된 기본 키 등).

예: 다음과 같은 경우INSERT SELECT쿼리는 이 버그가 분명합니다.

INSERT INTO TABLE1 (ID,COL1,COL2,COL3) 
  SELECT :myId, T.VAL1, T.VAL2, T.VAL3 
  FROM MyView v 
  JOIN Table2 t on t.ID = v.ID
  WHERE ........

매개 변수 myId는 이후에 Sql2o 매개 변수로 추가됩니다.

마이뷰는

SELECT ID, MAX(MDATE) FROM TABLEV WHERE.... GROUP BY ...

보기가 동일한 최대 날짜로 인해 최소 2개의 결과를 반환하는 경우 ID가 고정되어 있기 때문에 실패합니다(처음에는 시퀀스로 생성되지만 두 번째에는 매개 변수를 사용하여 저장됨).위반된 PK 제약 조건이 생성됩니다.

이것은 단지 하나의 사건이지만 무작위적인 행동으로 인해 저와 제 동료들을 미치게 합니다...

이러한 이벤트 삽입 사이에 1밀리초의 절전 모드를 추가하면 고정됩니다.비록 이 경우(동일한 밀리초에 두 번 상호 작용하는 사용자)는 생산 시스템에서 발생할 수 없지만, 중요한 것은 평소와 같은 마법이 발생하지 않는다는 것입니다.

이제 저를 모욕하셔도 됩니다 :)

@after 문에서 사용하는 대신 작업을 커밋한 동일한 위치에서 최대 풀 크기의 연결 수를 늘리고 작업을 롤백할 수 있습니다.효과가 있기를 바랍니다.

언급URL : https://stackoverflow.com/questions/38197158/shared-transaction-between-different-oracledb-connections