Подтвердить что ты не робот

Совместная транзакция между различными соединениями OracleDB

После нескольких дней, проведенных для расследования вопроса, я решил представить этот вопрос, потому что нет никакого смысла в том, что происходит.

Дело

Мой компьютер настроен с локальной базой данных Oracle Express. У меня есть проект JAVA с несколькими JUnit Tests, которые расширяют родительский класс (я знаю, что это не "лучшая практика" ), которая открывает соединение OJDBC (используя статический пул соединений Hikari из 10 подключений) в методе @Before и свернута Верните его в @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();
}

StacicConnectionPool

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.

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

Проблема Выполнение всех тестов вместе (последовательно), в 90% случаев они работают правильно. 10% один или два теста, случайным образом, терпят неудачу, поскольку в базе данных (например, дублируются уникальные данные) есть грязные данные (например, дублированные). просмотр журналов, откаты предыдущих тестов были выполнены правильно. На самом деле, если я проверю базу данных, она пуста) Если я буду выполнять эти тесты на сервере с более высокой производительностью, но тот же JDK, такой же Oracle DB XE, этот коэффициент отказа увеличен до 50%.

Это очень странно, и я понятия не имею, потому что соединения разные между тестами, и откатывается каждый раз. Уровень изоляции JDBC READ COMMITTED, поэтому, даже если мы использовали одно и то же соединение, это не должно создавать никаких проблем даже при использовании одного и того же соединения. Поэтому мой вопрос: Почему это происходит? у тебя есть идеи? Является ли откат JDBC синхронным, как я знаю, или могут быть некоторые случаи, когда он может продвигаться вперед, хотя он не полностью завершен?

Это мои основные параметры базы данных: процессы 100 сессии 172 сделки 189

4b9b3361

Ответ 1

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

Есть несколько запросов, которые получают результат некоторых сложных представлений (и глубоко глубоко настроенных на уровень 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

MyView -

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

Когда представление возвращает хотя бы 2 результата из-за одной и той же Макс. даты, он терпит неудачу, потому что идентификатор является фиксированным (генерируется последовательностью в начале, но сохраняется с использованием параметра во второй раз). Это порождает нарушение PK.

Это только один случай, но заставил меня (и моих коллег) сумасшедшим из-за этого случайного поведения...

Добавление спая в 1 миллисекунду между этими событиями вставляется, оно фиксировано. теперь мы работаем над тем, чтобы найти другое решение, хотя этот случай (пользователь, который взаимодействует два раза в той же миллисекунде) не может произойти в производственной системе  но важно то, что никакая магия не происходит, как обычно!

Теперь вы можете оскорбить меня:)

Ответ 2

Я столкнулся с той же проблемой 2-3 года назад (я потратил много времени, чтобы получить это прямо). Проблема в том, что @Before и @After не всегда действительно последовательны. [Вы можете попробовать это, начав процесс в отладке и поместите некоторые точки останова в аннотированных методах.

Изменить: Я не был достаточно ясен, как отметил Tonio. Порядок @Before и @After гарантируется с точки зрения работы перед тестом и после этого. Проблема была в моем случае, что иногда @Before и @After были испорчены.

Ожидаемое:

@Before → test1() → @After → @Before → @test2() → @After

Но иногда я испытывал следующий порядок:

@Before → 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 на одном и том же объекте. Пожалуйста, уточните меня, если у вас есть чанс, чтобы попробовать.:)

Я надеюсь, что это тоже решит вашу проблему.

Ответ 3

Если ваша проблема просто должна быть "решена" (например, не "лучшая практика" ), независимо от производительности, чтобы просто завершить тестирование, попробуйте установить:

config.setMaximumPoolSize(1);

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

Ответ 4

Попробуйте настроить аудит для всех операторов в Oracle. Затем найдите сеансы, которые живут одновременно. Я думаю, что в тестах есть проблема. Откат JDBC является синхронным. Commit может быть настроен как commit nowait, но я не думаю, что вы делаете это специально для своих тестов.

Также обратите внимание на параллельный dml. На одной таблице в той же транзакции вы не можете выполнять параллельный dml + любой другой dml без фиксации, потому что вы получаете Ora-12838.

Есть ли у вас аутоавтоматическая транзакция? Бизнес-логика в тестах может вручную отменить их, и во время тестов autonoumous transaction похож на другой сеанс, и он не видит никаких коммитов из родительского сеанса.

Ответ 5

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

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) {}
    }
}

На самом деле есть два "исправления" - цикл после закрытия, чтобы убедиться, что соединение закрыто перед возвратом в пул. Во-вторых, создайте точку сохранения перед тестом и затем восстановите ее.

Ответ 6

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

Но здесь альтернатива: поскольку у вас уже есть пустая схема базы данных, вы можете экспортировать ее в файл SQL. Затем перед каждым тестом:

  • Отбросить схему
  • Снова заново создайте схему.
  • Подайте данные образца (при необходимости)

Это позволит сэкономить много времени на отладку, убедитесь, что база данных в ее первозданном состоянии каждый раз, когда вы запускаете тесты. Все это можно сделать в script.

Примечание. Oracle Enterprise имеет функцию flashback для поддержки вашего вида операции. Кроме того, если вам удастся использовать Hibernate и другие, в других базах памяти (например HSQLDB), который вы можете использовать для увеличения скорости тестирования и поддержания согласованности в вашем наборе данных.

РЕДАКТИРОВАТЬ: Это кажется неправдоподобным, но на всякий случай: connection.rollback() вступает в силу, если вы не вызываете commit() перед этим.

Ответ 7

Вы можете сделать одну вещь, увеличив ее. соединений в максимальном размере пула и откат операции в том же месте, где вы совершили операцию, вместо того, чтобы использовать ее в операторе @после. Надеюсь, что это сработает.