TransactionScope:具有不同数据库连接的嵌套事务(SQL Server & Postgresql)
TransactionScope: nested transactions with different database connections (SQL Server & Postgresql)
我正在使用 NpgsqlConnection 编写带有事务的 SDK 方法供其他人使用。
当他们调用我的方法时,他们使用 SqlConnection 和另一个事务来包装他们的数据库内容和我的 SDK 的数据库内容。
如果我在没有事务的情况下设置我的 SDK 方法,外部代码没有问题,我的 SDK 方法可以回滚。 (这也很奇怪。仍在找出原因。)
如果我用事务设置我的 SDK 方法,外部代码会崩溃并显示 TransactionAbortedException
:
System.Transactions.TransactionAbortedException : The transaction has aborted.
---- Npgsql.PostgresException : 55000: prepared transactions are disabled
目前我们在 SDK 的连接字符串中使用 enlist=false
来防止内部事务加入外部事务,但我想知道这种行为背后的原因。
这是我重现问题的代码:
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"))
using (var cmd = new SqlCommand("insert into [Test].[dbo].[Test] (Id, \"Name\") values (1, 'A')", conn))
{
await conn.OpenAsync();
var result = await cmd.ExecuteNonQueryAsync();
await SdkMethodToDoStuffWithNpgsql(1);
scope.Complete();
}
}
我 SdkMethodToDoStuffWithNpgsql()
在注入了 Postgres 上下文的存储库中模拟一个方法。
public async Task SdkMethodToDoStuffWithNpgsql(long id)
{
var sqlScript = @"UPDATE test SET is_removal = TRUE WHERE is_removal = FALSE AND id = @id;
INSERT INTO log(id, data) SELECT id, data FROM log WHERE id = @id";
using (var scope = new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new NpgsqlConnection(this._context.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = new NpgsqlCommand(sqlScript, conn))
{
cmd.Parameters.Add(new NpgsqlParameter("id", NpgsqlDbType.Bigint) { Value = id });
await cmd.PrepareAsync();
var result = await cmd.ExecuteNonQueryAsync();
if (result != 2)
{
throw new InvalidOperationException("failed");
}
scope.Complete();
}
}
}
}
以上是预期的行为 - 在同一个 TransactionScope 中征用两个连接会触发“分布式事务”;这在 PostgreSQL 术语中称为“准备好的事务”,您必须在配置中启用它(这是您在上面看到的错误的原因)。如果打算有两个单独的事务(一个用于 SQL 服务器,一个用于 PostgreSQL),分别提交,那么选择退出是正确的做法。您还应该能够使用 TransactopScopeOption.Suppress.
请注意,分布式事务目前在 .NET Core 中不受支持,仅在 .NET Framework 中受支持 (see this issue)。因此,除非您使用的是 .NET Framework,否则即使您在 PostgreSQL.
中启用准备好的事务,这也不会起作用
我正在使用 NpgsqlConnection 编写带有事务的 SDK 方法供其他人使用。
当他们调用我的方法时,他们使用 SqlConnection 和另一个事务来包装他们的数据库内容和我的 SDK 的数据库内容。
如果我在没有事务的情况下设置我的 SDK 方法,外部代码没有问题,我的 SDK 方法可以回滚。 (这也很奇怪。仍在找出原因。)
如果我用事务设置我的 SDK 方法,外部代码会崩溃并显示 TransactionAbortedException
:
System.Transactions.TransactionAbortedException : The transaction has aborted.
---- Npgsql.PostgresException : 55000: prepared transactions are disabled
目前我们在 SDK 的连接字符串中使用 enlist=false
来防止内部事务加入外部事务,但我想知道这种行为背后的原因。
这是我重现问题的代码:
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new SqlConnection(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"))
using (var cmd = new SqlCommand("insert into [Test].[dbo].[Test] (Id, \"Name\") values (1, 'A')", conn))
{
await conn.OpenAsync();
var result = await cmd.ExecuteNonQueryAsync();
await SdkMethodToDoStuffWithNpgsql(1);
scope.Complete();
}
}
我 SdkMethodToDoStuffWithNpgsql()
在注入了 Postgres 上下文的存储库中模拟一个方法。
public async Task SdkMethodToDoStuffWithNpgsql(long id)
{
var sqlScript = @"UPDATE test SET is_removal = TRUE WHERE is_removal = FALSE AND id = @id;
INSERT INTO log(id, data) SELECT id, data FROM log WHERE id = @id";
using (var scope = new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
},
TransactionScopeAsyncFlowOption.Enabled))
{
await using (var conn = new NpgsqlConnection(this._context.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = new NpgsqlCommand(sqlScript, conn))
{
cmd.Parameters.Add(new NpgsqlParameter("id", NpgsqlDbType.Bigint) { Value = id });
await cmd.PrepareAsync();
var result = await cmd.ExecuteNonQueryAsync();
if (result != 2)
{
throw new InvalidOperationException("failed");
}
scope.Complete();
}
}
}
}
以上是预期的行为 - 在同一个 TransactionScope 中征用两个连接会触发“分布式事务”;这在 PostgreSQL 术语中称为“准备好的事务”,您必须在配置中启用它(这是您在上面看到的错误的原因)。如果打算有两个单独的事务(一个用于 SQL 服务器,一个用于 PostgreSQL),分别提交,那么选择退出是正确的做法。您还应该能够使用 TransactopScopeOption.Suppress.
请注意,分布式事务目前在 .NET Core 中不受支持,仅在 .NET Framework 中受支持 (see this issue)。因此,除非您使用的是 .NET Framework,否则即使您在 PostgreSQL.
中启用准备好的事务,这也不会起作用