【问题标题】:Generating transaction id for in-memory databases为内存数据库生成事务 id
【发布时间】:2011-06-11 14:47:57
【问题描述】:

在撰写本文时,TRANSACTION_ID() 不支持内存数据库。我可以使用序列表生成自己的 ID,但不清楚如何将现有 ID 传达给触发器。第一个触发器应该生成一个新 ID。后续触发器(在同一事务中)应共享现有 ID。

我可以使用线程局部变量来共享现有 ID,但这似乎很脆弱。有没有更好的方法来做到这一点?

【问题讨论】:

    标签: database h2


    【解决方案1】:

    使用序列而不是事务 ID 怎么样?

    CREATE SEQUENCE SEQ;
    

    事务中的第一个操作设置会话变量如下:

    SET @TID = SEQ.NEXTVAL;
    

    此事务中的其他操作使用会话变量:

    CALL @TID;
    

    【讨论】:

    • 我无法预测触发器将被调用的顺序,所以我不知道何时调用 NEXTVAL()。有没有办法注册一个钩子,以便在每个事务开始时只调用一次 NEXTVAL()?
    • 很抱歉我的回答不正确,因为如果其他会话调用NEXTVALCURRVAL 将会改变。所以这是行不通的......相反,如果CURRVAL,可能可以使用会话变量(SET @TID = SEQ.NEXTVAL)。
    • 这看起来很有希望,但h2database.com/html/tutorial.html#user_defined_variables 让我相信一个变量的定义将跨越多个事务(因为它是会话范围的,而不是事务范围的)。如何确保每笔交易使用不同的 ID?
    • 我刚刚检查过,如果我们每个连接有一个事务,则使用用户变量可以正常工作。我们需要一个针对每个连接的多个事务的解决方案,尤其是考虑到连接池的使用。有什么想法吗?
    【解决方案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 为 null,则注册一个新的事件侦听器并增加序列。
      2. 如果@TRANSACTION_ID 不为null,则从中获取当前事务ID。

    此解决方法的两个主要问题是:

    1. 它非常脆弱。如果 Session.unlinkAtCommit() 将来发生变化,它可能会破坏事件侦听器。
    2. 我们必须在每个触发器的顶部重复大量样板代码,以便检索事务 ID。

    将其实现为内置函数 TRANSACTION_LOCAL_ID() 会容易得多。此函数将返回类似于 HSQLDB 的特定于数据库实例的事务 id。

    【讨论】:

      猜你喜欢
      • 2021-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多