为内存数据库生成事务 id

Posted

技术标签:

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

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

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

【问题讨论】:

【参考方案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() 来监听事务提交(我假设这个钩子也会回滚,但我还没有验证这一点)

由于我们无法预测触发器调用的数量和顺序,我们必须在每个触发器中进行以下检查:

    如果 @TRANSACTION_ID 为 null,则注册一个新的事件侦听器并增加序列。 如果@TRANSACTION_ID 不为null,则从中获取当前事务ID。

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

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

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

【讨论】:

以上是关于为内存数据库生成事务 id的主要内容,如果未能解决你的问题,请参考以下文章

mybatis自增id绑定数据库数据和内存对象的id

STM 软件事务内存——本质是为提高并发,通过事务来管理内存的读写访问以避免锁的使用

使用带有事务的内存数据库进行单元测试时,如何抑制 InMemoryEventId.TransactionIgnoredWarning?

关于MySQL的commit非规律性失败案例的深入分析

利用事务一次提交大量插入操作会撑爆数据库服务器内存吗?

Redis 小白指南- 事务Watch 命令过期消息通知管道优化内存空间