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.

中启用准备好的事务,这也不会起作用