为内存数据库生成事务 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()? 很抱歉我的回答不正确,因为如果其他会话调用NEXTVAL
,CURRVAL
将会改变。所以这是行不通的......相反,如果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的主要内容,如果未能解决你的问题,请参考以下文章
STM 软件事务内存——本质是为提高并发,通过事务来管理内存的读写访问以避免锁的使用
使用带有事务的内存数据库进行单元测试时,如何抑制 InMemoryEventId.TransactionIgnoredWarning?