Table 在应用程序关闭之前,Firebird 并没有真正完成掉落
Table drops not actually done by Firebird until application closed
我有一个 .net 测试应用程序,它创建 table、使用它们,然后删除它们,多次。
当我 运行 这个应用程序针对 Firebird 3.0.2 数据库和 Firebird ADO.Net Data Provider 5.9.1 时,它很快就失败了,因为它必须创建一个 table与之前删除的同名:table 已经存在!
重新启动应用程序可以避免麻烦,但我不想在每次测试后重新启动它。
这个 question 非常相似,但悬而未决,直接使用 Firebird isql
工具而不是 .Net 应用程序。
有没有办法在不重新启动应用程序的情况下实际删除 Firebird 中的 table?
此应用程序测试了许多其他数据库,没有这个问题:SQL-Server、SQL-Server Compact Edition、MySql、SQLite、Oracle、Postgre SQL.
这是 Firebird 的 MCVE 失败。将前两行替换为具有连接字符串的 suitable 代码。所有其他代码只是 Firebird ADO.Net 数据提供程序和 NUnit。它并没有像我的实际应用程序那样完全失败,但我认为它是相同的潜在问题。
[Test]
public void CreateSelectDrop()
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
var cnxStr = cfg.Properties[Environment.ConnectionString];
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "create table test (id int not null primary key)";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "insert into test (id) values (1)";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "select id from test";
using (var reader = cmd.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(1, reader.GetInt32(0));
Assert.IsFalse(reader.Read());
}
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "delete from test";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "drop table test";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
}
仅从 table 中选择是不够的。只有在我将删除添加到测试中后,问题才出现。它在最后一次提交事务时失败,即删除的事务提交,并显示消息:
FirebirdSql.Data.FirebirdClient.FbException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
----> FirebirdSql.Data.Common.IscException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
at FirebirdSql.Data.FirebirdClient.FbTransaction.Commit()
at NHibernate.Test.DialectTest.FirebirdDialectFixture.CreateSelectDrop()
根据Nathan Brown in a Github discussion,这个问题似乎仅限于 Firebird ADO.Net 数据提供者。他将范围缩小到从 2.7.7 版本切换到 3.0.0。
似乎 table 丢弃被延迟,直到使用它们的连接实际关闭,而不仅仅是返回到池中。清除连接池会导致那些延迟的丢弃实际完成。
所以在 table drops 之前添加类似下面的代码可以解决我的问题:
using (var connection = GetConnection())
{
FbConnection.ClearPool(connection);
}
这个解决方案是在 5 000 多人的独立测试中 here 找到的。
看起来还有另一种选择,改为调用 FbConnection.ClearAllPool()
。虽然我没有检查过,但前者很可能只清除提供的连接连接字符串的连接池,而后者清除所有连接的池,而不管它们的连接字符串。
由于它是一个具有一些通用逻辑和一些特殊性的测试应用程序,我实际用作解决方案的代码将是:
// Firebird will pool each connection created during the test and will
// marked as used any table referenced by queries. It will delays those
// tables drop until connections are actually closed.
// This results in other tests failing when they try to create tables with
// same name.
// By clearing the connection pool the tables will get dropped. This is done
// by the following code.
// Moved from NH1908 test case, contributed by Amro El-Fakharany.
var clearConnection = Sfi.ConnectionProvider.GetConnection();
try
{
var fbConnectionType = clearConnection.GetType();
var clearPool = fbConnectionType.GetMethod("ClearPool");
clearPool.Invoke(null, new object[] {clearConnection});
}
finally
{
Sfi.ConnectionProvider.CloseConnection(clearConnection);
}
我有一个 .net 测试应用程序,它创建 table、使用它们,然后删除它们,多次。
当我 运行 这个应用程序针对 Firebird 3.0.2 数据库和 Firebird ADO.Net Data Provider 5.9.1 时,它很快就失败了,因为它必须创建一个 table与之前删除的同名:table 已经存在!
重新启动应用程序可以避免麻烦,但我不想在每次测试后重新启动它。
这个 question 非常相似,但悬而未决,直接使用 Firebird isql
工具而不是 .Net 应用程序。
有没有办法在不重新启动应用程序的情况下实际删除 Firebird 中的 table?
此应用程序测试了许多其他数据库,没有这个问题:SQL-Server、SQL-Server Compact Edition、MySql、SQLite、Oracle、Postgre SQL.
这是 Firebird 的 MCVE 失败。将前两行替换为具有连接字符串的 suitable 代码。所有其他代码只是 Firebird ADO.Net 数据提供程序和 NUnit。它并没有像我的实际应用程序那样完全失败,但我认为它是相同的潜在问题。
[Test]
public void CreateSelectDrop()
{
var cfg = TestConfigurationHelper.GetDefaultConfiguration();
var cnxStr = cfg.Properties[Environment.ConnectionString];
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "create table test (id int not null primary key)";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "insert into test (id) values (1)";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "select id from test";
using (var reader = cmd.ExecuteReader())
{
Assert.IsTrue(reader.Read());
Assert.AreEqual(1, reader.GetInt32(0));
Assert.IsFalse(reader.Read());
}
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "delete from test";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
using (var cnx = new FbConnection(cnxStr))
{
cnx.Open();
using (var tran = cnx.BeginTransaction())
{
using (var cmd = cnx.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "drop table test";
cmd.ExecuteNonQuery();
}
tran.Commit();
}
}
}
仅从 table 中选择是不够的。只有在我将删除添加到测试中后,问题才出现。它在最后一次提交事务时失败,即删除的事务提交,并显示消息:
FirebirdSql.Data.FirebirdClient.FbException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
----> FirebirdSql.Data.Common.IscException : lock conflict on no wait transaction
unsuccessful metadata update
object TABLE "TEST" is in use
at FirebirdSql.Data.FirebirdClient.FbTransaction.Commit()
at NHibernate.Test.DialectTest.FirebirdDialectFixture.CreateSelectDrop()
根据Nathan Brown in a Github discussion,这个问题似乎仅限于 Firebird ADO.Net 数据提供者。他将范围缩小到从 2.7.7 版本切换到 3.0.0。
似乎 table 丢弃被延迟,直到使用它们的连接实际关闭,而不仅仅是返回到池中。清除连接池会导致那些延迟的丢弃实际完成。
所以在 table drops 之前添加类似下面的代码可以解决我的问题:
using (var connection = GetConnection())
{
FbConnection.ClearPool(connection);
}
这个解决方案是在 5 000 多人的独立测试中 here 找到的。
看起来还有另一种选择,改为调用 FbConnection.ClearAllPool()
。虽然我没有检查过,但前者很可能只清除提供的连接连接字符串的连接池,而后者清除所有连接的池,而不管它们的连接字符串。
由于它是一个具有一些通用逻辑和一些特殊性的测试应用程序,我实际用作解决方案的代码将是:
// Firebird will pool each connection created during the test and will
// marked as used any table referenced by queries. It will delays those
// tables drop until connections are actually closed.
// This results in other tests failing when they try to create tables with
// same name.
// By clearing the connection pool the tables will get dropped. This is done
// by the following code.
// Moved from NH1908 test case, contributed by Amro El-Fakharany.
var clearConnection = Sfi.ConnectionProvider.GetConnection();
try
{
var fbConnectionType = clearConnection.GetType();
var clearPool = fbConnectionType.GetMethod("ClearPool");
clearPool.Invoke(null, new object[] {clearConnection});
}
finally
{
Sfi.ConnectionProvider.CloseConnection(clearConnection);
}