TransactionScope:具有不同数据库连接的嵌套事务(SQL Server 和 Postgresql)
Posted
技术标签:
【中文标题】TransactionScope:具有不同数据库连接的嵌套事务(SQL Server 和 Postgresql)【英文标题】:TransactionScope: nested transactions with different database connections (SQL Server & Postgresql) 【发布时间】:2021-07-16 18:57:06 【问题描述】:我正在编写一个带有事务的 SDK 方法,使用 NpgsqlConnection 供其他人使用。
当他们调用我的方法时,他们使用 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();
【问题讨论】:
【参考方案1】:以上是预期的行为——在同一个 TransactionScope 中加入两个连接会触发“分布式事务”;这在 PostgreSQL 术语中称为“准备好的事务”,您必须在配置中启用它(这是您在上面看到的错误的原因)。如果打算让两个单独的事务(一个用于 SQL Server,一个用于 PostgreSQL)分别提交,那么选择退出登记是正确的做法。您还应该能够使用 TransactopScopeOption.Suppress。
请注意,.NET Core 目前不支持分布式事务,仅在 .NET Framework (see this issue) 中支持。因此,除非您使用 .NET Framework,否则即使您在 PostgreSQL 中启用准备好的事务,这也不起作用。
【讨论】:
感谢您的解释!但是,如果我将 TransactionScopeOption 更改为 Suppress(对于使用 PostgreSQL 的“内部”方法),它仍然会引发您提到的异常。你知道设置 enlist=false 和使用 TransactionScopeOption.Suppress 的区别是什么原因吗? 不应该这样;我可以看到 Npgsql 在带有 TransactionScopeOption.Suppress 的 TransactionScope 内时不会加入任何事务 - 可能会再次检查您的代码以确保。以上是关于TransactionScope:具有不同数据库连接的嵌套事务(SQL Server 和 Postgresql)的主要内容,如果未能解决你的问题,请参考以下文章
实体框架:TransactionScope 有不同的 IsolationLevel
transactionscope报“此操作对该事务的状态无效”问题
关于分布式事务的一个误解:使用了TransactionScope就一定会开启分布式事务吗?