Генерация идентификатора транзакции для баз данных в памяти

#database #h2

#База данных #h2

Вопрос:

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

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

Ответ №1:

Как насчет использования последовательностей вместо идентификаторов транзакций?

 CREATE SEQUENCE SEQ;
  

Первая операция в транзакции устанавливает переменную сеанса следующим образом:

 SET @TID = SEQ.NEXTVAL;
  

Другие операции в рамках этой транзакции используют переменную сеанса:

 CALL @TID;
  

Комментарии:

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

2. Извините, мой ответ неверен, потому что CURRVAL изменится, если вызовут другие сеансы NEXTVAL . Итак, это не работает… Вместо этого, если CURRVAL , можно было бы использовать переменные сеанса ( SET @TID = SEQ.NEXTVAL ).

3. Это выглядит многообещающе, но h2database.com/html/tutorial.html#user_defined_variables наводит меня на мысль, что определение переменной будет охватывать несколько транзакций (поскольку оно ограничено сеансом, а не транзакцией). Как мне убедиться, что каждая транзакция использует другой идентификатор?

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

Ответ №2:

Я нашел (очень хитрый) обходной путь:

 /**
 * Invoked when a transaction completes.
 */
public abstract class TransactionListener extends Value
{
    private boolean invoked;

    /**
     * Invoked when the transaction completes.
     */
    protected abstract void onCompleted();

    @Override
    public String getSQL()
    {
        return null;
    }

    @Override
    public int getType()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public long getPrecision()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public int getDisplaySize()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public String getString()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public Object getObject()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public void set(PreparedStatement prep, int parameterIndex) throws SQLException
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    protected int compareSecure(Value v, CompareMode mode)
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public int hashCode()
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public boolean equals(Object other)
    {
        throw new AssertionError("Unexpected method invocation");
    }

    @Override
    public boolean isLinked()
    {
        return !invoked;
    }

    @Override
    public void close()
    {
        invoked = true;
        onCompleted();
    }
}

// -------------TRIGGER BELOW-----------

public void fire(final Connection connection, ResultSet oldRow, ResultSet newRow)
    throws SQLException
{
    Statement statement = connection.createStatement();
    long transactionId;
    ResultSet rs = statement.executeQuery("SELECT @TRANSACTION_ID");
    try
    {
        rs.next();
        transactionId = rs.getLong(1);
        if (transactionId == 0)
        {
            // Generate a new transaction id
            rs.close();
            JdbcConnection jdbcConnection = (JdbcConnection) connection;
            final Session session = (Session) jdbcConnection.getSession();
            session.unlinkAtCommit(new TransactionListener()
            {
                @Override
                protected void onCompleted()
                {
                    boolean oldAutoCommit = session.getAutoCommit();
                    session.setAutoCommit(false);
                    try
                    {
                        Statement statement = connection.createStatement();
                        statement.executeQuery("SELECT SET(@TRANSACTION_ID, NULL)");
                        statement.close();
                    }
                    catch (SQLException e)
                    {
                        throw new AssertionError(e);
                    }
                    finally
                    {
                        session.setAutoCommit(oldAutoCommit);
                    }
                }
            });
            rs = statement.executeQuery("SELECT SET(@TRANSACTION_ID, "
                  "audit_transaction_sequence.NEXTVAL)");
            rs.next();
            transactionId = rs.getLong(1);
        }
    }
    finally
    {
        rs.close();
    }
    assert (transactionId != 0);
    // ...
}
  

Вот как это работает:

  • Мы используем Session.unlinkAtCommit () для прослушивания фиксаций транзакций (я предполагаю, что это также отслеживает откаты, но я еще не проверял это)
  • Поскольку мы не можем предсказать количество и порядок вызова триггера, мы должны выполнить следующую проверку в каждом отдельном триггере:

    1. Если @TRANSACTION_ID равно нулю, зарегистрируйте новый прослушиватель событий и увеличьте последовательность.
    2. Если @TRANSACTION_ID значение не равно null, извлеките из него текущий идентификатор транзакции.

Две основные проблемы, связанные с этим обходным путем, заключаются в:

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

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