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就一定会开启分布式事务吗?

探索逻辑事务 TransactionScope

EF Core 是不是支持 TransactionScope?

TransactionScope 过早完成